仿淘宝、腾讯课堂评分组件 --- Android高级自定义组件
效果展示
前言
前面,我讲了自定义组件的基本知识,也教了大家写了些自定义组件,相信大家对 构造函数、测量(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); } } }
很明显可以看出,减少了绘制的次数,提高了性能。
相信肯定有厉害的朋友一步到位了,请不要嘲笑小弟哦!!!