IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 自定义View 未完待续 -> 正文阅读

[移动开发]自定义View 未完待续

自定义View

紫愿_人间尽好,一个致力于大二暑假进厂实习的少年,初次写作于2022/2/13

什么是自定义View

自定义View和自定义ViewGroup的区别?

  • 自定义View一般需要实现自己的onMeasure()onDraw()方法。

  • 自定义ViewGroup需要实现onMeasure()onLayout()方法。

自定义View的步骤

三大步骤:

  1. 布局 onLayout(){}
  2. 绘制 onDraw(){}
  3. 触摸事件 onTouchEvent(){}

布局

布局,即将确定子View的大小和位置,如果未设置则子View会处于父控件左上角。然后根据子控件的大小,确定自身需要的大小。

首先需要需要子控件进行自我测量,即调用子控件的onMeasure()方法,让子控件进行自我测量。布局之前必须先进行测量操作,否则get子控件的宽高获取值的时候会出现多次获取的时候值不一致的情况。那么调用子控件的onMeasure()方法的时候,包括重写自己的onMeasure()方法的时候,会遇到两个参数widthMeasureSpec: IntheightMeasureSpec: Int,为什么会有这两个参数?这两个参数的值怎么设置?这两个参数的值怎么用?我相信这些问题是初学者的难点和必须要解决的方面。

widthMeasureSpec和heightMeasureSpec是什么

让我们先看看这两个单词是什么意思:

width measure spec:宽度测量规范

height measure spec:高度测量规范

通过名字我们很好理解,就是父View告诉子View的建议的宽度/高度值。

那么父View是如何只用一个参数就可以将建议的宽度/高度值给子View描述清楚的呢?这一点就很妙了,真的惊叹于Google工程师的巧妙了。别小看了这个变量,他同时储存了specModespecSize两个信息。怎么做到的呢?我们知道整型在计算机中是以4个字节的二进制表示的,也就是由32个0或者1。一般情况下,Android系统的设备的宽度或者高度的像素值都没有超过 230,Google工程师们就把高位的第31和30位专门存储其他的意图信息。Spec解释

别看只有两位,这里面储存的信息就有三种,刚好就是定死的dp值、wrap_content和match_parent,配合父view对自身的限制以及自身对子View的限制,组合起来就有9种情况,这9种情况就涵盖了目前的所有情况——父View是match_parent,子View是wrap_content;父view是wrap_content,子view是match_parent等等的情况。那么剩下的30位则储存具体的数值大小的信息。这种根本用不到的高位存储信息的方式是一种新思路。

让我们看看Google官方是怎么算出Spec的结果的

public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

sUseBrokenMakeMeasureSpec为true的时候,是API17以及以前的版本的计算方法,这里直接将size和mode加起来,传入的参数顺序不一致仍能正常执行,但是两个值中的任何一个溢出都会影响MeasureSpec的结果,这引起了ConstraintsLayout的bug,Google官方将其更换为更为严格的位运算计算方法来解决这个问题。那么现在sUseBrokenMakeMeasureSpec的值如下所示:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;	// 第31、30位为1,其余位为0
// Use the old (broken) way of building MeasureSpecs.
private static boolean sUseBrokenMakeMeasureSpec = false;

默认为false,那么则会以(size & ~MODE_MASK) | (mode & MODE_MASK)来进行计算。

widthMeasureSpec和heightMeasureSpec参数的值该设置成什么样的值

系统已经给了一个很好的用于转化为WidthMeasureSprc或heightMeasureSpec的方法

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let them have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

也就是说,这里只需要传递三个参数int spec, int padding, int childDimension,参数如何传递?

  • spec可以直接传入自身的widthMeasureSpecheightMeasureSpec,用于获取子类的widthMeasureSpecheightMeasureSpec
  • padding则需要将自身对应的padding传入
  • childDimension传递子View的layoutParams中的宽度或者高度

这里展示我本人的一般写法:

val childLayoutParams = childView.layoutParams

val chileWidthMeasureSpec = getChildMeasureSpec(
    widthMeasureSpec, paddingLeft + paddingRight, childLayoutParams.width
)
val chileHeightMeasureSpec = getChildMeasureSpec(
    heightMeasureSpec, paddingTop + paddingBottom, childLayoutParams.height
)
    // 最后根据业务需求,计算出自己的宽高设置

一些小问题

onMeasure()方法可能会被父View调用多次,因此需要将使用到的对象属性进行清零初始化。这里我们可以看个例子:

// sdk30 -FrameLayout.java源码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    mMatchParentChildren.clear();
    /**
     * 省略部分代码
     */
    for (int i = 0; i < count; i++) {       
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
			measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

这里我们可以看到调用了两次子类的onMeasure方法,而FrameLayout自身在onMeasure的时候也是清空了自己的ArrayList。

绘制

学习完之后再更新

触摸事件

学习完之后再更新

紫愿_人间尽好,一个致力于大二暑假进厂实习的少年,初次写作于2022/2/13

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-14 21:17:24  更:2022-02-14 21:19:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 14:28:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码