用 "装修房子" 的故事看懂鸿洋大佬 AutoLayout 的原理

79 阅读4分钟

故事开场:小明的 "适配难题"

小明刚学 Android 开发时,遇到个头疼的问题:他按设计稿(720px 宽)写的布局,在自己的 720px 手机上好好的,到了同学的 1080px 手机上,所有按钮、文字都变小了,像被 "压缩" 了一样。

"这就像按 100 平米的图纸装修 200 平米的房子,家具全放进去就显得特小!" 产品经理吐槽道。

后来小明听说了张鸿洋的 AutoLayout 框架,用上之后,布局在任何手机上都和设计稿长得一样。这框架到底是怎么做到的?

原理揭秘:AutoLayout 的 "智能尺子"

其实 AutoLayout 的核心很简单:给每个控件一把 "智能尺子",这把尺子会根据当前屏幕和设计稿的比例,自动换算尺寸

就像装修时,图纸上标着 "沙发长 2 米",实际房子是图纸的 1.5 倍大,那沙发就该做 3 米(2×1.5)。

第一步:告诉框架 "设计稿多大"(初始化比例)

首先得让框架知道设计稿的尺寸(比如宽 720px),再拿到当前设备的屏幕尺寸(比如 1080px),算出一个缩放比例

// 设计稿宽度(假设是720px)
float designWidth = 720;
// 当前设备屏幕宽度(比如1080px)
float screenWidth = getResources().getDisplayMetrics().widthPixels;
// 计算比例:设备宽度 / 设计稿宽度
float scale = screenWidth / designWidth; // 1080/720 = 1.5

这一步就像告诉 "智能尺子":"实际房子是图纸的 1.5 倍大,所有尺寸都按这个比例放大"。

第二步:让控件 "记住设计稿尺寸"(自定义属性)

在 XML 布局里,我们不再用layout_width,而是用 AutoLayout 自定义的属性,比如layout_widthDesign,直接写设计稿上的尺寸:

<Button
    app:layout_widthDesign="100px"  // 设计稿上宽100px
    app:layout_heightDesign="50px"  // 设计稿上高50px"/>

框架怎么拿到这些值?它通过自定义属性解析实现:

// 定义自定义属性
<declare-styleable name="AutoLayout_Layout">
    <attr name="layout_widthDesign" format="dimension"/>
    <attr name="layout_heightDesign" format="dimension"/>
</declare-styleable>

// 在代码中获取属性值(比如在自定义布局的构造方法里)
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.AutoLayout_Layout);
// 拿到设计稿上的宽度(100px)
int widthDesign = ta.getDimensionPixelSize(R.styleable.AutoLayout_Layout_layout_widthDesign, 0);
// 拿到设计稿上的高度(50px)
int heightDesign = ta.getDimensionPixelSize(R.styleable.AutoLayout_Layout_layout_heightDesign, 0);
ta.recycle();

这一步相当于给沙发贴上标签:"图纸上我长 2 米"。

第三步:测量时 "按比例缩放"(重写 onMeasure)

Android 系统渲染布局时,会调用onMeasure方法计算控件大小。AutoLayout 的关键就是重写这个方法,用之前算好的比例缩放尺寸

比如设计稿上 100px 的按钮,在 1.5 倍的屏幕上就该是 150px:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 遍历所有子控件
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        // 获取子控件的LayoutParams(AutoLayout自定义的)
        AutoLayoutParams lp = (AutoLayoutParams) child.getLayoutParams();
        
        if (lp.widthDesign > 0) {
            // 用设计稿宽度 × 比例 = 实际宽度(100 × 1.5 = 150)
            int realWidth = (int) (lp.widthDesign * scale);
            // 重新设置控件宽度
            lp.width = realWidth;
        }
        
        if (lp.heightDesign > 0) {
            // 高度同理(50 × 1.5 = 75)
            int realHeight = (int) (lp.heightDesign * scale);
            lp.height = realHeight;
        }
    }
    // 调用父类方法完成测量
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

这就是 "智能尺子" 的核心操作:按比例把设计稿尺寸转换成实际屏幕尺寸。

第四步:处理 "特殊情况"(比如 margin、padding)

除了宽高,margin、padding 这些也要按比例缩放。原理和宽高一样,都是在测量时用比例换算:

// 处理margin(设计稿上marginLeft=20px,实际就是30px)
if (lp.leftMarginDesign > 0) {
    lp.leftMargin = (int) (lp.leftMarginDesign * scale);
}
// padding同理
child.setPadding(
    (int)(paddingLeftDesign * scale),
    (int)(paddingTopDesign * scale),
    (int)(paddingRightDesign * scale),
    (int)(paddingBottomDesign * scale)
);

就像装修时,图纸上 "沙发离墙 20cm",实际就要留 30cm(20×1.5)。

总结:AutoLayout 的本质

AutoLayout 就像一个 "自动缩放器",核心逻辑用一句话总结:
在控件测量阶段,把设计稿上的尺寸(宽高、margin、padding 等)乘以当前屏幕与设计稿的比例,得到实际显示尺寸

它通过自定义属性记录设计稿尺寸,重写onMeasure方法完成比例换算,让同一套布局在不同屏幕上都能和设计稿保持一致的视觉效果 —— 就像用同一套图纸,在不同大小的房子里都能装出相同的 "感觉"。

是不是很简单?其实复杂框架的底层,往往都是由这些 "按比例换算" 的简单逻辑构成的~