自定义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();
        }
    }
相关推荐
__water1 分钟前
RHA《Unity兼容AndroidStudio打Apk包》
android·unity·jdk·游戏引擎·sdk·打包·androidstudio
zhuiQiuMX1 小时前
字节面试手撕中等题但还没做出来
面试
一起搞IT吧2 小时前
相机Camera日志实例分析之五:相机Camx【萌拍闪光灯后置拍照】单帧流程日志详解
android·图像处理·数码相机
浩浩乎@3 小时前
【openGLES】安卓端EGL的使用
android
趣多多代言人3 小时前
从零开始手写嵌入式实时操作系统
开发语言·arm开发·单片机·嵌入式硬件·面试·职场和发展·嵌入式
Kotlin上海用户组4 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19965 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸5 小时前
Flutter 生命周期完全指南
android·flutter·ios
阿幸软件杂货间5 小时前
阿幸课堂随机点名
android·开发语言·javascript
没有了遇见5 小时前
Android 渐变色整理之功能实现<二>文字,背景,边框,进度条等
android