一、背景

视觉过来提了一个需求,要求完成一个星级评分控件,该控件中的星星的颜色需要实现渐变的效果,并且没有渐变的规律,也就是说各个星星的颜色需要不一样,效果如下:

二、问题分析

星星控件对应的控件是android.support.v7.widget.AppCompatRatingBar,利用这个控件可以实现星级评分效果,但是每个星星的颜色是一样的,效果如下:

具体的实现代码如下:

    <android.support.v7.widget.AppCompatRatingBarandroid:id="@+id/popup_ratingbar"android:layout_gravity="center_horizontal"android:layout_width="wrap_content"android:layout_height="wrap_content"style="@style/Widget.DeviceDefault.Light.RatingBar.Color.DodgerBlue"android:numStars="5"android:rating="3.5"android:isIndicator="false" />

style的定义Widget.DeviceDefault.Light.RatingBar.Color.DodgerBlue:

<style name="Widget.DeviceDefault.Light.RatingBar.Color.DodgerBlue" parent="android:style/Widget.DeviceDefault.Light.RatingBar"><item name="android:progressDrawable">@drawable/mz_ratingbar_full_light_color_dodgerblue</item><item name="android:indeterminateDrawable">@drawable/mz_ratingbar_full_light_color_dodgerblue</item><item name="android:minHeight">29.3dip</item>    </style>

mz_ratingbar_full_light_color_dodgerblue.xml

<layer-list xmlns:android="http://schemas.android.com/apk/res/android"><item android:id="@+android:id/background" android:drawable="@drawable/mz_btn_bigstar_off" /><item android:id="@+android:id/secondaryProgress" android:drawable="@drawable/mz_btn_bigstar_off" /><item android:id="@+android:id/progress" android:drawable="@drawable/mz_ratingbar_full_filled_light_color_dodgerblue" />
</layer-list>

mz_ratingbar_full_filled_light_color_dodgerblue.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true"android:state_window_focused="true"android:drawable="@drawable/mz_btn_bigstar_on_pressed_color_dodgerblue" /><item android:drawable="@drawable/mz_btn_bigstar_on_color_dodgerblue" /></selector>

mz_btn_bigstar_on_pressed_color_dodgerblue、mz_btn_bigstar_on_color_dodgerblue、mz_btn_bigstar_off是三种星星图片,分别对应星星的按下状态、正常状态和背景:

从上面的代码可以看到,AppCompatRatingBar提供了一个设置progressDrawable的接口,通过这个接口我们可以设置星星的样式,而我们在设置progressDrawable的时候,传入进去的只是一张星星的图片,那它是怎样做到绘制多个星星的呢?

首先,我们来看一下AppCompatRatingBar类的继承关系:

AppCompatRatingBar类

    @Overrideprotected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);Bitmap sampleTile = mAppCompatProgressBarHelper.getSampleTime();if (sampleTile != null) {// 根据星星个数计算控件的宽度final int width = sampleTile.getWidth() * getNumStars();setMeasuredDimension(ViewCompat.resolveSizeAndState(width, widthMeasureSpec, 0),getMeasuredHeight());}}

AppCompatRatingBar会根据星星的个数来计算控件的宽度,也就是控件的宽度等于:单个星星的宽度 X 星星个数。
在AppCompatRatingBar中并没有绘制的逻辑,查看父类RatingBar的代码,也没用重写onDraw方法,继续在父类中查找,在ProgressBar的onDraw方法中,有绘制星星的逻辑

    @Overrideprotected synchronized void onDraw(Canvas canvas) {super.onDraw(canvas);drawTrack(canvas);}void drawTrack(Canvas canvas) {final Drawable d = mCurrentDrawable;if (d != null) {...d.draw(canvas);...}}

在ProgressBar的构造函数中,会对progressDrawable进行处理

    public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);...if (progressDrawable != null) {// Calling setProgressDrawable can set mMaxHeight, so make sure the// corresponding XML attribute for mMaxHeight is read after calling// this method.if (needsTileify(progressDrawable)) {setProgressDrawableTiled(progressDrawable);} else {setProgressDrawable(progressDrawable);}}...}public void setProgressDrawableTiled(Drawable d) {if (d != null) {d = tileify(d, false);}setProgressDrawable(d);}private Drawable tileify(Drawable drawable, boolean clip) {...if (drawable instanceof BitmapDrawable) {...final BitmapDrawable clone = (BitmapDrawable) bitmap.getConstantState().newDrawable();、// 横向重复平铺clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);if (clip) {return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL);} else {return clone;}}...}

在ProgressBar的构造函数中,会根据prgressDrawable生成一个新的drawable,这个drawable横向是prgressDrawable的平铺效果,然后再把新的drawable设为progressDrawable。所以,我们只给控件的背景只设置了一个星星的图片,但是它会根据设置的星星个数计算控件的宽度,然后再对星星drawable进行横向平铺直到填满控件。

现在我们各个星星的颜色要求不一样,而现在控件的绘制逻辑是只传入一张星星的图片,然后将星星图片横向平铺知道撑满控件,现有的控件已经无法满足星星颜色渐变的需求了。这是,我们可以考虑自定义控件。

三、解决办法

既然现有的控件已经无法满足需求,我们就要考虑自定义控件来满足需求。分析过后尝试了以下几种方法:

1、在原有控件基础上绘制一层渐变的矩形

首先我们自定义控件MzAppCompatRatingBar,继承于AppCompatRatingBar,重新它的onDraw方法,在原有控件的基础上绘制一层渐变的矩形,代码如下:
    protected synchronized void onDraw(Canvas canvas) {super.onDraw(canvas);Drawable progressDrawable = getProgressDrawable();if (progressDrawable != null) {canvas.save();mPaint.setShader(new LinearGradient((float) progressDrawable.getBounds().left, (float) progressDrawable.getBounds().top,(float) progressDrawable.getBounds().right, (float) progressDrawable.getBounds().bottom, Color.argb(0, 255, 255, 255), Color.argb(150, 255, 255, 255), Shader.TileMode.REPEAT));canvas.drawPaint(mPaint);canvas.restore();}}

实现效果:

这种方式其实就是在原有控件的基础上盖了一层渐变的矩形,两者效果的叠加就会产生一种渐变的效果,如果背景色改变了,渐变的颜色也需要改变,与背景色一致,否则就会看到星星控件有背景色,比如:
这种方式实现的效果,整个控件是从左到右颜色逐渐渐变的,而按照视觉的要求,每个星星的颜色是固定的,并且也没有渐变的规律,所以使用这种方式是不可以的。

2、在原有控件基础上绘制自己的星星

既然原有的控件无法定制每个星星的颜色,那我们是否可以考虑自己绘制各个星星呢?我们可以在原有的控件基础上绘制五个颜色不一样的星星,然后根据滑动的进度来设置裁剪区域,只绘制进度左边的内容,代码如下:
    // 加载五张星星的图片private void createStarDrawables(int[] starColors) {mStarDrawables = new ArrayList<>();mStarDrawables.add(getResources().getDrawable(R.drawable.mz_btn_bigstar_on_pressed_color_limegreen));mStarDrawables.add(getResources().getDrawable(R.drawable.mz_btn_bigstar_on_pressed_color_grey));mStarDrawables.add(getResources().getDrawable(R.drawable.mz_btn_bigstar_on_pressed_color_firebrick));mStarDrawables.add(getResources().getDrawable(R.drawable.mz_btn_bigstar_on_pressed_color_coral));mStarDrawables.add(getResources().getDrawable(R.drawable.mz_btn_bigstar_on_pressed_color_seagreen));}@Overrideprotected synchronized void onDraw(Canvas canvas) {super.onDraw(canvas); // 先绘制原来的控件// 在原来控件的基础上根据进度绘制颜色不一样的星星Drawable progressDrawable = getProgressDrawable();if (progressDrawable != null) {canvas.save();// 获得进度位置final int pogressPos = getProgressPos();// 根据进度位置设置裁剪区域canvas.clipRect(0, 0, pogressPos, getHeight());int drawableLeft = getPaddingLeft();int drawableTop = getPaddingTop();// 绘制五张星星for (Drawable drawable : mStarDrawables) {drawable.setBounds(drawableLeft, drawableTop, drawableLeft + drawable.getIntrinsicWidth(), drawableTop + drawable.getIntrinsicHeight());drawableLeft += drawable.getIntrinsicWidth();drawable.draw(canvas);}canvas.restore();}}/*** 获取进度所对应的位置* @return*/private int getProgressPos() {int available = getWidth() - getPaddingLeft() - getPaddingRight();final int progressPos = (int) (getScale() * available + 0.5f) + getPaddingLeft();return progressPos;}/*** 获得当前滑动进度的百分比* @return*/private float getScale() {final int max = getMax(); // 最大进度return max > 0 ? getProgress() / (float) max : 0;}

显示效果如下:

ok,这个正式我们想要的效果,通过在原有控件上盖上自己绘制的星星,我们可以实现视觉的需求。

3、通过滤镜效果绘制不同颜色的星星

通过方法二,已经可以实现视觉提出的需求,但是,这种方法有一个缺点:每种颜色的星星都需要提供图片,增加了公共资源的大小。

那么,有什么改进的办法呢?在android中,可以通过setColorFilter来改变drawable的颜色,那么我们是否可以只提供一张纯色的星星图片,然后通过setColorFilter来动态改变星星的颜色呢?答案是可以的,改进后的代码如下:

    private void init() {createStarDrawables(new int[]{0xFFFF9602, 0xFFFFA300, 0xFFFEB100, 0xFFF9BE00, 0xFFF9BE00});}private void createStarDrawables(int[] starColors) {mStarColors = starColors;mstarDrawable = getResources().getDrawable(R.drawable.mz_btn_bigstar_test, null);}@Overrideprotected synchronized void onDraw(Canvas canvas) {super.onDraw(canvas);if (mStarDrawable != null && mStarColors != null) {// 在原来RatingBar的基础上在绘制一层颜色不一样的星星canvas.save();final int pogressPos = getProgressPos();canvas.clipRect(0, 0, pogressPos, getHeight());int drawableLeft = getPaddingLeft();int drawableTop = getPaddingTop();for (int i=0; i<getNumStars(); i++) {int starColor;if (i >= mStarColors.length) {starColor = mStarColors[mStarColors.length - 1];} else {starColor = mStarColors[i];}mStarDrawable.setColorFilter(starColor, PorterDuff.Mode.SRC_IN); // 对drawable进行染色处理mStarDrawable.setBounds(drawableLeft, drawableTop, drawableLeft + mStarDrawable.getIntrinsicWidth(), drawableTop + mStarDrawable.getIntrinsicHeight());drawableLeft += mStarDrawable.getIntrinsicWidth();mStarDrawable.draw(canvas);}canvas.restore();}}

其中mz_btn_bigstar_test是一张纯色的星星图片,如下:

绘制效果如下:

通过利用滤镜来改变drawable的颜色,我们完美解决了需要多张图片资源的情况。

四、代码整理

对于自定义控件,在完成了基本的功能后,我们还需要整理代码,定义属性和样式给应用使用,分析应该提供哪些接口给应用。比如在星级评分控件中,应用应该可以自定义星星的图片和颜色,所有我们需要定义属性给应用自定义 星星的图片和颜色,并在代码中提供相应的接口。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.RatingBar;
import com.liunian.common.R;public class MzRatingBar extends RatingBar {private Drawable mStarDrawable;private int[] mStarColors;public MzRatingBar(Context context) {this(context, null);}public MzRatingBar(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MzRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MzRatingBar, defStyleAttr, 0);int colorArrayId = a.getResourceId(R.styleable.MzRatingBar_mcStarColors, R.array.mc_rating_bar_default_colors);mStarColors = getResources().getIntArray(colorArrayId);mStarDrawable = a.getDrawable(R.styleable.MzRatingBar_mcStarDrawable);if (mStarDrawable == null) {mStarDrawable = getResources().getDrawable(R.drawable.mz_btn_big_star_on);}a.recycle();}@Overrideprotected synchronized void onDraw(Canvas canvas) {super.onDraw(canvas);if (mStarDrawable != null && mStarColors != null) {// 在原来RatingBar的基础上在绘制一层颜色不一样的星星canvas.save();final int pogressPos = getProgressPos();canvas.clipRect(0, 0, pogressPos, getHeight());int drawableLeft = getPaddingLeft();int drawableTop = getPaddingTop();for (int i=0; i<getNumStars(); i++) {int starColor;if (i >= mStarColors.length) {starColor = mStarColors[mStarColors.length - 1];} else {starColor = mStarColors[i];}mStarDrawable.setColorFilter(starColor, PorterDuff.Mode.SRC_IN);mStarDrawable.setBounds(drawableLeft, drawableTop, drawableLeft + mStarDrawable.getIntrinsicWidth(), drawableTop + mStarDrawable.getIntrinsicHeight());drawableLeft += mStarDrawable.getIntrinsicWidth();mStarDrawable.draw(canvas);}canvas.restore();}}/*** 设置各个星星的颜色* @param starColors*/public void setStarColors(int[] starColors) {if (starColors != null) {mStarColors = starColors;}}/*** 获取进度所对应的位置* @return*/private int getProgressPos() {int available = getWidth() - getPaddingLeft() - getPaddingRight();final int progressPos = (int) (getScale() * available + 0.5f) + getPaddingLeft();return progressPos;}private float getScale() {final int max = getMax();return max > 0 ? getProgress() / (float) max : 0;}
}

在attrs.xml声明属性的定义

    <declare-styleable name="MzRatingBar"><attr name="mcStarColors" format="reference" /><attr name="mcStarDrawable" format="reference" /></declare-styleable>

定义常用的style,这里定义了大星星和小星星两套style给应用使用

    <style name="Widget.Common.MzRatingBar.Large" parent="android:style/Widget.DeviceDefault.Light.RatingBar"><item name="android:progressDrawable">@drawable/mc_ratingbar_big_full_light</item><item name="android:indeterminateDrawable">@drawable/mc_ratingbar_big_full_light</item><item name="android:minHeight">29.3dip</item><item name="mcStarDrawable">@drawable/mz_btn_big_star_on</item><item name="mcStarColors">@array/mc_rating_bar_default_colors</item></style><style name="Widget.Common.MzRatingBar.Small" parent="android:style/Widget.DeviceDefault.Light.RatingBar"><item name="android:progressDrawable">@drawable/mc_ratingbar_small_full_light</item><item name="android:indeterminateDrawable">@drawable/mc_ratingbar_small_full_light</item><item name="android:minHeight">14.7dip</item><item name="mcStarDrawable">@drawable/mz_btn_small_star_on</item><item name="mcStarColors">@array/mc_rating_bar_default_colors</item></style>
mc_ratingbar_big_full_light.xml
<?xml version="1.0" encoding="utf-8"?><layer-list xmlns:android="http://schemas.android.com/apk/res/android"><item android:id="@android:id/background" android:drawable="@drawable/mz_btn_big_star" /><item android:id="@android:id/secondaryProgress" android:drawable="@drawable/mz_btn_big_star_secondary" /><item android:id="@android:id/progress" android:drawable="@drawable/mz_btn_big_star" />
</layer-list>

mc_ratingbar_small_full_light.xml

<?xml version="1.0" encoding="utf-8"?><layer-list xmlns:android="http://schemas.android.com/apk/res/android"><item android:id="@android:id/background" android:drawable="@drawable/mz_btn_small_star" /><item android:id="@android:id/secondaryProgress" android:drawable="@drawable/mz_btn_small_star_secondary" /><item android:id="@android:id/progress" android:drawable="@drawable/mz_btn_small_star" />
</layer-list>
在arrays.xml中声明默认的星星颜色
    <array name="mc_rating_bar_default_colors" translatable="false"><item>#ff961d</item><item>#fda21f</item><item>#fcb121</item><item>#fabd23</item><item>#f6c84b</item></array>
上面用到的资源有mz_btn_big_star_on、mz_btn_big_star_on、mz_btn_big_star_secondary、mz_btn_small_star_on、mz_btn_small_star_on、mz_btn_small_star_secondary,如下:

五、应用使用

定义好控件后,应用就可以使用了,具体用法如下:

1、在xml中使用

    <com.liunian.common.widget.MzRatingBarandroid:id="@+id/ratingbar1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"style="@style/Widget.Common.MzRatingBar.Large"android:isIndicator="false"android:numStars="5"android:rating="3" /><com.liunian.common.widget.MzRatingBarandroid:id="@+id/ratingbar2"android:layout_below="@id/ratingbar1"android:layout_marginTop="20dp"android:layout_centerHorizontal="true"android:layout_width="wrap_content"android:layout_height="wrap_content"style="@style/Widget.Common.MzRatingBar.Small"android:isIndicator="false"android:numStars="5"android:rating="3" />
效果如下:

2、自定义星星的颜色

    <com.liunian.common.widget.MzRatingBarandroid:id="@+id/ratingbar1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"style="@style/Widget.Common.MzRatingBar.Large"app:mcStarColors="@array/rating_bar_colors"android:isIndicator="false"android:numStars="5"android:rating="3" /><com.liunian.common.widget.MzRatingBarandroid:id="@+id/ratingbar2"android:layout_below="@id/ratingbar1"android:layout_marginTop="20dp"android:layout_centerHorizontal="true"android:layout_width="wrap_content"android:layout_height="wrap_content"style="@style/Widget.Common.MzRatingBar.Small"app:mcStarColors="@array/rating_bar_colors"android:isIndicator="false"android:numStars="5"android:rating="3" />
    <array name="rating_bar_colors" translatable="false"><item>#43b56b</item><item>#44aff9</item><item>#f56455</item><item>#5ecddf</item><item>#f6b944</item></array>

通过设置mcStarColors,指定一个颜色数组,颜色数组为各个星星的颜色。 也可以在代码中直接调用接口。效果如下:

六、总结

1、在接到控件方面的需求时,首先我们要考虑利用现有的控件是否能够达到要求,通过给原生控件定义样式能够满足大部分的控件需求;
2、在原生控件不能满足需求时,我们需要分析需求,选择一个最接近需求的控件继承,然后在该控件上扩展;
3、自定义控件需要大量的知识积累,才能做出完美的自定义控件。

android自定义控件(星级评分)相关推荐

  1. android中星级评分控件RatingBar的使用

    一.简单概述 1.相关属性: > android:isIndicator:是否用作指示,用户无法更改,默认false > android:numStars:显示多少个星星,必须为整数 an ...

  2. [Android] 星级评分条组件RatingBar

    星级评分条组件(RatingBar)一般是用来做评分,用星形来显示等级评定,它是ProgressBar的子类,继承了ProgressBar的所有属性和方法. 1.RatingBar属性 android ...

  3. 星星评价控件android开发_android自定义星级评分控件,可实现只显示实心星星

    话不多说,上图 近日app需求弄一个等级展示,看了下UI图,只显示实星(点亮的星星).如图 但是网上关于星级评分的例子大多这样 也展示虚心星星 通过自定义Viewpackage com.starsba ...

  4. Android 基础知识4-3.9 RatingBar(星级评分条)详解

    一.引言 Android开发中,时不时的就有要实现星星的评分效果,比如某宝,某团,相信大家也都见过,当然了我们可以自己去画,也可以用美工给切的图去实现,其实在Android原生的控件中就可以来实现这样 ...

  5. UI组件之ProgressBar及其子类(二)SeekBar拖动条和RatingBar星级评分条的使用

    拖动条采用拖动滑块的位置来表示数值 SeekBar的常用xml属性值: 重要的android:thumb制定一个Drawable对象,改变滑块外观 通过滑块来改变图片的透明度: main.xml &l ...

  6. uni-app+iconfont 实现星级评分(vue)

    效果图 ​​ 需求 点击星星进行评分,若评分低于三星,则展示问题原因,星级评分多少则高亮几颗星,所有选项均为必选项,评分完成后可提交表单,并把问题.星级分数.原因传给后台. 因需求原因先做了星星组件的 ...

  7. [微信小程序]星级评分和展示(详细注释附效果图)

    微信小程序开发交流qq群   173683895    承接微信小程序开发.扫码加微信. 正文: 星级评分分成两种情况: 一:展示后台给的评分数据   二:用户点击第几颗星星就显示为几星评分; < ...

  8. Android自定义控件NumberCircleProgressBar(圆形进度条)的实现

    Android自定义控件NumberCircleProgressBar(圆形进度条)的实现

  9. android自定义view获取控件,android 自定义控件View在Activity中使用findByViewId得到结果为null...

    转载:http://blog.csdn.net/xiabing082/article/details/48781489 1.  大家常常自定义view,,然后在xml 中添加该view 组件..如果在 ...

最新文章

  1. 人生苦短,我用PyCharm(附链接)
  2. c语言小学生算法,急求:C语言小学生算法练习软件实现的任务:面向小学生,随机选择两个整数进行加、减、乘、除,要求学生解答。设计内容:1、电...
  3. Rsync+sersync同步配置
  4. 每日程序C语言16-找出比赛对手名单
  5. 【算法】单源最短路径和任意两点最短路径总结(补增:SPFA)
  6. 《去哪网编程题》filename extension
  7. php去掉查询返回的字段序列号,php – 为返回的MySQL查询添加编号列表列
  8. python3-pandas 数据结构 Series、DataFrame 基础
  9. echarts图使用tab和下拉切换
  10. 5G 会榨干手机的电池?
  11. leapftp:425 failed to establish connection解决方法
  12. 从零开始设计一个共识算法——一场没有硝烟的战争
  13. Android 5.1 添加下拉通知栏数据流量开关快捷图标
  14. stm32f746 linux,【STM32F7】STM32F746-Discovery ucLinux预热
  15. 粤教版小学认识计算机教案,三年级信息技术上册第1课认识计算机教案粤教版.doc...
  16. 重庆python培训多少钱-重庆软件测试培训/Python
  17. 思科Cisco交换机的基本命令
  18. 刷机错误ERROR:STATUS_BROM_CMD__FAIL
  19. vue使用图像编辑器tui-image-editor
  20. 佛祖保佑,永不宕机/永无bug

热门文章

  1. 百旺红字发票信息表显示服务器返回为空,红字发票信息表状态详细说明
  2. Excel与Word链接操作:使用邮件合并工具
  3. Python将英文标点替换成空格
  4. 做独立站要做私域营销,做亚马逊更要做私域营销,Why?
  5. Kubernetes利用CNI-bridge插件打通网络
  6. found zsh 所有命令在终端失效
  7. python web面试题部分汇总
  8. 【HNOI模拟By lyp】Day1
  9. 今日芯声 | 四大运营商之一居然不支持华为?假的!
  10. (最新)天津各片区,各小区,各学校 对应关系,持续更新