• 使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉视差效果并解决各种滑动冲突


    使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉视差效果并解决各种冲突

    如果你还在为处理滑动冲突而发愁,那么你需要静下心来看看这边文章,如果你能彻底理解这篇文章中使用的技术,那么,一切滑动冲突的问题解决起来就轻而易举了:
    先扔一个最终实现的效果图


    先分析下效果图中实现的功能点
    • 顶部下拉时背景图形成视差效果
    • 上拉时标题栏透明切换显示
    • 底部实现TabLayout+ViewPager+Fragment+RecyclerView
    • NestedScrollView+ViewPager的滑动冲突解决
    • NestedScrollView+RecyclerView滑动冲突的解决

    复杂在哪里?整个布局中使用了SmartRefreshLayout,NestedScrollView,ViewPager,RecyclerView,每一个都有滑动事件,我们平时只是使用ScrollView+RecyclerView都会有滑动冲突,更何况这里有四个会引起冲突的控件一起使用!

    接下来,我们一步一步实现这个效果
    1、布局设计分析
    -FrameLayout(最外层)
        -ImageView(头部背景图)
            -SmartRefreshLayout(头部刷新控件)
                -JudgeNestedScrollView(自定义的NestedScrollView)
                    ...省略中间巴拉巴拉布局
                    -Tablayout
                        -ViewPager
    

    2、功能点实现说明
    2.1、下拉时视差效果的实现
    最外层为FrameLayout,ImageView高度设置超过屏幕顶部,借助SmartRefreshLayout控件在下拉和松开时头部背景图做平移处理,背景图片做了高斯模糊处理

    布局代码:
    <FrameLayout 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"
        >
    
        <ImageView
            android:id="@+id/iv_header"
            android:layout_width="match_parent"
            android:layout_height="670dp"
            android:layout_marginTop="-300dp"
            android:adjustViewBounds="true"
            android:contentDescription="@string/app_name"
            android:scaleType="centerCrop"
            android:src="@drawable/image_home"
            app:layout_collapseMode="parallax" />
    
        <com.scwang.smartrefresh.layout.SmartRefreshLayout
            android:id="@+id/refreshLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:srlEnablePreviewInEditMode="false">
            ...
    下拉刷新时头部背景图片平移代码:

    refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() {
                @Override
                public void onHeaderPulling(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                    mOffset = offset / 2;
                    ivHeader.setTranslationY(mOffset - mScrollY);
                }
    
                @Override
                public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                    mOffset = offset / 2;
                    ivHeader.setTranslationY(mOffset - mScrollY);
                }
            });

    2.2、TabLayout的顶部悬浮效果的实现
    此处使用的是最为简单笨拙的方法,两个TabLayout,一个固定为屏幕顶部ToolBar下面,并隐藏,另一个正常绘制在布局中;
    计算ToolBar的高度,根据NestedScrollView滑动的高度(这里的高度指的是跟随滑动的TabLayout的Y坐标)恰好到ToolBar的高度位置时显示隐藏的ToolBar;

    -FrameLayout
        -SmartRefreshLayout
            -Tablayout
            -Viewpager
        -SmartRefreshLayout
        -RelativeLayout
            -Toolbar
            -Tablayout
        -RelativeLayout
    -FrameLayout
    
    
    toolbar.post(new Runnable() {
                @Override
                public void run() {
                    toolBarPositionY = toolbar.getHeight();
                }
            });
    
    
    scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
                @Override
                public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    int[] location = new int[2];
                    magicIndicator.getLocationOnScreen(location);
                    int xPosition = location[0];
                    int yPosition = location[1];
                    if (yPosition < toolBarPositionY) {
                        toolBarTablayout.setVisibility(View.VISIBLE);
                    } else {
                        toolBarTablayout.setVisibility(View.GONE);
                    }
                }
            });

    2.3、ToolBar的渐变透明度以及按钮的切换

    <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                style="@style/AppTheme.Toolbar"
                android:layout_marginBottom="0dp"
                android:background="@android:color/transparent"
                app:layout_collapseMode="pin">
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center_vertical"
                    android:orientation="horizontal">
    
                    <ImageView
                        android:id="@+id/iv_back"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:src="@drawable/back_white" />
    
    
                    <android.support.v7.widget.ButtonBarLayout
                        android:id="@+id/buttonBarLayout"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:layout_weight="1"
                        android:gravity="center">
    
                        <de.hdodenhof.circleimageview.CircleImageView
                            android:id="@+id/toolbar_avatar"
                            style="@style/UserTitleAvatar"
                            android:src="@drawable/timg" />
    
                        <TextView
                            android:id="@+id/toolbar_username"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_gravity="center"
                            android:maxLines="1"
                            android:text="SiberiaDante"
                            android:textColor="@color/mainBlack"
                            android:textSize="@dimen/font_16" />
    
    
                    </android.support.v7.widget.ButtonBarLayout>
    
                    <ImageView
                        android:id="@+id/iv_menu"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:layout_gravity="end"
                        android:src="@drawable/icon_menu_white" />
                </LinearLayout>
    
            </android.support.v7.widget.Toolbar>

    ToolBar中间标题默认隐藏,使用的ButtonBarLayout包裹ImageView和TextView设置百分比透明,具体处理有两点:

    buttonBarLayout.setAlpha(0);
    toolbar.setBackgroundColor(0);

    * 下拉头部刷新时ToolBar渐变隐藏,同样利用SmartRefreshLayout处理

    refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() {
                @Override
                public void onHeaderPulling(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                    toolbar.setAlpha(1 - Math.min(percent, 1));
                }
    
                @Override
                public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                    toolbar.setAlpha(1 - Math.min(percent, 1));
                }
            });

    * 上下滑动时标题栏渐变显示和隐藏,并切换图标颜色(这里实际上是根据临界点直接更换图片,处理的比较简单)

    scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
                int lastScrollY = 0;
                int h = DensityUtil.dp2px(170);
                int color = ContextCompat.getColor(getApplicationContext(), R.color.mainWhite) & 0x00ffffff;
    
                @Override
                public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    int[] location = new int[2];
                    magicIndicator.getLocationOnScreen(location);
                    int xPosition = location[0];
                    int yPosition = location[1];
    
                    if (lastScrollY < h) {
                        scrollY = Math.min(h, scrollY);
                        mScrollY = scrollY > h ? h : scrollY;
                        buttonBarLayout.setAlpha(1f * mScrollY / h);
                        toolbar.setBackgroundColor(((255 * mScrollY / h) << 24) | color);
                        ivHeader.setTranslationY(mOffset - mScrollY);
                    }
                    if (scrollY == 0) {
                        ivBack.setImageResource(R.drawable.back_white);
                        ivMenu.setImageResource(R.drawable.icon_menu_white);
                    } else {
                        ivBack.setImageResource(R.drawable.back_black);
                        ivMenu.setImageResource(R.drawable.icon_menu_black);
                    }
    
                    lastScrollY = scrollY;
                }
            });

    2.4、NestedScrollView嵌套ViewPager导致ViewPager高度为0的处理
    很多人可能认为直接自定义ViewPager,测量子View的高度,让ViewPager去适应高度即可,其实不然,如果这样处理的话我们的Viewpager可能就是无限高度,我们在处理完NestedScrollView后,无限高度的ViewPager和RecyclerView又是一个问题,所以我这里的处理是计算ViewPager所需要的最大高度,即TabLayout在最顶部显示时到屏幕底部的最大高度为ViewPager高度

     toolbar.post(new Runnable() {
                @Override
                public void run() {
                    toolBarPositionY = toolbar.getHeight();
                    ViewGroup.LayoutParams params = viewPager.getLayoutParams();
                    params.height = SDScreenUtil.getScreenHeight() - toolBarPositionY - tablayout.getHeight()+1;
                    viewPager.setLayoutParams(params);
                }
            });
    这里为什么要+1,后面会有解释

    2.5、NestedScrollView嵌套RecyclerView滑动冲突
    NestedScrollView嵌套RecyclerView滑动冲突我们使用事件拦截处理,这里处理的是NestedScrollView的滑动,首先滑动的时候肯定是需要NestedScrollView的滑动事件,所以我们默认不拦截NestedScrollView的滑动事件,直到TabLayout顶部悬浮的时候,我们拦截NestedScrollView的滑动事件,交给RecyclerView来处理
    * 重写NestedScrollView

    public class JudgeNestedScrollView extends NestedScrollView {
        private boolean isNeedScroll = true;
        ...省略构造方法
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    return isNeedScroll;
            }
            return super.onInterceptTouchEvent(ev);
        }
    
        /*
        改方法用来处理NestedScrollView是否拦截滑动事件
         */
        public void setNeedScroll(boolean isNeedScroll) {
            this.isNeedScroll = isNeedScroll;
        }
    }

    这里默认不拦截NestedScrollView滑动事件,只有当我们TabLayout滑动到顶部时才去拦截,也就是TabLayout显示隐藏的时候

    scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
                @Override
                public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    int[] location = new int[2];
                    magicIndicator.getLocationOnScreen(location);
                    int xPosition = location[0];
                    int yPosition = location[1];
                    if (yPosition < toolBarPositionY) {
                        tablayout.setVisibility(View.VISIBLE);
                        scrollView.setNeedScroll(false);
                    } else {
                        tablayout.setVisibility(View.GONE);
                        scrollView.setNeedScroll(true);
                    }
    至于前面测量ViewPager高度的时候,为什么会+1处理,这是因为,如果不+1时,刚好是TabLayout要出现的临界点,也就是ViewPager恰好的高度,但是这个时候又刚好是我们NestedScrollView拦截没有取消的临界点,所以,在上滑的时候,TabLayout刚好悬浮顶部时,RecyclerView没有获取事件,无法进行滑动,这就是给ViewPager+1处理的理由;

    2.5、NestedScrollView嵌套ViewPager滑动冲突2
    如果你足够细心的话,就会发现,当你的TabLayout上滑到一半的时候,再去左右滑动ViewPager是滑动不了的,因为这个时候NestedScrollView依然消费事件,所以我们还需要对NestedScrollView事件进行处理,判断如果是左右滑动的时候,我们不让NestedScrollView处理,而是交给子View处理,即ViewPager

    public class JudgeNestedScrollView extends NestedScrollView {
        private boolean isNeedScroll = true;
        private float xDistance, yDistance, xLast, yLast;
        ...省略构造方法
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    xDistance = yDistance = 0f;
                    xLast = ev.getX();
                    yLast = ev.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    final float curX = ev.getX();
                    final float curY = ev.getY();
                    xDistance += Math.abs(curX - xLast);
                    yDistance += Math.abs(curY - yLast);
                    xLast = curX;
                    yLast = curY;
                    if (xDistance > yDistance) {
                        return false;
                    }
                    return isNeedScroll;
    
            }
            return super.onInterceptTouchEvent(ev);
        }
    
        /*
        改方法用来处理NestedScrollView是否拦截滑动事件
         */
        public void setNeedScroll(boolean isNeedScroll) {
            this.isNeedScroll = isNeedScroll;
        }
    }

    至此,完美的解决了所有的问题,当时有些细节这里并没有话费太多的时间去处理,如有任何问题,欢迎各位大佬进行指正

    源码:https://github.com/SiberiaDante/MultiScrollDemo


  • 相关阅读:
    编译asp.net core源代码,并搭建基于源代码的测试环境
    sql server 按照in里面的顺序进行查询
    j-roadflow-java工作流修改抄送任务已阅知表单为只读
    roadflow工作流用nginx做负载均衡的配置文件
    vue ie 报错SCRIPT5022: SecurityError sockjs.js (1683,3)
    RoadFlow Asp.net Core Vue工作流引擎增加对PostgreSQL数据库的支持
    点,线,面
    .NET 5应用程序中的跨域请求
    物料齐套计算
    高级计划AP(Advance Planning)是如何运作的 (转载)
  • 原文地址:https://www.cnblogs.com/shen-hua/p/8052459.html
Copyright © 2020-2023  润新知