废话少说,先上效果图:
得如何实现这样的效果呢?
首先,将动画拆分几个独立的部分:
- 「芳草地考场」横轴从左侧移动到中间,纵轴移动到标题栏,字体同时变小;
- 「切换考场」纵轴移动到标题栏,距离右侧边距变斤,字体同时变小;
- 「标题栏」背景渐变,图标颜色变化;
- 「考场横向列表」缩小,左移靠边,同时能吸顶;
现在来考虑具体要怎么做。做开始想到的就是 CoordinatorLayout + CollapsingToolbarLayout 来实现,Google 后发现实现的效果与预期不同,又没有足够的时间去研究,所以抛弃了此方案,打算直接自己写。那么要如何写呢?动画无非是由一系列的状态组合起来的,当滑动 100dp 时,界面这样展示;当滑动 200dp 时,界面那样展示。思路就很明确了:直接监听页面滑动,根据滑动的距离,来展示界面所有的元素。那么剩下的,便是根据滑动距离,来做 4 个部分动画元素的展示了。
最终实现的 xml 布局如下:
1 | <android.support.design.widget.CoordinatorLayout |
下面逐一来解析。
- bgView 是最上面的背景图,高度 375dp,「考场横向列表」卡片需要覆盖一部分在上面,AppBarLayout 是继承自 LinearLayout 的,所以加了个 android:layout_marginBottom=”-217dp” 以此来实现覆盖效果。
- 当滑到最大值时,顶部的 tipView 与 placeRv 需要吸顶,所以二者组合为一个 LinearLayout,同时只给 bgView 设置 **app:layout_scrollFlags=”scroll”**,便可实现吸顶。因为整个界面是通栏,所以在动画结束状态,videoLl 需要设置一个 **android:layout_marginTop=”68dp”**,使其正好处于标题栏之下。参考Android 滑动吸顶效果。
- 「芳草地考场」、「切换考场」、「标题栏」需要在页面最上层展示,所以直接写到 CoordinatorLayout 最外层。
- 「芳草地考场」、「切换考场」 的 marginTop 都是依据设计稿写死的值,使其正好处于 AppBarLayout 的某个位置,不能轻易改。
- NestedScrollView include 底部的列表布局,进行嵌套滑动。
ok,现在静态界面写好了,下面就是要根据页面滑动来进行元素展示了。CoordinatorLayout 直接子孩子可以直接使用 Behavior,这样可以将 4 个动画块分开,非直接子孩子则使用监听的方式。
「芳草地考场」Behavior:
1 | class ExamPlaceNameBehavior(context: Context, attr: AttributeSet) : CoordinatorLayout.Behavior<TextView>(context, attr) { |
「切换考场」Behavior:
1 | class SwitchPlaceBehavior(context: Context, attr: AttributeSet) : CoordinatorLayout.Behavior<TextView>(context, attr) { |
「标题栏」Behavior:
1 | class ExamTitleBehavior(context: Context, attr: AttributeSet) : CoordinatorLayout.Behavior<RelativeLayout>(context, attr) { |
「考场横向列表」监听:
1 | appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, y -> |
tipView 通过 ExamRouteLineTopTipModel.needClose() 判断是否需要展示,两种状态的 margin 不一致,需要区分开。另外,CoordinatorLayout 有子孩子变化,都会回调到 OnOffsetChangedListener,所以需要保存 progress,避免一直调用方法。
因为需求太具体,文章写起来不宜碎碎念,就只帖了代码,记录一下,以后碰到类似需求,有思路去做。