自定义View - 评分控件RatingBar

1. 说明


基于我们前边写的自定义View的入门、自定义TextView、仿QQ运动步数、玩转字体变色、类似QQ运动步数进度条、3个形状来回切换等自定义View外,这节课我们来写一个评分控件,类似于淘宝、腾讯课堂的评分控件 —— RatingBar。

我们前边写的上边的6个效果,都属于静态自定义View控件,不涉及用户去触摸该控件;
但是,从这节课开始,我们所写的效果就属于交互控件,何为交互控件? 就是用户可以在这个控件上边去触摸,即就是按下、移动、抬起。

2. 效果图如下:


刚开始进入app是一个图片都没有选中,如图所示:


图片.png

手指触摸后,就选中触摸的图片,如图所示:


图片.png

3. 思路分析


3.1:首先刚进来是初始化的样子:

1>:就是只有5张未被选中的图片,这里需要自定义属性、2张图片资源、评分的等级数量;
2>:指定控件的宽高;
3>:绘制图片Bitmap;

3.2:需要在onTouch()方法中处理用户和该控件的交互事件;

3.3:优化部分:

1>:如果分数相同,就不需要再去绘制了;
2>:就是尽量减少onDraw()方法的调用;
3>:onTouchEvent()方法必须return true,如果返回false,就表示事件不消费,第一次可以进入MotionEvent.ACTION_MOVE事件,以后就不能进来了,所以这里必须return true,并且以后只要是自定义View,并且凡是涉及到用户触摸事件,onTouchEvent()方法都return true;


4. 代码如下


自定义RatingBar.java代码如下:

/**
 * Email: [email protected]
 * Created by JackChen 2018/3/19 15:01
 * Version 1.0
 * Params:
 * Description:  评分控件RatingBar
*/

public class RatingBar extends View {


    // 用BitmapFactory 解析出来的没有选中的图片
    private Bitmap mStarNormalBitmap;
    // 用BitmapFactory 解析出来的已经选中的图片
    private Bitmap mStarFocusBitmap;
    // 总共的分数
    private int mGradeNumber = 5 ;
    // 当前的分数
    private int mCurrentNumber = 0 ;


    public RatingBar(Context context) {
        this(context,null);
    }

    public RatingBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // 以下是获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatingBar);
        // 获取资源id
        int starNormalId = array.getResourceId(R.styleable.RatingBar_starNormal, 0);
        if (starNormalId == 0){
            throw new RuntimeException("请设置属性 starNormal") ;
        }
        // 用BitmapFactory 解析资源
        mStarNormalBitmap = BitmapFactory.decodeResource(getResources(), starNormalId);


        int starFocusId = array.getResourceId(R.styleable.RatingBar_starFocus, 0);
        if (starFocusId == 0){
            throw new RuntimeException("请设置属性 starFocus") ;
        }
        // 用BitmapFactory 解析资源
        mStarFocusBitmap = BitmapFactory.decodeResource(getResources(), starFocusId);

        // 获取分数
        mGradeNumber = array.getInt(R.styleable.RatingBar_gradeNumber , mGradeNumber) ;
        array.recycle();

    }


    /**
     * 测量控件的宽高
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 控件的宽度
        int width = mStarFocusBitmap.getWidth() * mGradeNumber;
        int height = mStarFocusBitmap.getHeight();
        setMeasuredDimension(width , height);
    }


    /**
     * 画选中的图片和未选中的图片
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);
        for (int i = 0; i < mGradeNumber; i++) {
            // i * 星星的宽度
            int x = i * mStarFocusBitmap.getWidth() ;

            // 触摸的时候 mCurrentGrade的值是不断变化的
            if (mCurrentNumber > i){
                canvas.drawBitmap(mStarFocusBitmap , x , 0 , null);
            }else{
                canvas.drawBitmap(mStarNormalBitmap , x , 0 , null);
            }
        }
    }


    /**
     * 处理用户触摸
     * 移动、按下、抬起,处理逻辑都是一样的,判断手指的位置,根据当前位置计算出分数,然后去刷新并显示
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
//            case MotionEvent.ACTION_DOWN:   // 尽量减少onDraw()方法的调用
            case MotionEvent.ACTION_MOVE:
//            case MotionEvent.ACTION_UP:    // 尽量减少onDraw()

                // 手指的位置,计算当前的分数
                float moveX = event.getX();
                // 计算当前分数
                int currentGrade = (int) (moveX/mStarFocusBitmap.getWidth()+1);

                //范围问题
                if (currentGrade < 0){
                    currentGrade = 0 ;
                }

                if (currentGrade > mGradeNumber){
                    currentGrade = mGradeNumber ;
                }

                // 分数相同的情况下,就不要再去绘制了,尽量减少onDraw()方法的调用
                if (currentGrade == mGradeNumber){
                    return true ;
                }

                // 判断完异常情况之后,最后记录当前分数,然后重新绘制
                mCurrentNumber = currentGrade ;
                invalidate();    // 因为 invalidate()方法会调用
                break;
        }
        return true;
    }
}

attrs.xml资源文件如下:




    

        
        
        
    

直接在activity_main.xml布局文件中引用该RatingBar控件即可:




    


具体代码已上传至github:
https://github.com/shuai999/View_day06_2.git

你可能感兴趣的:(自定义View - 评分控件RatingBar)