1. 说明
基于我们前边写的自定义View的入门、自定义TextView、仿QQ运动步数、玩转字体变色、类似QQ运动步数进度条、3个形状来回切换等自定义View外,这节课我们来写一个评分控件,类似于淘宝、腾讯课堂的评分控件 —— RatingBar。
我们前边写的上边的6个效果,都属于静态自定义View控件,不涉及用户去触摸该控件;
但是,从这节课开始,我们所写的效果就属于交互控件,何为交互控件? 就是用户可以在这个控件上边去触摸,即就是按下、移动、抬起。
2. 效果图如下:
刚开始进入app是一个图片都没有选中,如图所示:

图片.png
手指触摸后,就选中触摸的图片,如图所示:

图片.png
3. 思路分析
3.1:首先刚进来是初始化的样子:
1>:就是只有5张未被选中的图片,这里需要自定义属性、2张图片资源、评分的等级数量;
2>:指定控件的宽高;
3>:绘制图片Bitmap;
3.2:需要在onTouch()方法中处理用户和该控件的交互事件;
3.3:优化部分:
1>:如果分数相同,就不需要再去绘制了;
2>:就是尽量减少onDraw()方法的调用;
3>:onTouchEvent()方法必须return true,如果返回false,就表示事件不消费,第一次可以进入MotionEvent.ACTION_MOVE事件,以后就不能进来了,所以这里必须return true,并且以后只要是自定义View,并且凡是涉及到用户触摸事件,onTouchEvent()方法都return true;
4. 代码如下
自定义RatingBar.java代码如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/19 15:01
* Version 1.0
* Params:
* Description: 评分控件RatingBar
*/
public class RatingBar extends View {
// 用BitmapFactory 解析出来的没有选中的图片
private Bitmap mStarNormalBitmap;
// 用BitmapFactory 解析出来的已经选中的图片
private Bitmap mStarFocusBitmap;
// 总共的分数
private int mGradeNumber = 5 ;
// 当前的分数
private int mCurrentNumber = 0 ;
public RatingBar(Context context) {
this(context,null);
}
public RatingBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public RatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 以下是获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatingBar);
// 获取资源id
int starNormalId = array.getResourceId(R.styleable.RatingBar_starNormal, 0);
if (starNormalId == 0){
throw new RuntimeException("请设置属性 starNormal") ;
}
// 用BitmapFactory 解析资源
mStarNormalBitmap = BitmapFactory.decodeResource(getResources(), starNormalId);
int starFocusId = array.getResourceId(R.styleable.RatingBar_starFocus, 0);
if (starFocusId == 0){
throw new RuntimeException("请设置属性 starFocus") ;
}
// 用BitmapFactory 解析资源
mStarFocusBitmap = BitmapFactory.decodeResource(getResources(), starFocusId);
// 获取分数
mGradeNumber = array.getInt(R.styleable.RatingBar_gradeNumber , mGradeNumber) ;
array.recycle();
}
/**
* 测量控件的宽高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 控件的宽度
int width = mStarFocusBitmap.getWidth() * mGradeNumber;
int height = mStarFocusBitmap.getHeight();
setMeasuredDimension(width , height);
}
/**
* 画选中的图片和未选中的图片
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
for (int i = 0; i < mGradeNumber; i++) {
// i * 星星的宽度
int x = i * mStarFocusBitmap.getWidth() ;
// 触摸的时候 mCurrentGrade的值是不断变化的
if (mCurrentNumber > i){
canvas.drawBitmap(mStarFocusBitmap , x , 0 , null);
}else{
canvas.drawBitmap(mStarNormalBitmap , x , 0 , null);
}
}
}
/**
* 处理用户触摸
* 移动、按下、抬起,处理逻辑都是一样的,判断手指的位置,根据当前位置计算出分数,然后去刷新并显示
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
// case MotionEvent.ACTION_DOWN: // 尽量减少onDraw()方法的调用
case MotionEvent.ACTION_MOVE:
// case MotionEvent.ACTION_UP: // 尽量减少onDraw()
// 手指的位置,计算当前的分数
float moveX = event.getX();
// 计算当前分数
int currentGrade = (int) (moveX/mStarFocusBitmap.getWidth()+1);
//范围问题
if (currentGrade < 0){
currentGrade = 0 ;
}
if (currentGrade > mGradeNumber){
currentGrade = mGradeNumber ;
}
// 分数相同的情况下,就不要再去绘制了,尽量减少onDraw()方法的调用
if (currentGrade == mGradeNumber){
return true ;
}
// 判断完异常情况之后,最后记录当前分数,然后重新绘制
mCurrentNumber = currentGrade ;
invalidate(); // 因为 invalidate()方法会调用
break;
}
return true;
}
}
attrs.xml资源文件如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RatingBar">
<attr name="starNormal" format="reference"/>
<attr name="starFocus" format="reference"/>
<attr name="gradeNumber" format="integer"/>
</declare-styleable>
</resources>
直接在activity_main.xml布局文件中引用该RatingBar控件即可:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jackchen.view_day06_2.MainActivity">
<com.jackchen.view_day06_2.RatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:starNormal="@drawable/star_normal"
app:starFocus="@drawable/star_selected"
app:gradeNumber="5"
/>
</RelativeLayout>
具体代码已上传至github:
https://github.com/shuai999/View_day06_2.git