先看下最终效果:
仿TapTap游戏商城首页多控件渐隐式滑动效果—
从视频里面可以看到首页滑动时一共有四组控件在进行联动:
其次当页面进行左右切换时顶部的NavigationBar中的指示器也会跟随页面左右滑动的距离而进行颜色渲染
技术点:
需要自定义可变多种颜色的TextView(参考我的另一篇文章:自定义一个可变颜色的TextView)
需要自定义页面联动的Behavior
界面结构:
Activity整体分为上下两部分,页面切换ViewPager+Fragment,顶部的NavigationBar是一个单独的RelativeLayout
在第一个Fragment中根部局使用CoordinatorLayout协调器Header背景使用AppBarLayout进行联动,内容列表使用RecyclerView,下载的Group使用RelativeLayout
第二个Fragment比较简单,顶部的导航栏背景使用了一个带背景颜色的View,下面的内容列表是RecyclerView
首页滑动:
导航栏和下载Group的联动的锚点是RecyclerView并且会根据滑动距离来计算出自身所要联动的比例,而间距则是RecyclerView距离顶部的Y轴距离(也是整个Header的高度)
在布局中设置依赖关系
<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:name="com.demo.myviews.fragments.RecommentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
app:elevation="0dp"
android:layout_height="300dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"
android:scaleType="centerCrop"
android:src="@mipmap/game_banner"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
com.google.android.material.appbar.CollapsingToolbarLayout>
com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/fragment_recomment"/>
<LinearLayout
android:background="@color/colorAccent"
app:layout_behavior="com.demo.myviews.taptap.behavior.NavigationBarBehavior"
android:layout_width="match_parent"
android:layout_height="50dp"/>
<RelativeLayout
app:layout_behavior="com.demo.myviews.taptap.behavior.DownloadGroupBehavior"
android:id="@+id/rlTitleBar"
android:layout_marginTop="200dp"
android:layout_width="match_parent"
android:layout_height="80dp">
<TextView
android:textStyle="bold"
android:layout_marginBottom="50dp"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="疯狂的小镇"
android:textColor="@android:color/white"/>
<TextView
android:gravity="center"
android:drawableTop="@mipmap/add"
android:layout_marginStart="50dp"
android:layout_alignParentBottom="true"
android:text="关注"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_width="50dp"
android:layout_height="30dp"/>
<TextView
android:gravity="center"
android:drawableTop="@mipmap/add"
android:layout_marginEnd="50dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:text="详情"
android:textSize="15sp"
android:textColor="@android:color/white"
android:layout_width="50dp"
android:layout_height="30dp"/>
<com.demo.myviews.widget.DownloadBtn
app:txtSize="12dp"
app:loadingColor="@color/detail_downloadbutton_processing"
app:normalColor="@color/colorAccent"
app:finishColor="@color/detail_comment_user_level_titel_color"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_width="100dp"
android:layout_height="30dp"/>
RelativeLayout>
androidx.coordinatorlayout.widget.CoordinatorLayout>
NavigationBar的联动器逻辑
自定义一个NavigationBarBehavior设置联动依赖为列表并获取到navigationBar对象
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
if (navigationBar == null){
ViewGroup rootView = (ViewGroup) parent.getParent().getParent();
navigationBar = rootView.findViewById(R.id.rlNavigationBar);
Log.d(TAG, "layoutDependsOn: " + navigationBar);
}
return dependency.getId() == R.id.list;
}
因为navigationBar和RecyclerView不在同级所以需要通过parent来获取
之后在所依赖联动的View发生改变时进行距离获取和比例计算(onDependentViewChanged)
步骤是:
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
// 获取原始距离
if (translationY == 0){
translationY = (int) dependency.getY() - child.getHeight();
Log.d(TAG, "translationY: " + translationY);
}
// 获取当前距离content列表的距离
float depY = dependency.getY() - child.getHeight();
// 除以2用于区分滑动到上半部还是下半部
int ban = translationY / 2;
float offset = 0;
int color = barColor;
// 处于原始距离的下半部移动 减去10是为了修复移到屏幕外后还有几像素的露白
if (depY > (ban-10)){
// 求出移动距离占child的比重 因为除的是原始距离所以需要乘以2是才能形成视觉差
offset = (depY / translationY * child.getHeight() - child.getHeight()) * 2;
color = Color.TRANSPARENT;
}else {
// 处于原始距离的上半部移动
// 当滑动到上半部时直接使用一半header的高度计算出占child的比重
offset = depY / ban * child.getHeight();
// 由于是从屏幕外到屏幕内 所以需要从负值到0
offset = offset > 0 ? -offset:0;
}
// Log.d(TAG, "depY: " + depY + " ban: " + ban + " offset: " + offset);
child.setBackgroundColor(color);
child.setTranslationY(offset);
navigationBar.setTranslationY(offset);
return super.onDependentViewChanged(parent, child, dependency);
}
上面代码整体的步骤是
下载Group的联动器逻辑
自定义DownloadGroupBehavior并设置联动依赖
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
return dependency.getId() == R.id.list;
}
下载Group所依赖的也是列表,之后在onDependentViewChanged列表变化时获取滑动距离和计算偏移量
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
// 获取原始距离
if (contentY == 0){
contentY = dependency.getY();
}
// 获取移动的距离
float moveOffset = contentY - dependency.getY() ;
// 根据距离计算出占child整个高度的比例
float alpha = moveOffset / child.getHeight();
// 1-当前的比例等于要设置的透明度
child.setAlpha(1-alpha);
// 设置偏移,向上移动超出了child的Y值的0点坐标,所以应该设置为负值
child.setTranslationY(-moveOffset);
return super.onDependentViewChanged(parent, child, dependency);
}
下载Group的整体处理步骤是
到这第一个Fragment页面的几个滑动效果就基本完成了。
再来看下ViewPager左右切换时的NavigationBar位置如何变化
从Fragment1切换到Fragment2后NavigationBar的位置是固定的就是处于顶部的位置,从Fragment2切回到Fragment1时NavigationBar的位置要回到原来离开时的位置
实现方式是监听了ViewPager的OnPageChangeListener,通过onPageSelected的回调返回的position进行判断并给NavigationBar重新设置位置,具体代码如下
@Override
public void onPageSelected(int position) {
Log.d(TAG, "onPageSelected: " + position + " Y: " + rlNavigationBar.getTranslationY());
if (position == 1){
oldY = (int) rlNavigationBar.getTranslationY();
rlNavigationBar.setTranslationY(0);
}else {
rlNavigationBar.setTranslationY(oldY);
}
}
代码地址:欢迎star或review