Android有个RatingBar的评分控件,不过在实际项目里局限性非常明显(无法准确并动态设置控件的大小)。所以,我有必要写个控件(不继承RatingBar),来弥补这个缺陷。
自定义控件一定要用到自定义属性(attrs.xml里设置),这将会大幅度提高开发效率。以下是RatingBarView用到的自定义属性:
<declare-styleable name="RatingBarView">
<!-- 星星宽度 -->
<attr name="starWidth" format="dimension" />
<!-- 星星高度 -->
<attr name="starHeight" format="dimension" />
<!-- 星星数量 -->
<attr name="starNum" format="integer" />
<!-- 星星当前进度 -->
<attr name="starRating" format="dimension" />
<!-- 星星之间的空隙 -->
<attr name="starSpace" format="dimension" />
<!--进度方式,整星还是半星-->
<attr name="starStep">
<enum name="Full" value="1" />
<enum name="Half" value="2" />
</attr>
<!-- 没选中 -->
<attr name="starDrawableEmpty" format="reference|color"/>
<!-- 选中,进度是半星 -->
<attr name="starDrawableHalf" format="reference|color"/>
<!-- 选中,进度是整星 -->
<attr name="starDrawableFill" format="reference|color"/>
</declare-styleable>
设置好自定义属性,在自定义控件(xxView extends View)的构造方法xxView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)中获取自己定义好的属性
//无论调用哪个构造方法,都会调用第三个
//一般我是直接在.xml中使用控件,不会去用代码创建
public RatingBarView(Context context) {
this(context, null);
}
public RatingBarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RatingBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mGradientHelper = new GradientHelper(context,attrs);
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RatingBarView);
mStarWidth = typedArray.getDimensionPixelSize(R.styleable.RatingBarView_starWidth, 30);
mStarHeight = typedArray.getDimensionPixelSize(R.styleable.RatingBarView_starHeight, 30);
mStarNum = typedArray.getInt(R.styleable.RatingBarView_starNum, 5);
mStarSpace = typedArray.getDimensionPixelSize(R.styleable.RatingBarView_starSpace, 0);
mStarStep = typedArray.getDimensionPixelSize(R.styleable.RatingBarView_starStep, 1);
mStarRating = typedArray.getDimension(R.styleable.RatingBarView_starRating, 3);
mStarDrawableEmpty = typedArray.getDrawable(R.styleable.RatingBarView_starDrawableEmpty);
mStarDrawableHalf = typedArray.getDrawable(R.styleable.RatingBarView_starDrawableHalf);
mStarDrawableFill = typedArray.getDrawable(R.styleable.RatingBarView_starDrawableFill);
typedArray.recycle();
}
}
到这里,我需要的条件已经集齐,可以绘制星星了。
目标效果图(不包括触摸事件):
实现上图,需要:星星宽高、星星数量、当前星星显示数量、星星之间的空隙、星星选择与未选中时的图片(自定义属性已经包含了)
实现思路:
- 根据星星数量和空隙外加控件的padding计算控件的宽高(宽度=星星宽度*星星数量+(星星数量-1)*星星间隙+ getPaddingLeft() + getPaddingRight(),高度=星星高度+ getPaddingTop() + getPaddingBottom())
- 计算星星的显示位置
- 按位置绘制星星
实现核心代码:
//设置控件的宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//宽度=星星宽度*星星数量+(星星数量-1)*星星间隙+ getPaddingLeft() + getPaddingRight()
int starSumWidth = mStarWidth*mStarNum + (mStarNum-1)*mStarSpace + getPaddingLeft() + getPaddingRight();
int measureWidth = MeasureSpec.makeMeasureSpec(starSumWidth, MeasureSpec.EXACTLY);
int measureHeight = MeasureSpec.makeMeasureSpec(mStarHeight + getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY);
setMeasuredDimension(measureWidth, measureHeight);
}
//记录每个星星的位置
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int left = getPaddingLeft();
int top = getPaddingTop();
for (int i=0;i<mStarNum;i++) {
Rect rect = new Rect(left, top, mStarWidth+left,mStarHeight+top);
mStarRect[i] = rect;
left += (mStarWidth+mStarSpace);
}
}
//按星星位置绘制星星,在onDraw()中使用
private void drawStar(Canvas canvas) {
for (int i=0;i<mStarRect.length;i++) {
Drawable drawable = new BitmapDrawable(null,drawableToBitmap(mStarDrawableEmpty));
if (i <= mStarRating-1) {
drawable = new BitmapDrawable(null,drawableToBitmap(mStarDrawableFill));
}
Log.e("第"+i,""+new Gson().toJson(mStarRect[i]));
drawable.setBounds(mStarRect[i]);
drawable.draw(canvas);
}
}
圆角背景实现:我是使用GradientDrawable来实现圆角背景(自定义TextView中用到过)
gradientColorArr[0] = gradientColorStart;//渐变开始颜色
gradientColorArr[1] = gradientColorEnd;//渐变结束颜色
//从左到右的渐变
GradientDrawable gradientDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,gradientColorArr);
gradientDrawable.setBounds(bounds);
gradientDrawable.setShape(GradientDrawable.RECTANGLE);
gradientDrawable.setCornerRadii(radiusGradientArray);//圆角设置
运行效果图