3、布局系统差异:CSS Flexbox vs 原生布局
做界面移植,布局系统是第一道坎。说白了,你在 Web 上用 Flexbox 写得飞起,到了 Android 和 iOS 那边,会发现「这玩意儿怎么对不上?」。我刚开始做跨平台移植时,就因为这个栽过跟头——一个简单的居中布局,Web 端一行代码搞定,原生端折腾了半天。
这一章,咱们就聊聊 Flexbox、Android LinearLayout 和 iOS Auto Layout 这三者的核心差异,以及单位换算的那些坑。
3.1 三大布局系统的核心哲学
先看一张图,理清它们各自的定位:
3.2 Flexbox vs LinearLayout:看似相似,实则不同
很多前端同学刚接触 Android 布局时,会觉得 LinearLayout 跟 Flexbox 差不多。嗯,表面上看确实像——都有主轴方向,都能控制对齐。但实际用起来,差别不小。
3.2.1 主轴方向控制
Flexbox 用 flex-direction 控制方向,LinearLayout 用 orientation。这个倒是对应的:
/* CSS Flexbox */
.container {
display: flex;
flex-direction: row; /* 水平排列 */
/* flex-direction: column; */ /* 垂直排列 */
}
<!-- Android LinearLayout -->
<LinearLayout
android:orientation="horizontal" <!-- 水平排列 -->
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 子视图 -->
</LinearLayout>
但有个关键区别:Flexbox 默认是 row,LinearLayout 默认是 horizontal。这个倒还好,真正坑人的在后面。
3.2.2 对齐方式:gravity vs justify-content
这里我开始踩坑了。Flexbox 用 justify-content 控制主轴对齐,align-items 控制交叉轴对齐。LinearLayout 呢?它用 gravity 和 layout_gravity。
我举个例子你就明白了:
/* Flexbox 水平居中 */
.container {
display: flex;
justify-content: center; /* 主轴居中 */
align-items: center; /* 交叉轴居中 */
}
<!-- LinearLayout 水平居中 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<!-- 注意:gravity 同时控制主轴和交叉轴 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello" />
</LinearLayout>
gravity="center_horizontal"。而 Flexbox 的 justify-content 和 align-items 是分开的。这个差异,我第一次移植时没注意,结果所有按钮都跑到了中间位置。
3.2.3 权重分配:flex vs layout_weight
这个倒是比较直观的对应关系:
| 功能 | CSS Flexbox | Android LinearLayout |
|---|---|---|
| 等比例分配 | flex: 1 |
layout_weight="1" |
| 固定宽度 + 剩余分配 | flex: 0 0 100px |
layout_width="100dp" + layout_weight="0" |
| 比例控制 | flex: 2 vs flex: 1 |
layout_weight="2" vs layout_weight="1" |
但有个细节:Android 的 layout_weight 需要配合 layout_width="0dp"(水平方向)或 layout_height="0dp"(垂直方向)使用。这个我一开始不知道,结果权重分配死活不对。
android:layout_width="0dp"。
3.3 Flexbox vs Auto Layout:完全不同的思维方式
如果说 LinearLayout 跟 Flexbox 还算「亲戚」,那 Auto Layout 简直就是「外星人」。我第一次接触 Auto Layout 时,内心是崩溃的——什么?没有主轴?没有交叉轴?全靠约束?
3.3.1 约束链 vs 弹性盒子
Flexbox 的核心是「弹性盒子」——子元素在容器内自动排列。Auto Layout 的核心是「约束链」——每个视图的位置和大小由一组约束方程决定。
// iOS Auto Layout 示例
NSLayoutConstraint.activate([
// 左边距 20
view.leadingAnchor.constraint(equalTo: parent.leadingAnchor, constant: 20),
// 顶部对齐
view.topAnchor.constraint(equalTo: parent.topAnchor, constant: 10),
// 宽度 100
view.widthAnchor.constraint(equalToConstant: 100),
// 高度 50
view.heightAnchor.constraint(equalToConstant: 50)
])
你看,每个约束都是独立的。没有「容器自动排列子元素」这个概念,你得手动告诉每个视图它应该在哪。
3.3.2 等宽布局的差异
在 Flexbox 里,等宽布局很简单:
.container {
display: flex;
}
.item {
flex: 1;
}
在 Auto Layout 里,你得这样写:
// 三个等宽按钮
NSLayoutConstraint.activate([
button1.leadingAnchor.constraint(equalTo: view.leadingAnchor),
button1.trailingAnchor.constraint(equalTo: button2.leadingAnchor),
button2.trailingAnchor.constraint(equalTo: button3.leadingAnchor),
button3.trailingAnchor.constraint(equalTo: view.trailingAnchor),
button1.widthAnchor.constraint(equalTo: button2.widthAnchor),
button2.widthAnchor.constraint(equalTo: button3.widthAnchor)
])
嗯,你没看错。三个等宽按钮,Flexbox 一行代码,Auto Layout 六行约束。这就是为什么很多 iOS 开发者喜欢用第三方库(比如 SnapKit)来简化约束写法。
- Flexbox:容器驱动,子元素自动排列
- LinearLayout:容器驱动,但只有线性排列
- Auto Layout:约束驱动,每个视图独立定义位置
3.4 单位换算:百分比、dp、sp 的坑
布局系统搞定了,单位换算又是一道坎。Web 上用百分比和 px 用惯了,到了移动端发现还有 dp、sp、pt 这些单位。
3.4.1 百分比 vs dp
Flexbox 里,你可以用百分比来定义宽度:
.item {
width: 50%; /* 父容器宽度的一半 */
}
但在 Android 里,LinearLayout 不支持百分比。你得用权重或者写死 dp 值。不过 Android 有个 PercentRelativeLayout 和 PercentFrameLayout(来自 support 库),但现在已经不推荐用了。
我的建议是:用 layout_weight 替代百分比。比如 50% 宽度,可以写成:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
3.4.2 dp、sp、pt 的区别
这个表格帮你快速理解:
| 单位 | 全称 | 用途 | 特点 |
|---|---|---|---|
| dp | Density-independent Pixels | 布局尺寸、边距 | 在不同密度屏幕上保持物理尺寸一致 |
| sp | Scale-independent Pixels | 字体大小 | 跟随用户系统字体缩放设置 |
| pt | Points | iOS 布局和字体 | 1pt = 1/72 英寸 |
| px | Pixels | 不推荐直接使用 | 不同屏幕密度下表现不一致 |
3.4.3 单位换算公式
如果你需要在代码里手动换算,记住这个公式:
// dp 转 px
px = dp * (density / 160)
// 其中 density 是屏幕密度
// mdpi: 160 -> 1dp = 1px
// hdpi: 240 -> 1dp = 1.5px
// xhdpi: 320 -> 1dp = 2px
// xxhdpi: 480 -> 1dp = 3px
在 iOS 上,pt 和 px 的关系更简单:
// 非 Retina 屏:1pt = 1px
// Retina 屏:1pt = 2px
// Super Retina 屏:1pt = 3px
3.5 移植实战:一个居中布局的三种写法
最后,咱们看一个实际例子。一个简单的居中布局:一个按钮在屏幕正中间。
Flexbox 版本:
<div style="display: flex; justify-content: center; align-items: center; height: 100vh;">
<button>点击我</button>
</div>
LinearLayout 版本:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击我" />
</LinearLayout>
Auto Layout 版本:
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
你看,最简单的居中布局,三种写法差异就这么大。更复杂的布局,差异只会更明显。
嗯,布局系统的差异就聊到这儿。记住一句话:不要试图用一种布局系统的思维去写另一种。每个平台都有自己的设计哲学,尊重它,才能写出好的界面。