仿淘宝、腾讯课堂评分组件 --- Android高级自定义组件

效果展示

仿淘宝、腾讯课堂评分组件 --- Android高级自定义组件

前言

前面,我讲了自定义组件的基本知识,也教了大家写了些自定义组件,相信大家对 构造函数、测量(onMeasure)及绘制(onDraw)方法了如执掌了,下面这个自定义组件,我会带大家更上一层楼,接触 人机交互事件(onTouchEvent)处理方法,面对一个新知识点,大家不要惧怕,始终要坚信,只要时间到位,没什么过不去的坎。废话不多说,上码!!!(这句话我是直接在输入法里连着打出来的,哈哈哈)

源码分析

老套路,“五步走”法

第一步:先创建自定义组件类,继承自View

 仿淘宝、腾讯课堂评分组件 --- Android高级自定义组件

第二步:创建自定义属性文件 

<?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  ,我就用一张图片来解释吧,老规矩,公式在代码里已经呈现了,图中只呈现了两个获取宽度的方法,获取高度的类似。

仿淘宝、腾讯课堂评分组件 --- Android高级自定义组件

第五步:外面类调用 

虽然,外面类里并没有写啥,但是,步骤不能少,哈哈哈。

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);
            }
        }
    }

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

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

上一篇:逻辑回归模型(Logistic Regression, LR)基础


下一篇:android,如何在edittext中绘制虚线