zoukankan      html  css  js  c++  java
  • 使用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


  • 相关阅读:
    POJ1486 Sorting Slides 二分图or贪心
    POJ2060 Taxi Cab Scheme 最小路径覆盖
    POJ3083 Children of the Candy Corn 解题报告
    以前的文章
    POJ2449 Remmarguts' Date K短路经典题
    这一年的acm路
    POJ3014 Asteroids 最小点覆盖
    POJ2594 Treasure Exploration 最小路径覆盖
    POJ3009 Curling 2.0 解题报告
    POJ2226 Muddy Fields 最小点集覆盖
  • 原文地址:https://www.cnblogs.com/shen-hua/p/8052459.html
Copyright © 2011-2022 走看看