通常說(shuō)到CoordinatorLayout
浊闪,我們首先想到的就是和AppBarLayout
一起使用,實(shí)現(xiàn)布局中特殊的Header效果
實(shí)際上概荷,CoordinatorLayout
是可以單獨(dú)使用的秕岛,不必和AppBarLayout
綁定到一起
CoordinatorLayout
的LayoutParam
中,有個(gè)Behavior
參數(shù),CoordinatorLayout
正是通過(guò)這個(gè)參數(shù)來(lái)決定子View的某些行為
Behavior
如何設(shè)置Bahavior
共有四種方式
- xml文件中通過(guò)
app:layout_behavior="com.example.touchdemo.coordinator.behavior1.TextBehavior"
- 若是動(dòng)態(tài)添加的子View继薛,則通過(guò)
LayoutParam#setBehavior
方法 - 若是自定義的View修壕,則有額外兩種方式
- 自定義的View繼承
CoordinatorLayout.AttachedBehavior
接口。如BottomAppBar
- 自定義View的class添加
CoordinatorLayout.DefaultBehavior
注解遏考,如AppBarLayout
- 自定義的View繼承
需要注意的是慈鸠,只有CoordinatorLayout
的直接子View設(shè)置Behavior
才有效果
Behavior可以實(shí)現(xiàn)哪些功能
Behavior有很多方法,我們可以歸為以下四類
- 布局相關(guān)
- 事件傳遞
- 依賴相關(guān)
- 嵌套滑動(dòng)
Behavior布局相關(guān)方法
public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
return false;
}
public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child,
int layoutDirection) {
return false;
}
Behavior事件傳遞方法
public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull MotionEvent ev) {
return false;
}
public boolean onTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull MotionEvent ev) {
return false;
}
也就是說(shuō)灌具,如果有需要青团,我們可以通過(guò)Behavior攔截兄弟View的touch事件
Behavior依賴相關(guān)方法
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull View dependency) {
//返回true表明child依賴于dependency
return false;
}
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull View dependency) {
//在dependency的size、position變化時(shí)咖楣,會(huì)回調(diào)該方法督笆。
//返回true表明已處理
return false;
}
public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull View dependency) {
}
Behavior嵌套滑動(dòng)相關(guān)方法
所有方法名中有Nested的方法。如果對(duì)嵌套滑動(dòng)不熟悉诱贿,需要先掌握NestedScrollingParent娃肿、NestedScrollingChild
這些預(yù)備知識(shí)
CoordinatorLayout如何處理依賴關(guān)系
onMeasure
方法中通過(guò)prepareChildren
方法,將children的依賴關(guān)系保存到一個(gè)有向無(wú)環(huán)圖中:
private final List<View> mDependencySortedChildren = new ArrayList<>();
//有向無(wú)環(huán)圖結(jié)構(gòu)珠十,不了解沒(méi)關(guān)系料扰,知道作用就可以
private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
private void prepareChildren() {
//清空
mDependencySortedChildren.clear();
mChildDag.clear();
//遍歷children,將依賴關(guān)系保存到mChildDag中
for (int i = 0, count = getChildCount(); i < count; i++) {
final View view = getChildAt(i);
final LayoutParams lp = getResolvedLayoutParams(view);
lp.findAnchorView(this, view);
//將i對(duì)應(yīng)的子view添加為圖的一個(gè)節(jié)點(diǎn)
mChildDag.addNode(view);
for (int j = 0; j < count; j++) {
//遍歷其他所有的子View焙蹭,查找被i依賴的j
if (j == i) {
continue;
}
final View other = getChildAt(j);
if (lp.dependsOn(this, view, other)) {
//view依賴于other
if (!mChildDag.contains(other)) {
// 保證other被添加為圖的節(jié)點(diǎn)
mChildDag.addNode(other);
}
// 添加從view指向other的一條路徑
mChildDag.addEdge(other, view);
//注意這里沒(méi)有break晒杈,說(shuō)明view可以依賴于多個(gè)other
}
}
}
// mChildDag.getSortedList會(huì)將所有節(jié)點(diǎn)進(jìn)行拓?fù)渑判? //拓?fù)渑判虻慕Y(jié)果保證 依賴的view在前,被依賴的view在后
//然后將結(jié)果保存到mDependencySortedChildren
mDependencySortedChildren.addAll(mChildDag.getSortedList());
// 翻轉(zhuǎn)列表孔厉,使被依賴的view在前桐智,依賴的view在后
//這樣當(dāng)被依賴的view有事件需要通知依賴的view時(shí),只需要從對(duì)應(yīng)的索引向后遍歷即可
Collections.reverse(mDependencySortedChildren);
}
從以上處理依賴關(guān)系的邏輯可以得出以下結(jié)論:
- CoordinatorLayout只能處理直接子view的依賴烟馅,即Behavior只能應(yīng)用到直接子view上
- 子view不能相互依賴,否則有向無(wú)環(huán)圖無(wú)法保證
- 依賴是多對(duì)多的關(guān)系然磷,一個(gè)View可以被多個(gè)View依賴郑趁,也可以依賴于多個(gè)View
Behavior依賴相關(guān)方法的分發(fā)
依賴相關(guān)方法的分發(fā)在onChildViewsChanged
,有三種事件類型:
static final int EVENT_PRE_DRAW = 0;
static final int EVENT_NESTED_SCROLL = 1;
static final int EVENT_VIEW_REMOVED = 2;
主要代碼不再貼出來(lái)
舉個(gè)例子
實(shí)現(xiàn)下面這樣嵌套滑動(dòng)的例子
header是一個(gè)TextView姿搜,下面跟著一個(gè)RecyclerView寡润,布局如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".coordinator.CoordinatorActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_light"
android:gravity="center"
android:padding="40dp"
android:text="我可以嵌套滑動(dòng)"
android:textColor="#fff"
android:textSize="20dp"
app:layout_behavior="com.example.touchdemo.coordinator.behavior1.TextBehavior" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.example.touchdemo.coordinator.behavior1.RecyclerViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
TextBehavior:
public class TextBehavior extends CoordinatorLayout.Behavior<TextView> {
private int currentOffsetY;
public TextBehavior() {
}
public TextBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull TextView child,
@NonNull View directTargetChild,
@NonNull View target,
int axes,
int type) {
//在垂直方向開啟嵌套滑動(dòng)
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull TextView child,
@NonNull View target,
int dx,
int dy,
@NonNull int[] consumed,
int type) {
//獲取可滑動(dòng)的offsetY值
int offsetY = calOffsetY(child, dy);
//滑動(dòng)TextView
ViewCompat.offsetTopAndBottom(child, offsetY - currentOffsetY);
consumed[1] = currentOffsetY - offsetY;
currentOffsetY = offsetY;
}
private int calOffsetY(TextView child, int dy) {
int offsetY = currentOffsetY - dy;
int maxOffsetY = 0;
int minOffsetY = -child.getHeight();
if (offsetY < minOffsetY) {
offsetY = minOffsetY;
}
if (offsetY > maxOffsetY) {
offsetY = maxOffsetY;
}
return offsetY;
}
}
RecyclerViewBehavior:
public class RecyclerViewBehavior extends CoordinatorLayout.Behavior<RecyclerView> {
public RecyclerViewBehavior() {
}
public RecyclerViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull RecyclerView child, @NonNull View dependency) {
//依賴于TextView
return dependency instanceof TextView;
}
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull RecyclerView child, @NonNull View dependency) {
//TextView布局改變時(shí),修改RecyclerView的offsetY
ViewCompat.offsetTopAndBottom(child, dependency.getBottom() - child.getTop());
return true;
}
}
完