让你的RatingBar更好用

1,853 阅读3分钟

原文地址:tinycoder.cc/2018/04/24/…

背景

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