仿淘宝、腾讯课堂评分组件 --- 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);            }        }    }

很明显可以看出,减少了绘制的次数,提高了性能。

相信肯定有厉害的朋友一步到位了,请不要嘲笑小弟哦!!!

来源:https://www.icode9.com/content-4-874051.html

(0)

相关推荐