自定义view流程

自定义view流程

  1. 继承View,重写构造函数,两参数的 由系统自动调用

  2. 重写onMeasure(先执行)、onLayout(其次)、onDraw(最后执行)

  3. 声明自定义属性,在values目录下,新建attrs.xml文件

    xml 复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="myViewAttrs"> <!--自定义属性-->
            <attr name="originColor" format="color"/>
            <attr name="changeColor" format="color"/>
        </declare-styleable>
    </resources>
  4. 在布局文件里就可以使用自定义属性了

    xml 复制代码
    <!--必须要声明xmlns:app 才能使用自定义属性-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    	xmlns:app="http://schemas.android.com/apk/res-auto" 
        android:id="@+id/ll_nav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
    
        <com.wow.modularization.MyTextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="变化前"
            android:gravity="center"
            android:textSize="20sp"
            app:changeColor="@android:color/holo_blue_light"
            app:originColor="@color/purple_200" />
    </LinearLayout>
  5. 在两参的构造函数里获取自定义属性的值

    java 复制代码
    int originColor, changeColor;
    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        // 获取自定义类型属性 数组
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myViewAttrs);
        originColor = typedArray.getColor(R.styleable.myViewAttrs_originColor, getTextColors().getDefaultColor());
        changeColor = typedArray.getColor(R.styleable.myViewAttrs_changeColor, getTextColors().getDefaultColor());
        typedArray.recycle(); // 回收资源
    }
  6. 如果继承系统view(例如 TextView),系统view已经测量好了宽高,而你也不需要额外改变其尺寸,则可以不重写onMeasure()函数,只重写onDraw()函数最后就是自由发挥了

跳转到 自定义view全解 参考文章

view绘制流程简版

构造函数(Context context) // 代码中new调用

构造函数(Context context, AttributeSet attrs) // 系统反射调用,xml解析是由 LayoutInflater.inflater处理的

构造函数(Context context, AttributeSet attrs, int defStyleAttr) // 主题 style

序列化(xml、json...) 就是自定义的格式数据串:用的多的是 IOT(物联网)

物联网:通常由蓝牙 传递数据 串口 协议。

View的绘制基本由measure()、layout()、draw()这个三个函数完成

函数 作用 相关方法
measure() 测量View的宽高 measure(),setMeasuredDimension(),onMeasure()
layout() 计算当前View以及子View的位置 layout(),onLayout(),setFrame()
draw() 视图的绘制工作 draw(),onDraw()

想象下:你有个大的毛坯房(ViewGroup),你会怎么做?

1、测量其尺寸大小(measure)

2、分出对应的房间布局(layout)

3、贴上好看的瓷砖,把房子画的漂漂亮亮(draw)

结论:

自定义view 主要实现 onMeasure(测量自身大小) + onDraw

自定义ViewGroup 主要实现 onMeasure + onLayout

measure()

重点:MeasureSpec

MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高。

MeasureSpec的值保存在一个int值当中。一个int值有32位,前两位表示模式mode后30位表示大小size。即MeasureSpec =mode + size

MeasureSpec当中一共存在三种mode:UNSPECIFIEDEXACTLYAT_MOST

对于View来说 ,MeasureSpec的mode和Size有如下意义

模式 意义 对应
EXACTLY 精准模式,View需要一个精确值,这个值即为MeasureSpec当中的Size match_parent
AT_MOST 最大模式,View的尺寸有一个最大值,View不可以超过MeasureSpec当中的Size值 wrap_content
UNSPECIFIED 无限制,View对尺寸没有任何限制,View设置为多大就应当为多大 一般系统内部使用
java 复制代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	// 获取测量模式(Mode)
    int specMode = MeasureSpec.getMode(measureSpec)

    // 获取测量大小(Size)(父view给的大小)
    int specSize = MeasureSpec.getSize(measureSpec)

    // 通过Mode 和 Size 生成新的SpecMode
    int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
}

子View的测量模式是由 自身LayoutParam和 父View的MeasureSpec来决定的。

setMeasuredDimension(int measuredWidth, int measuredHeight) :该方法用来设置View的宽高,在我们自定义View时也会经常用到。

结合下图理解 MeasureSpec

getMeasureWidth 与 getWidth的区别

getMeasureWidth : 在measure() 过程结束 就可以获取到对应的值。 通过 setMeasuredDimension() 方法设置的

getWidth :在layout() 过程结束 才能获取到;通过视图右边的坐标减去左边的坐标计算出来的。

layout()

子view布局

java 复制代码
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
	// 对 子view 分配空间,相当与一个大平房,隔出了一个房间一样
    int top = itemPaddingTop, i = 0, len = 0;
    for (Integer count : lineCounts) {
        int left = itemPaddingLeft, tempTop = 0;
        len += count;
        for (; i < len; i++) {
            View view = lineViewsList.get(i);
            view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());
            left += itemPaddingLeft + view.getMeasuredWidth();
            tempTop = Math.max(tempTop, view.getMeasuredHeight());
        }
        top += itemPaddingTop + tempTop;
    }
}

draw()

点击查看 save / restore 图文详解

注意了save() 保存 的只是CanvasRenderingContext2D对象的状态 以及对象的所有属性 ,并不包括 这个对象上绘制的图形 。所以 restore() 只是 canvas的状态还原 了,而不会把 已经绘制的图形还原掉。

  • save:用来保存最近一次的Canvas的状态和属性。
  • restore:用来获取save保存的Canvas之前的状态和属性。防止save后对Canvas执行的平移、放缩、旋转、错切、裁剪等可以改变画布的操作对后续的绘制的影响。

save 进栈; restore 出栈

绘图工具类

java 复制代码
    private Scroller scroller; // 滑动工具
    private VelocityTracker tracker; // 速率追踪器工具
    private void init(Context ct){
        context = ct;
        scroller = new Scroller(ct);
        tracker = VelocityTracker.obtain();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        tracker.addMovement(event); // 追踪

        ...
        switch (event.getAction()){
            case MotionEvent.ACTION_MOVE:
                ....
                //scrollBy方法将对我们当前View的位置进行偏移
                scrollBy(-deltaX, 0); // View方法 scrollTo(mScrollX + x, mScrollY + y);
                break;
            case MotionEvent.ACTION_UP:
                ...
                tracker.computeCurrentVelocity(1000);
                float xVelocity = tracker.getXVelocity();
                ...
                tracker.clear();//清除 追踪

                // scroller 工具 移动 回调computeScroll()
                scroller.startScroll(
                        getScrollX(),  // startX
                        getScrollY(),  // startY
                        destX - getScrollX(), // dx
                        destY - getScrollY(), // dy
                        1000); // duration
                invalidate();

                break;
        }

        return true;
    }

	@Override
    public void computeScroll() { 
        super.computeScroll();
        if (scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }
相关推荐
Jewel10538 分钟前
Flutter代码混淆
android·flutter·ios
Yawesh_best2 小时前
MySQL(5)【数据类型 —— 字符串类型】
android·mysql·adb
Xiao Fei Xiangζั͡ޓއއ2 小时前
一觉睡醒,全世界计算机水平下降100倍,而我却精通C语言——scanf函数
c语言·开发语言·笔记·程序人生·面试·蓝桥杯·学习方法
NMBG222 小时前
[JAVAEE] 面试题(四) - 多线程下使用ArrayList涉及到的线程安全问题及解决
java·开发语言·面试·java-ee·intellij-idea
曾经的三心草4 小时前
Mysql之约束与事件
android·数据库·mysql·事件·约束
刘艳兵的学习博客8 小时前
刘艳兵-DBA033-如下那种应用场景符合Oracle ROWID存储规则?
服务器·数据库·oracle·面试·刘艳兵
guoruijun_2012_48 小时前
fastadmin多个表crud连表操作步骤
android·java·开发语言
Winston Wood8 小时前
一文了解Android中的AudioFlinger
android·音频
B.-10 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克10 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart