原文地址: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"/>
代码运行结果: