RecyclerView问题汇总
2019-05-10 10:18 阅读(494)

目录介绍

系列博客

25.0.0.0 请说一下RecyclerView?adapter的作用是什么,几个方法是做什么用的?如何理解adapter订阅者模式?

public VH onCreateViewHolder(ViewGroup parent, int viewType)
创建Item视图,并返回相应的ViewHolder
public void onBindViewHolder(VH holder, int position)
绑定数据到正确的Item视图上。
public int getItemCount()
返回该Adapter所持有的Itme数量
public int getItemViewType(int position)
用来获取当前项Item(position参数)是哪种类型的布局

如何理解adapter订阅者模式

public final void notifyDataSetChanged() {
    mObservable.notifyChanged();
}

b.接着查看.notifyChanged();源码

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    public boolean hasObservers() {
        return !mObservers.isEmpty();
    }

    public void notifyChanged() {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }

    public void notifyItemRangeChanged(int positionStart, int itemCount) {
        notifyItemRangeChanged(positionStart, itemCount, null);
    }

    public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
        }
    }

    public void notifyItemRangeInserted(int positionStart, int itemCount) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
        }
    }
}
public static abstract class AdapterDataObserver {
    public void onChanged() {
        // Do nothing
    }

    public void onItemRangeChanged(int positionStart, int itemCount) {
        // do nothing
    }

    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        onItemRangeChanged(positionStart, itemCount);
    }
}

c.接着查看setAdapter()源码中的setAdapterInternal(adapter, false, true)方法

public void setAdapter(Adapter adapter) {
    // bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    requestLayout();
}
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
        boolean removeAndRecycleViews) {
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        removeAndRecycleViews();
    }
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {
        //注册一个观察者RecyclerViewDataObserver
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
    markKnownViewsInvalid();
}

25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder的复用?什么时候停止调用onCreateViewHolder?

public final VH createViewHolder(ViewGroup parent, int viewType) {
    TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
    final VH holder = onCreateViewHolder(parent, viewType);
    holder.mItemViewType = viewType;
    TraceCompat.endSection();
    return holder;
}

对于ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,可以查看

public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
    //代码省略了,有需要的小伙伴可以自己看看,这里面逻辑实在太复杂呢
}

25.0.0.2 ViewHolder封装如何对findViewById优化?ViewHolder中为何使用SparseArray替代HashMap存储viewId?

class MyViewHolder extends RecyclerView.ViewHolder {

    private SparseArray<View> viewSparseArray;
    private TextView tvTitle;

    MyViewHolder(final View itemView) {
        super(itemView);
        if(viewSparseArray==null){
            viewSparseArray = new SparseArray<>();
        }
        tvTitle = (TextView) viewSparseArray.get(R.id.tv_title);
        if (tvTitle == null) {
            tvTitle = itemView.findViewById(R.id.tv_title);
            viewSparseArray.put(R.id.tv_title, tvTitle);
        }
    }
}

25.0.0.3 LayoutManager作用是什么?LayoutManager样式有哪些?setLayoutManager源码里做了什么?

public void setLayoutManager(LayoutManager layout) {
    if (layout == mLayout) {
        return;
    }
    // 停止滑动
    stopScroll();
    if (mLayout != null) {
        // 如果有动画,则停止所有的动画
        if (mItemAnimator != null) {
            mItemAnimator.endAnimations();
        }
        // 移除并回收视图
        mLayout.removeAndRecycleAllViews(mRecycler);
        // 回收废弃视图
        mLayout.removeAndRecycleScrapInt(mRecycler);
        //清除mRecycler
        mRecycler.clear();
        if (mIsAttached) {
            mLayout.dispatchDetachedFromWindow(this, mRecycler);
        }
        mLayout.setRecyclerView(null);
        mLayout = null;
    } else {
        mRecycler.clear();
    }
    mChildHelper.removeAllViewsUnfiltered();
    mLayout = layout;
    if (layout != null) {
        if (layout.mRecyclerView != null) {
            throw new IllegalArgumentException("LayoutManager " + layout +
                    " is already attached to a RecyclerView: " + layout.mRecyclerView);
        }
        mLayout.setRecyclerView(this);
        if (mIsAttached) {
            mLayout.dispatchAttachedToWindow(this);
        }
    }
    //更新新的缓存数据
    mRecycler.updateViewCacheSize();
    //重新请求 View 的测量、布局、绘制
    requestLayout();
}

25.0.0.4 SnapHelper主要是做什么用的?SnapHelper是怎么实现支持RecyclerView的对齐方式?

LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(mRecyclerView);

PagerSnapHelper类分析

PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(mRecyclerView);

25.0.0.5 SpanSizeLookup的作用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup实现原理如何理解?

GridLayoutManager manager = new GridLayoutManager(this, 6);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        SpanModel model = mDataList.get(position);
        if (model.getType() == 1) {
            return 6;
        } else if(model.getType() == 2){
            return 3;
        }else if (model.getType() == 3){
            return 2;
        }else if (model.getType() == 4){
            return 2;
        } else {
            return 1;
        }
    }
});

25.0.0.6 ItemDecoration的用途是什么?自定义ItemDecoration有哪些重写方法?分析一下addItemDecoration()源码?

public void onDraw(Canvas c, RecyclerView parent)
装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡
public void onDrawOver(Canvas c, RecyclerView parent)
装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。

分析一下addItemDecoration()源码?

public void addItemDecoration(ItemDecoration decor) {
    addItemDecoration(decor, -1);
}

//主要看这个方法,我的GitHub:https://github.com/yangchong211/YCBlogs
public void addItemDecoration(ItemDecoration decor, int index) {
    if (mLayout != null) {
        mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"
                + " layout");
    }
    if (mItemDecorations.isEmpty()) {
        setWillNotDraw(false);
    }
    if (index < 0) {
        mItemDecorations.add(decor);
    } else {
        // 指定添加分割线在集合中的索引
        mItemDecorations.add(index, decor);
    }
    markItemDecorInsetsDirty();
    // 重新请求 View 的测量、布局、绘制
    requestLayout();
}

25.0.0.7 上拉加载更多的功能是如何做的?添加滚动监听事件需要注意什么问题?网格布局上拉加载如何优化?

@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
    if (manager instanceof GridLayoutManager) {
        final GridLayoutManager gridManager = ((GridLayoutManager) manager);
        gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                // 如果当前是footer的位置,那么该item占据2个单元格,正常情况下占据1个单元格
                return getItemViewType(position) == footType ? gridManager.getSpanCount() : 1;
            }
        });
    }
}

25.0.0.8 RecyclerView绘制原理如何理解?性能优化本质是什么?RecyclerView绘制原理过程大概是怎样的?

RecyclerView
    以LinearLayoutManager为例
    忽略ItemDecoration
    忽略ItemAnimator
    忽略Measure过程
    假设RecyclerView的width和height是确定的
Recycler
    忽略mViewCacheExtension

25.0.0.9 RecyclerView的Recyler是如何实现ViewHolder的缓存?如何理解recyclerView三级缓存是如何实现的?

public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;
    RecycledViewPool mRecyclerPool;
    private ViewCacheExtension mViewCacheExtension;
    static final int DEFAULT_CACHE_SIZE = 2;
}

25.0.1.0 屏幕滑动(状态是item状态可见,不可见,即将可见变化)时三级缓存是如何理解的?adapter中的几个方法是如何变化?

25.0.1.1 SnapHelper有哪些重要的方法,其作用就是是什么?LinearSnapHelper中是如何实现滚动停止的?

@SuppressWarnings("WeakerAccess")
@Nullable
public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager,
        @NonNull View targetView);

findSnapView抽象方法

@SuppressWarnings("WeakerAccess")
@Nullable
public abstract View findSnapView(LayoutManager layoutManager);

findTargetSnapPosition抽象方法

public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,
        int velocityY);

LinearSnapHelper中是如何实现滚动停止的?

@Override
public boolean onFling(int velocityX, int velocityY) {
    LayoutManager layoutManager = mRecyclerView.getLayoutManager();
    if (layoutManager == null) {
        return false;
    }
    RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
    if (adapter == null) {
        return false;
    }
    int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
    return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
            && snapFromFling(layoutManager, velocityX, velocityY);
}

接着看看snapFromFling方法源代码,就是通过该方法实现平滑滚动并使得在滚动停止时itemView对齐到目的坐标位置

private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX,
        int velocityY) {
    if (!(layoutManager instanceof ScrollVectorProvider)) {
        return false;
    }

    RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
    if (smoothScroller == null) {
        return false;
    }

    int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
    if (targetPosition == RecyclerView.NO_POSITION) {
        return false;
    }

    smoothScroller.setTargetPosition(targetPosition);
    layoutManager.startSmoothScroll(smoothScroller);
    return true;
}
@Nullable
protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) {
    if (!(layoutManager instanceof ScrollVectorProvider)) {
        return null;
    }
    return new LinearSmoothScroller(mRecyclerView.getContext()) {
        @Override
        protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
            int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
                    targetView);
            final int dx = snapDistances[0];
            final int dy = snapDistances[1];
            final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
            if (time > 0) {
                action.update(dx, dy, time, mDecelerateInterpolator);
            }
        }

        @Override
        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
            return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
        }
    };
}

25.0.1.2 LinearSnapHelper代码中calculateDistanceToFinalSnap作用是什么?那么out[0]和out[1]分别指什么?

@Override
public int[] calculateDistanceToFinalSnap(
        @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
    int[] out = new int[2];
    if (layoutManager.canScrollHorizontally()) {
        out[0] = distanceToCenter(layoutManager, targetView,
                getHorizontalHelper(layoutManager));
    } else {
        out[0] = 0;
    }

    if (layoutManager.canScrollVertically()) {
        out[1] = distanceToCenter(layoutManager, targetView,
                getVerticalHelper(layoutManager));
    } else {
        out[1] = 0;
    }
    return out;
}

接着看看distanceToCenter方法

private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,
        @NonNull View targetView, OrientationHelper helper) {
    final int childCenter = helper.getDecoratedStart(targetView)
            + (helper.getDecoratedMeasurement(targetView) / 2);
    final int containerCenter;
    if (layoutManager.getClipToPadding()) {
        containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
    } else {
        containerCenter = helper.getEnd() / 2;
    }
    return childCenter - containerCenter;
}

25.0.1.3 如何实现可以设置分割线的颜色,宽度,以及到左右两边的宽度间距的自定义分割线,说一下思路?

//获取当前view的位置信息,该方法主要是设置条目周边的偏移量
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
//在item背后draw
public void onDraw(Canvas c, RecyclerView parent, State state)
//在item上边draw
public void onDrawOver(Canvas c, RecyclerView parent, State state)

25.0.1.4 如何实现复杂type首页需求?如果不封装会出现什么问题和弊端?如何提高代码的简便性和高效性?

public class HomeAdapter extends RecyclerView.Adapter {
    public static final int TYPE_BANNER = 0;
    public static final int TYPE_AD = 1;
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType){
            case TYPE_BANNER:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
            case TYPE_AD:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int type = getItemViewType(position);
        switch (type){
            case TYPE_BANNER:
                // banner 逻辑处理
                break;
            case TYPE_AD:
                // 广告逻辑处理
                break;
            // ... 此处省去N行代码
        }
    }

    @Override
    public int getItemViewType(int position) {
        if(position == 0){
            return TYPE_BANNER;//banner在开头
        }else {
            return mData.get(position).type;//type 的值为TYPE_AD,TYPE_IMAGE,TYPE_AD,等其中一个
        }
    }
    public static class BannerViewHolder extends RecyclerView.ViewHolder{
        public BannerViewHolder(View itemView) {
            super(itemView);
        }
    }
    public static class NewViewHolder extends RecyclerView.ViewHolder{
        public VideoViewHolder(View itemView) {
            super(itemView);
        }
    }
}

25.0.1.5 关于item条目点击事件在onCreateViewHolder中写和在onBindViewHolder中写有何区别?如何优化?

@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    final View view = LayoutInflater.from(mContext).inflate(R.layout.item_me_gv_grid, parent, false);
    final MyViewHolder holder = new MyViewHolder(view);
    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (listener != null) {
                listener.onItemClick(view, holder.getLayoutPosition());
            }
        }
    });
    return holder;
}

2.在onBindViewHolder中写

@Override
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (listener != null) {
                listener.onItemClick(holder.itemView, holder.getAdapterPosition());
            }
        }
    });
}

25.0.1.6 RecyclerView滑动卡顿原因有哪些?如何解决嵌套布局滑动冲突?如何解决RecyclerView实现画廊卡顿?

//RecyclerView.SCROLL_STATE_IDLE //空闲状态
//RecyclerView.SCROLL_STATE_FLING //滚动状态
//RecyclerView.SCROLL_STATE_TOUCH_SCROLL //触摸后状态
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            LoggerUtils.e("initRecyclerView"+ "恢复Glide加载图片");
            Glide.with(ImageBrowseActivity.this).resumeRequests();
        }else {
            LoggerUtils.e("initRecyclerView"+"禁止Glide加载图片");
            Glide.with(ImageBrowseActivity.this).pauseRequests();
        }
    }
});

25.0.1.7 RecyclerView常见的优化有哪些?实际开发中都是怎么做的,优化前后对比性能上有何提升?

25.0.1.8 如何解决RecyclerView嵌套RecyclerView条目自动上滚的Bug?如何解决ScrollView嵌套RecyclerView滑动冲突?

beforeDescendants:viewgroup会优先其子类控件而获取到焦点
afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

如何解决ScrollView嵌套RecyclerView滑动冲突?

public class NoNestedScrollview extends NestedScrollView {
    @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);
    }
}

第二种解决方式博客

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;
    }
});

可能会出现的问题博客

<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>

25.0.1.9 如何处理ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager?如何解决RecyclerView使用Glide加载图片导致图片错乱问题?

@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); 
}

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

//给ImageView打上Tag作为特有标记
imageView.setTag(tag);
 
//下载图片
loadImage();
 
//根据tag判断是不是需要设置给ImageView
if(tag == iamgeView.getTag()) {
    imageView.setBitmapImage(iamge);
}

项目开源地址:github.com/yangchong21…