先看看效果图
1.自定义 View 的基本流程
- 创建 View Class
- 创建 attr 属性文件,确定属性
- View Class 绑定 attr 属性
- onMeasure 测量
- onDraw 绘制
- onTouchEvent ( 用户交互需要处理 )
1.1 创建 View Class
package com.example.view_day05_ratingbar;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
public class RatingBar extends View {
private static final String TAG = "wqq";
private Bitmap mStartNormalBitmap,mStartFocusBitmap;
private int mGradeNumber = 5;
private int mCurrentGrade = 0;
public RatingBar(Context context) {
this(context, null);
}
public RatingBar(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RatingBar(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.RatingBar);
int starNormalID = array.getResourceId(R.styleable.RatingBar_starNormal,0);
if (starNormalID == 0) {
throw new RuntimeException("请设置属性 startNormal");
}
mStartNormalBitmap = BitmapFactory.decodeResource(getResources(),starNormalID);
int startFocusId = array.getResourceId(R.styleable.RatingBar_starFocus, 0);
if (startFocusId == 0) {
throw new RuntimeException("请设置属性 startFocusId");
}
mStartFocusBitmap = BitmapFactory.decodeResource(getResources(), startFocusId);
mGradeNumber = array.getInt(R.styleable.RatingBar_gradeNumber,0);
array.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//高度 一张图片的高度,自己去实现 padding
int height = mStartNormalBitmap.getHeight();
int width = mGradeNumber * mStartFocusBitmap.getWidth();
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < mGradeNumber; i++) {
int x = i * mStartFocusBitmap.getWidth();
if (mCurrentGrade > i) {
canvas.drawBitmap(mStartFocusBitmap,x,0,null);
} else {
canvas.drawBitmap(mStartNormalBitmap,x,0,null);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//移动 按下 抬起 处理逻辑都是一样,判断手指的位置,根据当前位置计算出分数,再去刷新界面显示
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN: //按下
case MotionEvent.ACTION_MOVE: // 移动
//case MotionEvent.ACTION_UP: // 抬起,up事件可以去掉,优化ondraw调用次数
float moveX = event.getX(); // event.getX(),获取相对于当前控件的位置;event.getRawX(),获取屏幕的x位置
Log.e(TAG, "movex: "+ moveX + "event:"+ event.getAction());
int currentGrade = (int)(moveX/mStartFocusBitmap.getWidth() + 1);
if (currentGrade < 0) {
currentGrade = 0;
}
if (currentGrade > mGradeNumber) {
currentGrade = mGradeNumber;
}
//相等的话,就不用重复绘制了
if (currentGrade == mCurrentGrade) {
return true;
}
mCurrentGrade = currentGrade;
//刷新显示
invalidate();// 由于ondraw流程很长,尽量减少ondraw的调用,减少内存
break;
}
return true;
}
}
下篇文字会分析 onTouchEvent 源码,为什么要设置返回 true
1.2 创建 attr
<?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" />
<!--当前的分数-->
<attr name="currentGrade" format="integer" />
<!--星星之间的间距-->
<attr name="starPadding" format="dimension" />
</declare-styleable>
</resources>
1.3 创建图片资源
在\app\src\main\res目录下创建文件夹drawable-xhdpi,将星星图片复制进去。
1.4 布局文件
activity_main.xml
<?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=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.view_day05_ratingbar.RatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/purple_200"
app:starPadding="50dp"
app:starFocus="@drawable/select"
app:starNormal="@drawable/start_normal"
app:gradeNumber="5"/>
</RelativeLayout>