Skip to content

Latest commit

 

History

History
335 lines (263 loc) · 13.2 KB

wiki4.md

File metadata and controls

335 lines (263 loc) · 13.2 KB

目录介绍

  • 01.如何判断RecyclerView控件滑动到顶部和底部
  • 02.RecyclerView嵌套RecyclerView 条目自动上滚的Bug
  • 03.ScrollView嵌套RecyclerView滑动冲突
  • 04.ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager
  • 05.RecyclerView嵌套RecyclerView的滑动冲突问题
  • 06.RecyclerView使用Glide加载图片导致图片错乱问题解决

01.如何判断RecyclerView控件滑动到顶部和底部

  • 有一种使用场景,购物商城的购物车页面,当RecyclerView滑动到顶部时,让刷新控件消费事件;当RecyclerView滑动到底部时,让下一页控件[猜你喜欢]消费事件。
    • 代码如下所示:
    public class VerticalRecyclerView extends RecyclerView {
    
        private float downX;
        private float downY;
        /** 第一个可见的item的位置 */
        private int firstVisibleItemPosition;
        /** 第一个的位置 */
        private int[] firstPositions;
        /** 最后一个可见的item的位置 */
        private int lastVisibleItemPosition;
        /** 最后一个的位置 */
        private int[] lastPositions;
        private boolean isTop;
        private boolean isBottom;
    
        public VerticalRecyclerView(Context context) {
            this(context, null);
        }
    
        public VerticalRecyclerView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public VerticalRecyclerView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            LayoutManager layoutManager = getLayoutManager();
            if (layoutManager != null) {
                if (layoutManager instanceof GridLayoutManager) {
                    lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
                    firstVisibleItemPosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
                } else if (layoutManager instanceof LinearLayoutManager) {
                    lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
                    firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
                } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                    StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                    if (lastPositions == null) {
                        lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
                        firstPositions = new int[staggeredGridLayoutManager.getSpanCount()];
                    }
                    staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
                    staggeredGridLayoutManager.findFirstVisibleItemPositions(firstPositions);
                    lastVisibleItemPosition = findMax(lastPositions);
                    firstVisibleItemPosition = findMin(firstPositions);
                }
            } else {
                throw new RuntimeException("Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager");
            }
    
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = ev.getX();
                    downY = ev.getY();
                    //如果滑动到了最底部,就允许继续向上滑动加载下一页,否者不允许
                    getParent().requestDisallowInterceptTouchEvent(true);
                    break;
                case MotionEvent.ACTION_MOVE:
                    float dx = ev.getX() - downX;
                    float dy = ev.getY() - downY;
                    boolean allowParentTouchEvent;
                    if (Math.abs(dy) > Math.abs(dx)) {
                        if (dy > 0) {
                            //位于顶部时下拉,让父View消费事件
                            allowParentTouchEvent = isTop = firstVisibleItemPosition == 0 && getChildAt(0).getTop() >= 0;
                        } else {
                            //位于底部时上拉,让父View消费事件
                            int visibleItemCount = layoutManager.getChildCount();
                            int totalItemCount = layoutManager.getItemCount();
                            allowParentTouchEvent = isBottom = visibleItemCount > 0 && (lastVisibleItemPosition) >= totalItemCount - 1 && getChildAt(getChildCount() - 1).getBottom() <= getHeight();
                        }
                    } else {
                        //水平方向滑动
                        allowParentTouchEvent = true;
                    }
                    getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent);
            }
            return super.dispatchTouchEvent(ev);
    
        }
    
        private int findMax(int[] lastPositions) {
            int max = lastPositions[0];
            for (int value : lastPositions) {
                if (value >= max) {
                    max = value;
                }
            }
            return max;
        }
    
        private int findMin(int[] firstPositions) {
            int min = firstPositions[0];
            for (int value : firstPositions) {
                if (value < min) {
                    min = value;
                }
            }
            return min;
        }
    
        public boolean isTop() {
            return isTop;
        }
    
        public boolean isBottom() {
            return isBottom;
        }
    }
    

02.RecyclerView嵌套RecyclerView 条目自动上滚的Bug

  • RecyclerViewA嵌套RecyclerViewB 进入页面自动跳转到RecyclerViewB上面页面会自动滚动。
    • 两种解决办法
    • 一,recyclerview去除焦点
      • recyclerview.setFocusableInTouchMode(false);
      • recyclerview.requestFocus();
    • 二,在代码里面 让处于ScrollView或者RecyclerView1 顶端的某个控件获得焦点即可
      • 比如顶部的一个textview
      • tv.setFocusableInTouchMode(true);
      • tv.requestFocus();

03.ScrollView嵌套RecyclerView滑动冲突

  • 第一种方式:
    • 重写父控件,让父控件 ScrollView 直接拦截滑动事件,不向下分发给 RecyclerView,具体是定义一个ScrollView子类,重写其 onInterceptTouchEvent()方法
    public class NoNestedScrollview extends NestedScrollView {
    
        private int downX;
        private int downY;
        private int mTouchSlop;
    
        public NoNestedScrollview(Context context) {
            super(context);
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }
    
        public NoNestedScrollview(Context context, AttributeSet attrs) {
            super(context, attrs);
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }
    
        public NoNestedScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent e) {
            int action = e.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    downX = (int) e.getRawX();
                    downY = (int) e.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    //判断是否滑动,若滑动就拦截事件
                    int moveY = (int) e.getRawY();
                    if (Math.abs(moveY - downY) > mTouchSlop) {
                        return true;
                    }
                    break;
                default:
                    break;
            }
            return super.onInterceptTouchEvent(e);
        }
    }
    
  • 第二种解决方式
    • a.禁止RecyclerView滑动
    recyclerView.setLayoutManager(new GridLayoutManager(mContext,2){
        @Override
        public boolean canScrollVertically() {
            return false;
        }
    
        @Override
        public boolean canScrollHorizontally() {
            return super.canScrollHorizontally();
        }
    });
    
    recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayout.VERTICAL,false){
        @Override
        public boolean canScrollVertically() {
            return false;
        }
    });
    
    • b.重写LayoutManager
      • 代码设置LayoutManager.setScrollEnabled(false);
    public class ScrollLayoutManager extends LinearLayoutManager {
    
        private boolean isScrollEnable = true;
    
        public ScrollLayoutManager(Context context) {
            super(context);
        }
    
        public ScrollLayoutManager(Context context, int orientation, boolean reverseLayout) {
            super(context, orientation, reverseLayout);
        }
    
        public ScrollLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        public boolean canScrollVertically() {
            return isScrollEnable && super.canScrollVertically();
        }
    
        /**
         * 设置 RecyclerView 是否可以垂直滑动
         * @param isEnable
         */
        public void setScrollEnable(boolean isEnable) {
            this.isScrollEnable = isEnable;
        }
    }
    
  • 可能会出现的问题
    • 虽然上面两种方式解决了滑动冲突,但是有的手机上出现了RecyclerView会出现显示不全的情况。
    • 针对这种情形,使用网上的方法一种是使用 RelativeLayout 包裹 RecyclerView 并设置属性:android:descendantFocusability="blocksDescendants"
      • android:descendantFocusability="blocksDescendants",该属>性是当一个view 获取焦点时,定义 ViewGroup 和其子控件直接的关系,常用来>解决父控件的焦点或者点击事件被子空间获取。
      • beforeDescendants: ViewGroup会优先其子控件获取焦点
      • afterDescendants: ViewGroup只有当其子控件不需要获取焦点时才获取焦点
      • blocksDescendants: ViewGroup会覆盖子类控件而直接获得焦点
    • 相关代码案例:https://github.com/yangchong211/LifeHelper
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:descendantFocusability="blocksDescendants">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_hot_review"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:foregroundGravity="center" />
    </RelativeLayout>
    

04.ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager

  • 继承RecyclerView,重写dispatchTouchEvent,根据ACTION_MOVE的方向判断是否调用getParent().requestDisallowInterceptTouchEvent去阻止父view拦截点击事件
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        /*---解决垂ViewPager嵌套直RecyclerView嵌套水平RecyclerView横向滑动到底后不滑动ViewPager start ---*/
        ViewParent parent = this;
        while(!((parent = parent.getParent()) instanceof ViewPager));
        // 循环查找viewPager
        parent.requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }
    

05.RecyclerView嵌套RecyclerView的滑动冲突问题

06.RecyclerView使用Glide加载图片导致图片错乱问题解决

  • 为何会导致图片加载后出现错乱效果

    • 因为有ViewHolder的重用机制,每一个item在移除屏幕后都会被重新使用以节省资源,避免滑动卡顿。而在图片的异步加载过程中,从发出网络请求到完全下载并加载成Bitmap的图片需要花费很长时间,而这时候很有可能原先需要加载图片的item已经划出界面并被重用了。而原先下载的图片在被加载进ImageView的时候没有判断当前的ImageView是不是原先那个要求加载的,故可能图片被加载到被重用的item上,就产生了图片错位的问题。解决思路也很简单,就是在下载完图片,准备给ImageView装上的时候检查一下这个ImageView。
  • 第一种方法

    • 使用settag()方式,这种方式还是比较好的,但是,需要注意的是,Glide图片加载也是使用将这个方法的,所以当你在Bindviewholder()使用时会直接抛异常,你需要使用settag(key,value)方式进行设置,这种方式是不错的一种解决方式,注意取值的时候应该是gettag(key)这个方法哈,当异步请求回来的时候对比下tag是否一样在判断是否显示图片。这边直接复制博主的代码了。
    //给ImageView打上Tag作为特有标记
    imageView.setTag(tag);
    
    //下载图片
    loadImage();
    
    //根据tag判断是不是需要设置给ImageView
    if(tag == iamgeView.getTag()) {
        imageView.setBitmapImage(iamge);
    }
    
  • 第二种办法

  • 参考博客