用一个 "城市交通调度" 的故事,结合代码来解释 CoordinatorLayout 的工作原理。这个解释适合零基础小白,保证有趣又好懂!
🌆 故事背景:城市交通调度中心
想象一下,你是一座超级大都市的 "交通调度中心主任"。这座城市有:
-
主干道:允许各种车辆高速行驶(对应 CoordinatorLayout 中的滑动控件,如 RecyclerView)
-
特殊车辆:比如救护车、消防车,它们需要特殊通行权(对应 FloatingActionButton、AppBarLayout 等需要响应滑动的组件)
-
交通规则:当主干道上的车辆流动时,特殊车辆需要根据情况调整自己的位置(比如避让、跟随或保持距离)
这就是 CoordinatorLayout 的核心场景:当一个 View 滑动时,其他 View 需要做出相应的动作。
🚦 CoordinatorLayout 的 "交通调度规则"
CoordinatorLayout 就像这个城市的 "智能交通系统",它通过以下三个步骤解决滑动冲突:
- 安装传感器:在主干道上安装传感器,实时监测车辆流动(监听滑动事件)
- 分配调度员:为每辆特殊车辆分配一个调度员,告诉他如何响应主干道的变化(Behavior)
- 执行调度方案:当主干道有情况时,调度员指挥特殊车辆做出相应调整(处理滑动冲突)
👨💻 代码演示:CoordinatorLayout 的 "智能调度系统"
下面是 CoordinatorLayout 的核心代码简化版,展示了它如何处理滑动冲突:
java
public class CoordinatorLayout extends ViewGroup {
// 存储所有特殊车辆的调度员
private SparseArray<Behavior> mBehaviors = new SparseArray<>();
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
// 当主干道(View)开始滑动时,通知所有调度员
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
Behavior behavior = getBehavior(view);
if (behavior != null) {
// 询问调度员:你是否关心这个滑动事件?
if (behavior.onStartNestedScroll(this, view, child, target, nestedScrollAxes)) {
// 如果关心,标记这个view需要响应滑动
view.setTag(R.id.tag_needs_offset, true);
}
}
}
return nestedScrollAxes != 0;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
// 主干道滑动前,让调度员先处理(比如让特殊车辆先避让)
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getTag(R.id.tag_needs_offset) == Boolean.TRUE) {
Behavior behavior = getBehavior(view);
if (behavior != null) {
// 调度员指挥特殊车辆移动
behavior.onNestedPreScroll(this, view, target, dx, dy, consumed);
}
}
}
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
// 主干道滑动时,让调度员继续处理
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getTag(R.id.tag_needs_offset) == Boolean.TRUE) {
Behavior behavior = getBehavior(view);
if (behavior != null) {
// 继续指挥特殊车辆移动
behavior.onNestedScroll(this, view, target,
dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
}
}
}
// 获取view对应的调度员(Behavior)
private Behavior getBehavior(View view) {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
return lp.getBehavior();
}
}
🚑 自定义调度员:让 FAB 按钮 "聪明" 起来
现在,让我们为城市中的 "救护车"(FloatingActionButton) 编写一个调度员 (Behavior),让它在主干道滑动时 "聪明" 地避让:
java
public class FabScrollBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
public FabScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent,
FloatingActionButton child,
View directTargetChild, View target,
int nestedScrollAxes) {
// 只关心垂直方向的滑动
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedScroll(CoordinatorLayout parent, FloatingActionButton child,
View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(parent, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0) {
// 主干道向上滑动(用户向下滚动),让FAB隐藏
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams)
child.getLayoutParams();
int size = child.getHeight() + layoutParams.bottomMargin;
child.animate().translationY(size).setDuration(300).start();
} else if (dyConsumed < 0) {
// 主干道向下滑动(用户向上滚动),让FAB显示
child.animate().translationY(0).setDuration(300).start();
}
}
}
📱 如何使用这个 "调度系统"
在布局文件中,你只需要这样写,就能让 FAB 按钮根据 RecyclerView 的滑动自动隐藏 / 显示:
xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:src="@drawable/ic_add"
app:layout_behavior=".FabScrollBehavior"/> <!-- 指定调度员 -->
</androidx.coordinatorlayout.widget.CoordinatorLayout>
🧠 核心原理总结:三个关键点
- Behavior 机制:每个需要响应滑动的 View 都可以绑定一个 Behavior(调度员),它负责定义 View 如何响应滑动。
- 事件分发:CoordinatorLayout 通过
onStartNestedScroll
、onNestedPreScroll
、onNestedScroll
等方法,将滑动事件分发给所有绑定了 Behavior 的 View。 - 自定义逻辑:在 Behavior 中,你可以根据滑动的方向和距离,自定义 View 的响应方式,比如移动、缩放、显示 / 隐藏等。
🎉 现在你也是 "滑动冲突处理专家" 了!
通过这个城市交通调度的故事,你应该已经理解了:
-
CoordinatorLayout 就像交通调度中心
-
Behavior 就像每个特殊车辆的专属调度员
-
滑动事件就像主干道上的车流
-
特殊 View(如 FAB、AppBar)就像需要特殊通行权的车辆
当主干道上的车流发生变化时,调度中心会通知所有调度员,调度员再指挥各自的车辆做出相应调整,从而避免了交通混乱(滑动冲突)。
是不是很简单?下次遇到滑动冲突问题,记得你也是一位合格的 "交通调度员" 啦! 🚗🚑🚒