效果展示
前言
前面,我讲了自定义组件的基本知识,也教了大家写了些自定义组件,相信大家对 构造函数、测量(onMeasure)及绘制(onDraw)方法了如执掌了,下面这个自定义组件,我会带大家更上一层楼,接触 人机交互事件(onTouchEvent)处理方法,面对一个新知识点,大家不要惧怕,始终要坚信,只要时间到位,没什么过不去的坎。废话不多说,上码!!!(这句话我是直接在输入法里连着打出来的,哈哈哈)
源码分析
老套路,“五步走”法
第一步:先创建自定义组件类,继承自View
第二步:创建自定义属性文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RatingBar">
<attr name="normalBit" format="reference"/> //触摸前引用的资源
<attr name="focusBit" format="reference"/> //触摸后引用的资源
<attr name="gradeNum" format="integer"/> //数量
</declare-styleable>
</resources>
第三步:在布局中引用
(大家不要固化思维,引用 也可以在代码里动态实现,例如:我这篇文章 Android开发-仿今日头条文字变色效果)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:gravity="center"
tools:context=".MainActivity">
<com.wustyq.view_day06.RatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
app:normalBit="@drawable/ic_half_star2"
app:focusBit="@drawable/ic_un_star2"
app:gradeNum="5"/>
</LinearLayout>
第四步:在自定义组件类中获取属性值 并编写相关方法
package com.wustyq.view_day06;
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.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
public class RatingBar extends View {
private Bitmap mNormalBit;
private Bitmap mFocusBit;
private int mGradeNum;
private int mCurrentGrade = 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 typedArray = context.obtainStyledAttributes(attrs, R.styleable.RatingBar);
//不能直接获取资源bitmap,所以,先获取资源 ID ,再由 BitmapFactory 转换
int normalBitId = typedArray.getResourceId(R.styleable.RatingBar_normalBit, 0);
if (normalBitId == 0){
throw new RuntimeException("请设置属性 normalBit");
}
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4;
mNormalBit = BitmapFactory.decodeResource(getResources(),normalBitId,opts);
//不能直接获取资源bitmap,所以,先获取资源 ID ,再由 BitmapFactory 转换
int focusBitId = typedArray.getResourceId(R.styleable.RatingBar_focusBit, 0);
if (focusBitId == 0){
throw new RuntimeException("请设置属性 focusBit");
}
mFocusBit = BitmapFactory.decodeResource(getResources(),focusBitId,opts);
mGradeNum = typedArray.getInt(R.styleable.RatingBar_gradeNum, mGradeNum);
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//高度 一张图片的高度 实现 padding 间隔自己根据 示意图 和下面 公式对着算
int height = Math.max(mNormalBit.getHeight(),mFocusBit.getHeight()) + getPaddingTop() + getPaddingBottom();
int width = Math.max(mNormalBit.getWidth(),mFocusBit.getWidth())*mGradeNum + mGradeNum * (getPaddingLeft() + getPaddingRight());
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画触摸前的图片
for (int i = 0; i < mGradeNum; i++) {
float x = i * mNormalBit.getWidth() + (i+1)*getPaddingLeft();
canvas.drawBitmap(mNormalBit,x,getPaddingTop(),null);
}
//画触摸后的图片
for (int i = 0; i < mCurrentGrade; i++) {
float x = i * mFocusBit.getWidth() + (i+1)*getPaddingLeft();
canvas.drawBitmap(mFocusBit,x,getPaddingTop(),null);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
System.out.println("moveX -> " + event.getAction());
//按下 移动 抬起 处理逻辑都一样,判断手指的位置,根据当前位置计算分数,再去刷新
switch(event.getAction()){
case MotionEvent.ACTION_DOWN: //按下
case MotionEvent.ACTION_MOVE: //移动
// case MotionEvent.ACTION_UP: //抬起 优化,尽量减少onDraw()
{
float moveX = event.getX(); //event.getRawX()获取屏幕的x位置 event.getX()相当于控件的x位置
System.out.println("moveX -> " + moveX);
//计算分数
float currentGrade = moveX/(mNormalBit.getWidth()+ getPaddingLeft());
if (currentGrade <= 0.0f){
currentGrade = 0;
}else if (currentGrade > mGradeNum){
currentGrade = mGradeNum;
}else {
currentGrade += 1;
}
if (currentGrade == mCurrentGrade)return true;
mCurrentGrade = (int) currentGrade;
invalidate(); //系统帮我们调用 onDraw() 减少onDraw()的调用 , 目前是不断调用
}
break;
}
return true;//onTouch 后面会逐步讲源码 false 代表不消费 第一次 Down false DOWN以后的事件进不来
}
}
这一段代码是有点干货的,例如:
第一点:Bitmap图片的处理,inSampleSize 这个参数代表采样率 通过配置这个值可以压缩图片 如果 inSampleSize = 2 那么加载到内存中的图片 宽度是原图的1/2 高度也是原图的1/2 总的大小是原图的1/4;需要注意 如果inSampleSize < 1 会按照1来处理 解码器默认会按照2的幂指数来处理图片的压缩 所以可以传入 2的幂指数来作为 inSampleSize 的值。
第二点:如何获取并绘制 属性值为资源文件的 属性,这句话有点绕,相信各位的理解能力哈。步骤是这样的 typedArray.getResourceId() 获取资源文件id ----> BitmapFactory.decodeResource(getResources(),normalBitId,opts) 通过BitmapFactory解析资源文件Id获取图片Bitmap ---> canvas.drawBitmap(mNormalBit,x,getPaddingTop(),null) 绘制图片
第三点:如何监控 padding ,我就用一张图片来解释吧,老规矩,公式在代码里已经呈现了,图中只呈现了两个获取宽度的方法,获取高度的类似。
第五步:外面类调用
虽然,外面类里并没有写啥,但是,步骤不能少,哈哈哈。
package com.wustyq.view_day06;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
代码优化
在这里,我有一点要强调的,就是当我们把代码 码完了之后,就不要认为工作做完了,我们还得回过头来看看,有没有哪些代码可以优化的,
里面有些代码已经优化了,比如 onTouchEvent() 方法里,尽量减少onDraw()方法的调用,间接的就是说减少 invalidate() 的调用,大家有时间可以执行查阅 invalidate() 源码
接下来我带着大家优化一段
没有优化前的代码
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画触摸前的图片
for (int i = 0; i < mGradeNum; i++) {
float x = i * mNormalBit.getWidth() + (i+1)*getPaddingLeft();
canvas.drawBitmap(mNormalBit,x,getPaddingTop(),null);
}
//画触摸后的图片
for (int i = 0; i < mCurrentGrade; i++) {
float x = i * mFocusBit.getWidth() + (i+1)*getPaddingLeft();
canvas.drawBitmap(mFocusBit,x,getPaddingTop(),null);
}
}
优化之后的代码
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mGradeNum; i++) {
float x1 = i * mNormalBit.getWidth() + (i+1)*getPaddingLeft();
float x2 = i * mFocusBit.getWidth() + (i+1)*getPaddingLeft();
if(i < mCurrentGrade){
//画触摸后的图片
canvas.drawBitmap(mFocusBit,x2,getPaddingTop(),null);
}else {
//画触摸前的图片
canvas.drawBitmap(mNormalBit,x1,getPaddingTop(),null);
}
}
}
很明显可以看出,减少了绘制的次数,提高了性能。
相信肯定有厉害的朋友一步到位了,请不要嘲笑小弟哦!!!