Android 自定义RatingBar

122 阅读2分钟

1.实现效果

可以点击或滑动选评分

Screen_recording_202 -original-original (1).gif

准备: 1.需要选中和未选中的图片资源。 2.自定义CustomRatingBar, 确定自定义属性

<declare-styleable name="CustomRatingBar"
<!-- 总评分大小选中和未选中图片资源评分item之间的间隔-->
    <attr name="gradeNumber" format="integer" />
    <attr name="startNormal" format="reference" />
    <attr name="startFocus" format="reference" />
    <attr name="itemPadding" format="dimension" />
</declare-styleable>

3.自定义view:CustomRatingBar (1)绘制初始状态,即未选中的状态 (2)处理交互的效果,即处理点击和移动的事件,计算出需绘制的评分个数

代码:

public class CustomRatingBar extends View {

    private static final int DEFAULT_GRADE_NUMBER = 5;
    /** 评分的等级数量*/
    private int mGradeNumber = DEFAULT_GRADE_NUMBER;
    /** 每个星级评分之间的间隔*/
    private int mItemPadding = 0;
    /** 当前选中的评分*/
    private int mCurrentFocusNumber = 0;
    private Bitmap mStarNormalBitmap;
    private Bitmap mStarFocusBitmap;

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

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

    public CustomRatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomRatingBar);
        int starNormalId = array.getResourceId(R.styleable.CustomRatingBar_startNormal, R.drawable.rating_star_on_normal);
        mStarNormalBitmap = BitmapFactory.decodeResource(context.getResources(), starNormalId);
        int focusNormalId = array.getResourceId(R.styleable.CustomRatingBar_startFocus, R.drawable.rating_star_on_focus);
        mStarFocusBitmap = BitmapFactory.decodeResource(context.getResources(), focusNormalId);
        
        mGradeNumber = array.getInt(R.styleable.CustomRatingBar_gradeNumber, DEFAULT_GRADE_NUMBER);
        float itemPaddingDp = array.getDimension(R.styleable.CustomRatingBar_itemPadding, 0);
        mItemPadding = dip2px(context, itemPaddingDp);
        array.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 以图片的高度作为控件的高度
        int height = mStarNormalBitmap.getHeight();
        int width = MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (int i = 0; i < mGradeNumber; i++) {
            int itemPadding = mItemPadding;
            if (i == 0) {
                itemPadding = 0;
            }
            // 绘制图片的x坐标:图片宽度 + 间隔(绘制第一个时不需要加上间隔)
            int x = (mStarNormalBitmap.getWidth() + itemPadding) * i;
            Bitmap drawBitmap = i < mCurrentFocusNumber ? mStarFocusBitmap : mStarNormalBitmap;
            canvas.drawBitmap(drawBitmap, x, 0, null);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
            int x = (int) event.getX();
            int currentFocusNumber = (int) (x * 1.0 / (mStarNormalBitmap.getWidth() + mItemPadding)) + 1;
            // 减少绘制次数,评分不同时才重绘
            if (mCurrentFocusNumber != currentFocusNumber) {
                mCurrentFocusNumber = currentFocusNumber;
                invalidate();
            }
        }
        // 第一次接收到down事件,return true,后续的move和up事件才能传递到当前view。 比如可以单独针对down事件return true,也是可以正常绘制的
        return true;
    }

    // 根据手机的分辨率从 dp 的单位 转成为 px(像素)
    public static int dip2px(Context context, float dpValue) {
        // 获取当前手机的像素密度(1个dp对应几个px)
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f); // 四舍五入取整
    }
}