让你的RatingBar更好用

RatingBar是评论模块中常用的一个View,它提供的功能也能够满足我们开发过程中的逻辑部分,但是UI的定制性方面还有待改进。

最明显的两个问题:

  • 不能直接通过设置高度设置RatingBar中星星的大小;
  • 不能设置星星之间的间距;

虽然这两个问题可通过切图来解决,但是如果需要多个尺寸,则需要多个尺寸的切图,相对来说还是比较麻烦。为了让RatingBar更好用,我们可以通过自定义一个StarRatingBar来实现我们的需求。

StarRatingBar提供的功能

  • 可定义星星的大小,颜色;
  • 支持绘制星星和使用星星图片两种方式,使用图片时,图片会根据定义的星星大小进行缩放;
  • 可定义星星之间的间距,步长;
  • 支持点击和滑动修改RatingBar的值;

StarRatingBar的属性

<declare-styleable name="StarRatingBar">
    <!-- 默认的星星图片 优先级高于绘制的星星 -->
    <attr name="defaultStar" format="reference"/>
    <!-- 选中的星星图片 -->
    <attr name="star" format="reference"/>
    <!-- 默认的星星颜色 -->
    <attr name="defaultStarColor" format="color"/>
    <!-- 选中的星星颜色 -->
    <attr name="starColor" format="color"/>
    <!-- 星星的个数 -->
    <attr name="starNum" format="integer"/>
    <!-- 可选的星星的步长 如:0.5表示可选半颗星 -->
    <attr name="starStep" format="float"/>
    <!-- 星星之间的间距 -->
    <attr name="starGap" format="dimension"/>
    <!-- 星星的大小 -->
    <attr name="starSize" format="dimension"/>
    <!-- 选中星星的部分 小于等于starNum -->
    <attr name="rating" format="float"/>
    <!-- 星星是否响应事件 默认为true 表示仅作为展示 -->
    <attr name="isIndicator" format="boolean"/>
</declare-styleable>

源码:

源码地址: StarRatingBar

public class StarRatingBar extends View {

    private Drawable mDefaultStar;
    private Drawable mStar;
    private int mDefaultStarColor;
    private int mStarColor;
    private int mStarNum;
    private float mStarStep;
    private int mStarGap;
    private int mStarSize;
    private float mRating;
    private boolean mIsIndicator;

    private Paint starPaint;

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

    public StarRatingBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StarRatingBar);
        mDefaultStar = typedArray.getDrawable(R.styleable.StarRatingBar_defaultStar);
        mStar = typedArray.getDrawable(R.styleable.StarRatingBar_star);
        mDefaultStarColor = typedArray.getColor(R.styleable.StarRatingBar_defaultStarColor, Color.GREEN);
        mStarColor = typedArray.getColor(R.styleable.StarRatingBar_starColor, Color.YELLOW);
        mStarNum = typedArray.getInteger(R.styleable.StarRatingBar_starNum, 5);
        mStarStep = typedArray.getFloat(R.styleable.StarRatingBar_starStep,  0.2f);
        mStarGap = typedArray.getDimensionPixelOffset(R.styleable.StarRatingBar_starGap, 10);
        mStarSize = typedArray.getDimensionPixelOffset(R.styleable.StarRatingBar_starSize, 80);
        mRating = typedArray.getFloat(R.styleable.StarRatingBar_rating, 1.5f);
        mIsIndicator = typedArray.getBoolean(R.styleable.StarRatingBar_isIndicator, true);
        typedArray.recycle();

        starPaint = new Paint();
        starPaint.setAntiAlias(true);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int radius = mStarSize / 2;
        canvas.translate(radius, radius);
        if (mDefaultStar != null) {
            drawStarDrawable(canvas, mDefaultStar, mStarNum);
        } else {
            starPaint.setColor(mDefaultStarColor);
            int gap = 0;
            for (int i = 0; i < mStarNum; i++) {
                drawStar(canvas, starPaint, i * mStarSize + gap, radius);
                gap += mStarGap;
            }
        }

        // 设置可视区域
        int size = Math.round(mRating);
        float decimal = 0;
        // 根据步长获取小数位
        if (size > mRating) {
            decimal = (mRating - size + 1);
            int rate = (int) (decimal / mStarStep);
            decimal = rate * mStarStep;
        }
        int right = (int) (((int)mRating) * (mStarSize + mStarGap) + decimal * mStarSize - radius);
        canvas.clipRect(-radius, -radius, right, mStarSize - radius);
        if (mStar != null) {
            drawStarDrawable(canvas, mStar, size);
        } else {
            Paint newPaint = new Paint();
            newPaint.setAntiAlias(true);
            newPaint.setColor(mStarColor);
            int gap = 0;
            for (int i = 0; i < size; i++) {
                drawStar(canvas, newPaint, i * mStarSize + gap, radius);
                gap += mStarGap;
            }
        }
    }

    private void drawStarDrawable(Canvas canvas, Drawable starDrawable, int starNum) {
        Bitmap bitmap = ((BitmapDrawable)starDrawable).getBitmap();
        int gap = 0;
        int radius = mStarSize / 2;
        for (int i = 0; i < starNum; i++) {
            Rect desRect = new Rect(i * mStarSize - radius + gap , -radius, (i+1) * mStarSize - radius + gap, mStarSize - radius);
            canvas.drawBitmap(bitmap, null, desRect, starPaint);
            gap += mStarGap;
        }
    }

    private void drawStar(Canvas canvas, Paint paint, int startX, int radius) {
        Point[] points = new Point[5];
        for (int i = 0; i < 5; i++) {
            points[i] = new Point();
            points[i].x = startX + (int) (radius * Math.cos(Math.toRadians(72 * i - 18)));
            points[i].y = (int) (radius * Math.sin(Math.toRadians(72 * i - 18)));
        }
        Path path = new Path();
        path.moveTo(points[0].x, points[0].y);
        int i = 2;
        while (i != 5) {
            if (i >= 5) {
                i %= 5;
            }
            path.lineTo(points[i].x, points[i].y);
            i += 2;
        }
        path.close();
        canvas.drawPath(path, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mIsIndicator) {
            return super.onTouchEvent(event);
        }

        mRating = event.getX() / (mStarSize + mStarGap);
        invalidate();
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.AT_MOST) {
            widthSize = getPaddingLeft() + getPaddingRight();
            if (mStarNum > 0) {
                widthSize += mStarNum * mStarSize + (mStarNum - 1) * mStarGap;
            }
        } else if (widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = getSuggestedMinimumWidth();
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = getPaddingTop() + getPaddingBottom() + mStarSize;
        } else if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = getSuggestedMinimumHeight();
        }

        setMeasuredDimension(widthSize, heightSize);
    }

    // ... 属性的setter/getter方法
}

实例:

使用绘制的星星样式:

<com.sxu.starratingbar.StarRatingBar
    android:id="@+id/rating_bar"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:defaultStarColor="#999999"
    app:starColor="#ffbb00"
    app:starSize="32dp"
    app:starGap="6dp"/>

使用图片的星星样式:

<com.sxu.starratingbar.StarRatingBar
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    app:defaultStar="@mipmap/star_unselected_icon"
    app:star="@mipmap/star_selected_icon"
    app:starSize="24dp"
    app:starGap="6dp"/>

代码运行结果:

image

,