Recyclrview中嵌套横向滑动的Recyclerview滑动冲突问题和滑动卡顿,子recyclerview复用方案

项目中遇到一种需求,竖向滑动的列表中的item要有一种类型是可以左右滑动的横向列表item,我首先想到的就是外面的列表和里面的横向滑动的item都用recyclerview来实现,解决下滑动冲突应该就没问题,顺着思路就开始写代码,先开始两个列表都是直接用的原生的recyclerview,跑起来后竖向滑动很流畅,但横向的recyclerview滑动会不灵敏,从网上找了一种解决方案自定义外面的recyclerview,重写recyclerview的滑动拦截事件,让它只拦截竖向滑动的事件,下面我把自定义的recyclerview的代码贴出来:

外面的列表用到的自定义的总的recyclrview基类

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;


/**
 * Created by wjj on 2018/10/11.
 * 总的recyclrview基类
 */


public class MyRecycleView extends RecyclerView{
    private static final int INVALID_POINTER = -1;
    private int mScrollPointerId = INVALID_POINTER;
    private int mInitialTouchX, mInitialTouchY;
    private int mTouchSlop;
    public MyRecycleView(Context context) {
        this(context, null);
    }

    public MyRecycleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyRecycleView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        final ViewConfiguration vc = ViewConfiguration.get(getContext());
        mTouchSlop = vc.getScaledTouchSlop();
    }

    @Override
    public void setScrollingTouchSlop(int slopConstant) {
        super.setScrollingTouchSlop(slopConstant);
        final ViewConfiguration vc = ViewConfiguration.get(getContext());
        switch (slopConstant) {
            case TOUCH_SLOP_DEFAULT:
                mTouchSlop = vc.getScaledTouchSlop();
                break;
            case TOUCH_SLOP_PAGING:
                mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);
                break;
            default:
                break;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        final int action = MotionEventCompat.getActionMasked(e);
        final int actionIndex = MotionEventCompat.getActionIndex(e);

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
                mInitialTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = (int) (e.getY() + 0.5f);
                return super.onInterceptTouchEvent(e);

            case MotionEventCompat.ACTION_POINTER_DOWN:
                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
                mInitialTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
                mInitialTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
                return super.onInterceptTouchEvent(e);

            case MotionEvent.ACTION_MOVE: {
                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
                if (index < 0) {
                    return false;
                }

                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
                if (getScrollState() != SCROLL_STATE_DRAGGING) {
                    final int dx = x - mInitialTouchX;
                    final int dy = y - mInitialTouchY;
                    final boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally();
                    final boolean canScrollVertically = getLayoutManager().canScrollVertically();
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop && (Math.abs(dx) >= Math.abs(dy) || canScrollVertically)) {
                        startScroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop && (Math.abs(dy) >= Math.abs(dx) || canScrollHorizontally)) {
                        startScroll = true;
                    }
                    return startScroll && super.onInterceptTouchEvent(e);
                }
                return super.onInterceptTouchEvent(e);
            }

            default:
                return super.onInterceptTouchEvent(e);
        }
    }
}

竖向滑动列表用到的自定义的的recyclrview代码,注意这个才是真正用到的外面列表的自定义recyclrview代码


import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;

/**
 * Created by wjj on 2018/10/11.
 *解决recycrview嵌套横向recyclerview后,子的item recyclerview横向滑动不灵敏问题
 */

public class FeedRootRecyclerView extends MyRecycleView{
    public FeedRootRecyclerView(Context context) {
        this(context, null);
    }

    public FeedRootRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FeedRootRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         /* do nothing */
    }
}


然后里面子的item的recyclerview就用原生的就行,不用自定义,这样运行起来后就没有滑动不灵敏的问题了。

 

下面再来记录下我遇到的外面列表滑动卡顿的问题,网上关于recyclerview滑动卡顿有一种方案是给recyclerview 添加OnScrollListener

然后判断列表滑动时停止加载图片,停止滑动后再加载网络图片,这种方案我试了下,效果感觉不是太佳

                // 动态改变adapter中isScrolling的状态值,避免adapter在recyclerview正在滑动时进行网络,广告请求或者图片,视频加载
                //查看源码可知State有三种状态:SCROLL_STATE_IDLE(静止)、SCROLL_STATE_DRAGGING(上升)、SCROLL_STATE_SETTLING(下落)
                // 静止状态 滚动静止时才加载图片资源,极大提升流畅度
//                if (newState==RecyclerView.SCROLL_STATE_IDLE){
//                    adapter?.isStopScrolling = true
//                    adapter?.notifyDataSetChanged()  // notify调用后onBindViewHolder会响应调用
//                }else{
//                    adapter?.isStopScrolling = false
//                }

经过测试发现我这里出现的问题不是网络图片加载和recyclerview嵌套recyclerview滑动冲突造成的,而是竖向recyclerview的item类型多,然后布局比较复杂,特别是子的recyclerview的item,每次滑动复用都会重新创建适配器,特别耗时造成的,,优化的方案我用了两个:

1.简化每个itemview 的布局嵌套和不必要的绘制,对gpu的绘制压力大耗时是造成滑动卡和丢帧的原因,Android里面超过16毫秒眼睛就能明显感到卡顿,我检查我的代码,发现有个自定义的绘制阴影的方案特别耗时,因为每次复用都会计算宽高和绘制阴影

                var sp = ShadowProperty()
                        .setShadowColor(0x40000000)
                        .setShadowDy(DipDpUtil.dip2px(activity, 0.5f))
                        .setShadowRadius(DipDpUtil.dip2px(activity, 3f))
                        .setShadowSide(ShadowProperty.ALL)
                var sd = ShadowViewDrawable(sp, 0x77000000, DipDpUtil.dip2px(activity, 6f).toFloat(), DipDpUtil.dip2px(activity, 6f).toFloat())

                ViewCompat.setBackground(itemHolder.itemV1, sd)
                ViewCompat.setLayerType(itemHolder.itemV1, ViewCompat.LAYER_TYPE_SOFTWARE, null)

我把这个自定义控件代码注释掉后,滑动卡顿就会好转很多,然后得出一个结论,在列表中如果给控件加阴影,绘制会耗时,用图片做阴影滑动会非常流畅,当时ui就是不愿意切图,程序就用了自定义绘制阴影的控件,最后还是换成了用图片绘制阴影的方案。

2.如果子的itemview是recyclerview类型的时候,adapter不能重复的重建,要用好自定义复用策略

我贴下我的代码:

 val linearManager = LinearLayoutManager(actiity)
                holder.recycle_picfour.layoutManager = linearManager
                linearManager.orientation = LinearLayoutManager.HORIZONTAL
                var isVideo = position == 1
                if (holder.recycle_picfour.adapter!=null){  
 (holder.recycle_picfour.adapter as HomeFraChildRecyclerAdapter).wallpaperBeanList = wallpaperBeanList
                    (holder.recycle_picfour.adapter as HomeFraChildRecyclerAdapter).itemWallpaperBean = itemWallpaperBean
                    (holder.recycle_picfour.adapter as HomeFraChildRecyclerAdapter).isVideo = isVideo         
                    (holder.recycle_picfour.adapter as HomeFraChildRecyclerAdapter).notifyDataSetChanged()
                }else{
                    var adapter = HomeFraChildRecyclerAdapter(actiity, isVideo, itemWallpaperBean)
                    holder.recycle_picfour.adapter = adapter
                }





                //之前方案,缓存recyclerview和adapter的复用的方案,存在数据会重复显示的问题,已用上面的adapter notifyDataSetChanged方法取代
//                if (recyclerAdapterViewMap[position]!=null){
//                    if (position==1){
//                        var adapter = HomeFraChildRecyclerAdapter(actiity, isVideo, itemWallpaperBean)
//                        holder.recycle_picfour.adapter = adapter
//                        holder.recycle_picfour.setItemViewCacheSize(7)
//                    }else{
////                        holder.recycle_picfour = recyclerViewMap!![position]!!
//                        recyclerAdapterViewMap!![position]!!.itemWallpaperBean = itemWallpaperBean
//                        holder.recycle_picfour.adapter = recyclerAdapterViewMap!![position]!!
//                        recyclerAdapterViewMap!![position]!!.notifyDataSetChanged()
//                    }
//                }else{
//                    holder.recycle_picfour.setItemViewCacheSize(7)
//                    if (holder.recycle_picfour.adapter!=null){
//                        recyclerAdapterViewMap!![position]!!.itemWallpaperBean = itemWallpaperBean
//                        holder.recycle_picfour.adapter = recyclerAdapterViewMap!![position]!!
//                        recyclerAdapterViewMap!![position]!!.notifyDataSetChanged()
////                        Toast.makeText(actiity,"holder.recycle_picfour.adapter!=null",Toast.LENGTH_SHORT).show()
//                    }else{
//                        var adapter = HomeFraChildRecyclerAdapter(actiity, isVideo, itemWallpaperBean)
//                        holder.recycle_picfour.adapter = adapter
//////                        Toast.makeText(actiity,"holder.recycle_picfour.adapter是新建的",Toast.LENGTH_SHORT).show()
//                        recyclerAdapterViewMap.put(position,adapter)
//                    }
////                    recyclerViewMap.put(position,holder.recycle_picfour)
//
//                }





这里的代码里面我注释掉的代码是我找到正确方法前尝试的方案,也记录下来作为怕坑的经验记录,便于后续温习,然后没有注释的代码是正确的复用方案,需要注意的是adapter里面的数据一定要动态手动赋值然后调用notifyDataSetChanged才会真正刷新recyclerview的itemview,否则会出现数据显示错乱的现象,这样无论竖向还是横向的recyclerview滑动就都会非常流畅了,我当时用了手机设置里面开发者选项中的gpu过度绘制呈现,蓝色的柱状图过高,超过绿色的线太多就说明滑动比较卡顿,柱状图越高越说明那一帧的绘制时间比较长。

 

你可能感兴趣的:(android)