每日一问:LayoutParams 你知道多少?

前面的文章中着重讲解了 View 的测量流程。其中我提到了一句非常重要的话:View 的测量匡高是由父控件的 MeasureSpec 和 View 自身的 `LayoutParams 共同决定的。我们在前面的 每日一问:谈谈对 MeasureSpec 的理解 把 MeasureSpec 的重点进行了讲解,其实另外一个 LayoutParams 同样是非常非常重要。

从概念讲起

LayoutParams,顾名思义,就是布局参数。而且大多数人对此都是司空见惯,我们 XML 文件里面的每一个 View 都会接触到 layout_xxx 这样的属性,这实际上就是对布局参数的描述。大概大家也就清楚了,layout_ 这样开头的东西都不属于 View,而是控制具体显示在哪里。

LayoutParams 都有哪些初始化方法

通常来说,我们都会把我们的控件放在 XML 文件中,即使我们有时候需要对屏幕做比较「取巧」的适配,会直接通过 View.getLayoutParams() 这样的方法获取 LayoutParams 的实例,但我们接触的少并不代表它的初始化方法不重要。

实际上,用代码写出来的 View 加载效率要比在 XML 中加载快上大约 1 倍。只是在如今手机配置都比较高的情况下,我们常常忽略了这种方式。

我们来看看 ViewGroup.LayoutParams 到底有哪些构造方法。

public LayoutParams(Context c, AttributeSet attrs) {
    TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
    setBaseAttributes(a,
            R.styleable.ViewGroup_Layout_layout_width,
            R.styleable.ViewGroup_Layout_layout_height);
    a.recycle();
}

public LayoutParams(int width, int height) {
    this.width = width;
    this.height = height;
}

public LayoutParams(LayoutParams source) {
    this.width = source.width;
    this.height = source.height;
}

LayoutParams() {  }

MarginLayoutParams

除去最后一个放给 MarginLayoutParams 做处理的方法外,我们在 ViewGroup 中还有 3 个构造方法。他们分别负责给 XML 处理、直接让用户指定宽高、还有类似集合的 addAll() 这样的方式的赋值方法。

实际上,ViewGroup 的子类的 LayoutParams 类拥有更多的构造方法,感兴趣的自己翻阅源码查看。在这里我想更加强调一下我上面提到的 MarginLayoutParams

MarginLayoutParams 继承于 ViewGroup.LayoutParams

public static class MarginLayoutParams extends ViewGroup.LayoutParams {
    @ViewDebug.ExportedProperty(category = "layout")
    public int leftMargin;

    @ViewDebug.ExportedProperty(category = "layout")
    public int topMargin;

    @ViewDebug.ExportedProperty(category = "layout")
    public int rightMargin;

    @ViewDebug.ExportedProperty(category = "layout")
    public int bottomMargin;

    @ViewDebug.ExportedProperty(category = "layout")
    private int startMargin = DEFAULT_MARGIN_RELATIVE;

    @ViewDebug.ExportedProperty(category = "layout")
    private int endMargin = DEFAULT_MARGIN_RELATIVE;

    public MarginLayoutParams(Context c, AttributeSet attrs) {
        super();
        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
        setBaseAttributes(a,
                R.styleable.ViewGroup_MarginLayout_layout_width,
                R.styleable.ViewGroup_MarginLayout_layout_height);

        int margin = a.getDimensionPixelSize(
                com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
        if (margin >= 0) {
            leftMargin = margin;
            topMargin = margin;
            rightMargin= margin;
            bottomMargin = margin;
        } else {
            int horizontalMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
            // ... something
        }
        // ... something
    }
}

一看代码,自然就清楚了,为什么我们以前会发现在 XML 布局里, layout_margin 属性的值会覆盖 layout_marginLeftlayout_marginRight 等属性的值。

实际上,事实上,绝大部分容器控件都是直接继承 ViewGroup.MarginLayoutParams 而非 ViewGroup.LayoutParams。所以我们再自定义 LayoutParams 的时候记得继承 ViewGroup.MarginLayoutParams

在代码里面使用 LayoutParams

前面介绍了 LayoutParams 的几种构造方法,我们下面以 LinearLayout.LayoutParams 来看看几种简单的使用方式。

val textView1 = TextView(this)
textView1.text = "不指定 LayoutParams"
layout.addView(textView1)

val textView2 = TextView(this)
textView2.text = "手动指定 LayoutParams"
textView2.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
layout.addView(textView2)

val textView3 = TextView(this)
textView3.text = "手动传递 LayoutParams"
textView3.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams(100, 100))
layout.addView(textView3)

我们看看 addView() 都做了什么。

public void addView(View child) {
    addView(child, -1);
}

public void addView(View child, int index) {
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
    }
    addView(child, index, params);
}

@Override
protected LayoutParams generateDefaultLayoutParams() {
    if (mOrientation == HORIZONTAL) {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    } else if (mOrientation == VERTICAL) {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }
    return null;
}

public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        System.out.println(this + " addView");
    }
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    requestLayout();
    invalidate(true);
    addViewInner(child, index, params, false);
}

private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {

    // ...

    if (!checkLayoutParams(params)) {
        params = generateLayoutParams(params);
    }

    // ...
}

@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof LinearLayout.LayoutParams;
}

看起来 ViewGroup 真是煞费苦心,如果我们没有给 View 设置 LayoutParams,则系统会帮我们根据 orientation 设置默认的 LayoutParams。甚至是我们即使在 addView() 之前设置了错误的 LayoutParams 值,系统也会我们帮我们进行纠正。

虽然系统已经做的足够完善,帮我们各种矫正错误,但在 addView() 之后,我们还强行设置错误的 LayoutParams,那还是一定会报 ClassCastException 的。

LayoutParams 很重要,每一名 Android 开发都应该尽力地去掌握,只有弄清楚了系统的编写方式,应对上面类似简书的流式布局才能更好处理。

实际上 Google 出的 FlexboxLayout 已经做的相当完美。
当然如果使用的 RecyclerView,还可以自己写一个 FlowLayoutManager 进行处理。

原文较多地参考自:https://blog.csdn.net/yisizhu/article/details/51582622

原文地址:https://www.cnblogs.com/liushilin/p/11012636.html

时间: 2024-10-29 20:54:03

每日一问:LayoutParams 你知道多少?的相关文章

养成每日三问的好习惯

养成每日三问的好习惯 有什么用? 帮助回顾每日工作,不断完善自己: 每天与自己设定的目标对比,敦促自己持续进步: 帮助建立信心 积极与自己的潜意识沟通,发现自己的潜能: 帮助找到目标 知道哪里做的不够好,才会产生改进的动力 怎么做? 每天至少和自己沟通三个问题,问题设定后,在一个阶段周期内(一个月or3个月)就不要在改变: 每日3问的时间最好控制在5分钟,时间太长不容易持续:时间可以选择在早上或是晚上: 对于每日3问的答案,最好有文字记录,这样,可以定期回顾这些答案:发现自己的需求或问题,从而为

每日一问:简述 View 的绘制流程

Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要.网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追求短平快,所以本文笔者尽量精简. 想必大多数 Android 开发都知道自定义 View 需要关注的几个方法:onMeasure().onLayout() 和 onDraw(),这其实也是每个 View 至关重要的绘制流程. 基本绘制都是会从根视图 ViewRoot 的 performTravers

每日一问:

1 python中id.is.=.== 分别是比较什么的?id 查看内存地址,is是比较内存地址是不是相同,=是赋值,==比较的是变量的值 2 说说python的小数据池(了解即可,面试偶尔会问到).python中数字.在一定规则范围内,如果同时定义多个相同值的数字类型或字符串类型变量,他们会共用一个内存地址.可以用 is 验证.数字:范围在 -5~256小数据池的目的是节省内存空间. 1 alist = [{'name':'alex', 'age':20}, {'name':'luffy',

Python【每日一问】04

问:a =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],求出列表a中所有奇数并构造新列表 答: 利用列表的元素下标遍历列表 a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 创建一个空列表b b = [] # 遍历列表的元素下标 for i in range(0, len(a)): if a[i] % 2 == 1: # 判断是否是元素是否是奇数 b.append(a[i]) # 使用 append()方法将符合条件的元素追加到列表b中 print

Python【每日一问】06

问:简述Python文件打开模式 r. w. a. r+.w+.a+之间的区别 答: 1.只读模式 r 文件存在:只读打开,只能执行读操作 文件不存在:报错 2.只写模式 w 文件存在:文件指针置于开头,清空原数据,写入新数据 文件不存在:创建一个新的空文件,写入数据 [注]:文件的数据不可读 3.追加模式 a 文件存在:文件指针置于文件的结尾,新的内容将会被写入到已有内容之后 文件不存在:创建一个新的空文件 [注]:文件的数据不可读 4.r+ 5.w+ 6.a+ 原文地址:https://ww

Python【每日一问】07

问:请解释使用 *args 和 **kwargs 的含义 答: *args:非关键字参数,表示将实参中按照位置传值,多余的值都给 args,多余的实参被打包成 tuple(元组),然后传递给函数调用 # 定义函数 def func(a,b,*args): pass func(1,2,c,d,e) # 1被赋值给a,2被赋值给b, # c,d,e被打包成元组,存在args中 **kwargs:关键字参数,表示形参中按照关键字传值,多余的值(以 k=v 的 形式)都给 kwargs,多余的关键字实参

Python【每日一问】09

问:请分别写一段Python代码实现一下功能: (1)计算一个文件中的大写字母数量 (2)输入中文,返回相应的拼音,并写入文件中 答: (1)计算一个文件中的大写字母数量 file_name = "upper.txt" with open(file_name,"r+",encoding="utf-8") as f: contents = f.read() count = 0 for char in contents: if char.isuppe

Python【每日一问】11

问: 请简单说明一下以下代码的执行过程以及最终的输出结果 def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b # 相当于 t=(b,a+b),a = t[0],b = t[1] n = n + 1 f = fib(10) print("== start ==") print(f.__next__()) print(f.__next__()) print(f.__next__()) 代码的执行

Python【每日一问】15

问:简述with方法打开处理文件实际上做了哪些工作 答: filename= "test.txt" with open(filename, "w", encoding="utf-8") as f: f.write("test code") 相当于 filename= "test.txt" file = open(filename,"w") try: file.write("te