Android中高级联动控件 RecyclerView+ViewPager嵌套滑动

先看下最终效果:

仿TapTap游戏商城首页多控件渐隐式滑动效果—
从视频里面可以看到首页滑动时一共有四组控件在进行联动:

  1. 顶部的NavigationBar会随着Content列表向上滑动时渐渐隐去
  2. Header的背景整体会随着下面的Content列表而逐渐上划消失
  3. Header底部的下载Group会随着滑动距离渐渐淡化
  4. 当Content列表滑动快到顶部时之前隐去的NavigationBar设置了背景且会从屏幕外逐渐展示出来并回到原来的位置

其次当页面进行左右切换时顶部的NavigationBar中的指示器也会跟随页面左右滑动的距离而进行颜色渲染

技术点:
需要自定义可变多种颜色的TextView(参考我的另一篇文章:自定义一个可变颜色的TextView)
需要自定义页面联动的Behavior

界面结构:

  • Activity整体分为上下两部分,页面切换ViewPager+Fragment,顶部的NavigationBar是一个单独的RelativeLayout

  • 在第一个Fragment中根部局使用CoordinatorLayout协调器Header背景使用AppBarLayout进行联动,内容列表使用RecyclerView,下载的Group使用RelativeLayout

  • 第二个Fragment比较简单,顶部的导航栏背景使用了一个带背景颜色的View,下面的内容列表是RecyclerView

通过上面的布局后它们单独的样子分别是这样的
Android中高级联动控件 RecyclerView+ViewPager嵌套滑动_第1张图片

Android中高级联动控件 RecyclerView+ViewPager嵌套滑动_第2张图片
Android中高级联动控件 RecyclerView+ViewPager嵌套滑动_第3张图片
通过不同页面的分层叠加最终呈现出带有导航栏效果的页面

首页滑动:
导航栏和下载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);
    }

上面代码整体的步骤是

  • 获取列表距离顶部的原始距离
  • 获取列表当前距离顶部的距离
  • 使用原始距离÷2再和当前距离相比较用于区分滑动到了原始距离的上半部还是下半部
  • 计算出NavigationBar需要上/下偏移的比例和颜色
  • 最后设置颜色和偏移量

下载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的整体处理步骤是

  • 布局初始化完成后获取列表距离顶部的原始距离
  • 获取列表当前移动的距离计算出偏移量
  • 根据偏移量计算出透明度
  • 给child(下载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

你可能感兴趣的:(android,android,java,viewpager,app,android,studio)