在知道Behavior的使用方法了之后,再用一个模仿虾米音乐的首页来实践一下Behavior的使用。
为了实现这个效果,我们我可以分为下面四步走
第一步 观察一下可以分为几个部分
这个页面从上到下,大致可以分为searchbar header banner list四个部分。
还有一个部分bottom是一直在底部没有互动的,我们就先暂时不管。
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/img_search"
android:layout_width="match_parent"
android:layout_height="45dp"
android:scaleType="fitXY"
android:src="@drawable/xiami_search"
app:layout_behavior="com.xugter.cooridnatorlayoutstudy.part3.SearchBarBehavior" />
<ImageView
android:id="@+id/img_header"
android:layout_width="match_parent"
android:layout_height="48dp"
android:scaleType="fitXY"
android:src="@drawable/xiami_header"
app:layout_behavior="com.xugter.cooridnatorlayoutstudy.part3.HeaderBehavior" />
<ImageView
android:id="@+id/img_banner"
android:layout_width="match_parent"
android:layout_height="127dp"
android:scaleType="fitXY"
android:src="@drawable/xiami_banner"
app:layout_behavior="com.xugter.cooridnatorlayoutstudy.part3.BannerBehavior" />
<android.support.v4.widget.NestedScrollView
android:id="@+id/view_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="43dp"
app:layout_behavior="com.xugter.cooridnatorlayoutstudy.part3.ListBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="81dp"
android:onClick="useless"
android:scaleType="fitXY"
android:src="@drawable/xiami_tab" />
......
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<ImageView
android:layout_width="match_parent"
android:layout_height="43dp"
android:layout_gravity="bottom"
android:scaleType="fitXY"
android:src="@drawable/xiami_bottom" />
</android.support.design.widget.CoordinatorLayout>
第二步 确定这几个部分的依赖关系
这里我定的是
searchbar→header→banner→list
第三步 确定可以愿意接受滑动事件的部分
这里我定的是list部分
第四步 开始写代码
1.List
这里我们先从依赖的根源开始写相应的behavior,即ListBehavior。
写ListBehavior第一步先确定List的初始布局
从上面的gif图来看,初始位置是在searchbar header banner的下面的。当画到最上面的时候在header的下面。
所以可以在onlayout那里,先确定能显示最大的时候位置,用child.layout来确定他的宽和高。再通过sety把他放到初始位置。
@Override
public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull View child, int layoutDirection) {
//来定位list的初始位置,并进行第一次的位移
child.layout(0, headerHeight, child.getMeasuredWidth(), parent.getMeasuredHeight() - bottomHeight);
child.setY(searchBarHeight + headerHeight + bannerHeight);
return true;
}
这几个height都会在behavior构造的时候初始化。
然后就是处理list的滑动事件了。
这里有个好玩的事情发生了,在上一篇介绍behavior的时候,我们说的是能接收滑动事件的childA把事件发给Coordinatorlayout,然后coordinatorlayout发给愿意接收滑动事件的childB,再通过onNestedPreScroll和onNestedScroll交互。
但是这里情况有点不一样,这里childA是list,childB也是list,childA和childB就变成同一个人了。其实这是不用管的,虽然是同一个对象,但是可以当成两个对象,直接把list当成childB,至于childA是谁你就不用管了。
首先在onStartNestedScroll直接返回true,表示愿意接收滑动事件。
然后再onNestedPreScroll开始处理滑动事件,这里就需要根据list当前的位置和滑动的方向来判断是否需要消费这个滑动事件。这里可以根据代码里面的注释,大致看一下思路。
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
if (startPos) {
if (scrollMode == 0) {
if (child.getY() == headerHeight) {
//当list处于页面的顶部,需要根据滑动的方向来确定 是滚动模式还是嵌套滑动模式
if (dy > 0) {
//向上滑动 进入滚动模式
scrollMode = 1;
} else {
//向下滑动 进入嵌套滑动模式
scrollMode = -1;
}
} else {
//当list不处于页面顶部,进入嵌套滑动模式
scrollMode = -1;
}
}
//是滚动模式,不触发嵌套滑动 直接返回
if (scrollMode > 0) return;
if (dy > 0) {
if (child.getY() > headerHeight) {
//只有未到达顶部 list才能嵌套滑动 不然滑不动界面
float targetPos = child.getY() - dy;
if (targetPos > headerHeight) {
child.setY(targetPos);
} else {
child.setY(headerHeight);
}
}
} else {
if (child.getY() < searchBarHeight + headerHeight + bannerHeight) {
//只有未到达底部 list才能嵌套滑动 不然滑不动界面
float targetPos = child.getY() - dy;
if (targetPos < searchBarHeight + headerHeight + bannerHeight) {
child.setY(targetPos);
} else {
child.setY(searchBarHeight + headerHeight + bannerHeight);
}
}
}
//嵌套滑动 无论什么情况 都要消费掉所有事件
consumed[1] = dy;
}
}
其中为了每次滑动动作开始的时候,重置一下状态我在onInterceptTouchEvent加了一些动作,当接受到DOWN这个事件的时候,回重置一些状态。这个部分主要是为了实现,当虾米list滑到顶部的时候,需要断掉滑动。然后需要再次按下滑动,才能继续滑动这个效果。
@Override
public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull MotionEvent ev) {
if (ev.getAction() == 0) {
if (child.getScrollY() > 0) {
//List没有滚到顶部,先进行自己滚动,不触发嵌套滑动
startPos = false;
} else {
//List滚动到顶部,待确定触发嵌套滑动
startPos = true;
}
//重置一下 是否滚动模式
scrollMode = 0;
}
return super.onInterceptTouchEvent(parent, child, ev);
}
ListBehaivor的思路大致就是这样了,完成ListBehaivor基本上就完成了70%了。
2.Banner
接下来我们在看依赖于list的BannerBehaivor。
这个部分思路就很简单,首先在layoutDependsOn里确定依赖关系。
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
return dependency.getId() == R.id.view_content;
}
然后再onDependentViewChanged里,根据dependency的位置来更新自己的位置
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
if (dependency.getY() >= headerHeight && dependency.getY() <= headerHeight + bannerHeight) {
child.setAlpha((dependency.getY() - headerHeight) / bannerHeight);
child.setY(headerHeight);
} else if (dependency.getY() > headerHeight + bannerHeight) {
child.setY(dependency.getY() - bannerHeight);
}
return true;
}
3.Header
同理HeaderBehavior也先确定依赖关系
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
return dependency.getId() == R.id.img_banner;
}
然后再根据dependency的位置来更新自己的位置
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
if (dependency.getY() - child.getMeasuredHeight() > 0) {
child.setY(dependency.getY() - child.getMeasuredHeight());
} else {
child.setY(0);
}
return true;
}
4.SearchBar
再同理来写SearchBarBehavior
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
return dependency.getId() == R.id.img_header;
}
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
float progress = dependency.getY() / getSearchBarHeight();
child.setAlpha(progress * progress);
return true;
}
这样大致完成了虾米首页的效果,还有一些细节没有完善,比如自动开合的效果等,这个就等有闲心再完善一下吧。
效果如下:
其实这个方案也不是唯一的,比如可以把header和banner当成一个child,然后根据位置信息控制banner的透明
再比如 接收嵌套滑动事件的也可以不是list,可以是header或者其他,思路也大同小异,可以自己尝试实现一下。
总结
实现类似页面效果的步骤大致分为四步
1.观察,确定分为几个部分
2.确定依赖关系
3.确定接收滑动事件的部分
4.写代码,写代码都一般思路是先从根view(也就是最后被依赖的部分)开始,一般根view也是愿意接收滑动事件的view。然后再根据依赖路径,一直写behavior。