RecyclerView使用攻略(助力篇)

小序

实际上RecyclerView已不是什么新颖的话题了,至少对于用过的前辈们而言是这样的,并且大部分人都会觉得这斯很强大,必须上。而对于刚接触的小伙伴们,难免会遇到各种问题,或是因为陌生,又或是因为项目需求(譬如:侧滑出现删除按钮,拖动与上下拉刷新等等)。虽有云“前人种树,后人乘凉”一说,但依然是需要后人来灌溉的,至少这么说是为了铺垫。

显示效果

RecyclerView基本使用

上面图示仅是RecyclerView数据显示的基本使用,包括Adapter、Divider,相信绝大部分人都是在Hongyang前辈的树下乘过凉,由于其还没有考虑到关于RecyclerView的扩展问题,所以当我们在使用的时候难免会遇到以下两个存在的主流问题:

  1. 列表添加头部(底部)视图,也保留相应的分割线;
  2. 瀑布流列表Item的高度差导致的位置混乱,以及添加分割线出错;

解决问题与效果实现

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.widget.LinearLayout;

/**
 * RecyclerView 分割符
 */
public class RecyclerVDivider extends RecyclerView.ItemDecoration {

    private Drawable mDividerDraw;
    private int mLeftSpace, mRightSpace, mTopSpace, mBottomSpace;
    private int mSpaceColor;

    /**
     * @param context src/main/res/values/styles <item name="android:listDivider"/>
     */
    public RecyclerVDivider(Context context) {
        TypedArray a = context.obtainStyledAttributes(new int[]{android.R.attr.listDivider});
        mDividerDraw = a.getDrawable(0);
        a.recycle();
    }

    /**
     * @param drawable src/main/res/drawable
     */
    public RecyclerVDivider(Drawable drawable) {
        mDividerDraw = drawable;
    }

    /**
     * @param leftSpace 左侧间距
     */
    public void setLeftSpace(int leftSpace) {
        mLeftSpace = leftSpace;
    }

    /**
     * @param rightSpace 右侧间距
     */
    public void setRightSpace(int rightSpace) {
        mRightSpace = rightSpace;
    }

    /**
     * @param topSpace 顶部间距
     */
    public void setTopSpace(int topSpace) {
        mTopSpace = topSpace;
    }

    /**
     * @param bottomSpace 底部间距
     */
    public void setBottomSpace(int bottomSpace) {
        mBottomSpace = bottomSpace;
    }

    /**
     * @param spaceColor 分割线间距颜色,避免浮现父容器颜色
     */
    public void setSpaceColor(int spaceColor) {
        mSpaceColor = spaceColor;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (0 != mSpaceColor) c.drawColor(mSpaceColor);
        drawHorizontal(c, parent);
        drawVertical(c, parent);
    }

    private int getSpanCount(RecyclerView parent) {
        int spanCount = -1;
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
        }
        return spanCount;
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getLeft() - params.leftMargin + mLeftSpace;
            final int right = child.getRight() + params.rightMargin
                    + mDividerDraw.getIntrinsicWidth() - mRightSpace;
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDividerDraw.getIntrinsicHeight();
            mDividerDraw.setBounds(left, top, right, bottom);
            mDividerDraw.draw(c);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getTop() - params.topMargin + mTopSpace;
            final int bottom = child.getBottom() + params.bottomMargin - mBottomSpace;
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDividerDraw.getIntrinsicWidth();
            mDividerDraw.setBounds(left, top, right, bottom);
            mDividerDraw.draw(c);
        }
    }

    private boolean isVertical(LayoutManager layoutManager) {
        if (layoutManager instanceof GridLayoutManager) {
            return ((GridLayoutManager) layoutManager).getOrientation() == LinearLayout.VERTICAL;
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager) layoutManager).getOrientation() == LinearLayout.VERTICAL;
        } else if (layoutManager instanceof LinearLayoutManager) {
            return ((LinearLayoutManager) layoutManager).getOrientation() == LinearLayout.VERTICAL;
        }
        return true;
    }

    /**
     * @param adapter RecyclerView.Adapter
     * @return 当前遍历Item所处列数
     */
    private int getSpanIndex(RecyclerView.Adapter adapter) {
        if (adapter instanceof BaseRVAdapter) {
            return ((BaseRVAdapter) adapter).mSpanIndex + 1;
        }
        return 0;
    }

    /**
     * @param adapter   RecyclerView.Adapter
     * @param spanCount 最大可显示列数
     * @return 所需绘制底部分割线的最大行数(除去底部视图以及最后一排数据)
     */
    private int getMaxRaw(RecyclerView.Adapter adapter, int spanCount) {
        int childCount = adapter.getItemCount();
        int maxRawSize = childCount - childCount % spanCount;

        if (adapter instanceof BaseRVAdapter) {
            BaseRVAdapter baseRVAdapter = (BaseRVAdapter) adapter;

            if (null != baseRVAdapter.mFooterViews && null != baseRVAdapter.mHeaderViews) {
                childCount = baseRVAdapter.mList.size();
                maxRawSize = childCount - childCount % spanCount;
                maxRawSize += baseRVAdapter.mHeaderViews.size();
            }
        }
        return maxRawSize;
    }

    /**
     * @param adapter RecyclerView.Adapter
     * @return true_表示当前为数据Item,false_标识当前为头部/底部View
     */
    private boolean getItemState(RecyclerView.Adapter adapter) {
        if (adapter instanceof BaseRVAdapter) {
            return ((BaseRVAdapter) adapter).isMainItem;
        }
        return true;
    }

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        LayoutManager layoutManager = parent.getLayoutManager();
        int spanCount = getSpanCount(parent);
        boolean isLastRaw = isVertical(layoutManager) ?
                itemPosition + 1 >= getMaxRaw(parent.getAdapter(), spanCount) :
                getSpanIndex(parent.getAdapter()) == spanCount;

        boolean isLastColum = isVertical(layoutManager) ?
                spanCount == getSpanIndex(parent.getAdapter()) :
                itemPosition + 1 >= getMaxRaw(parent.getAdapter(), spanCount);

        if (getItemState(parent.getAdapter())) {
            outRect.set(0, 0,
                    isLastColum ? 0 : mDividerDraw.getIntrinsicWidth(),// 如果是最后一列,则不需要绘制右边
                    isLastRaw ? 0 : mDividerDraw.getIntrinsicHeight());// 如果是最后一行,则不需要绘制底部
        }
    }
}

可见分割线的绘制判断改动比较大,且与自定义RecyclerView.Adapter的实现类BaseRVAdapter.class耦合在一起了。但为了解决以上存在的两个问题,这也是没有办法中的办法(表示蓝瘦到香菇)。以上简单的判断处理相信大家是能看懂,主要还是对这些判断的数据来源比较感冒些。

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * RecyclerView 适配器
 */
public abstract class BaseRVAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {

    public static final int TYPE_NORMAL = 0;
    public static final int TYPE_HEADER = 1;
    public static final int TYPE_FOOTER = -1;

    public Context mContext;
    public List<T> mList = new ArrayList<>();
    public List<View> mHeaderViews = new ArrayList<>();
    public List<View> mFooterViews = new ArrayList<>();
    public boolean isCloseItemAnim;

    public BaseRVAdapter(Context context, @NonNull List<T> list) {
        this.mList = list;
        this.mContext = context;
    }

    public T getData(int position) {
        return mList.get(position);
    }

    public void updateAdapter(@NonNull List<T> list) {
        mList.clear();
        mList.addAll(list);
        notifyDataSetChanged();
        // notifyItemRangeChanged(startIndex(0), mList.size());
    }

    public void updateData(int position, T object) {
        mList.set(position, object);

        if (isCloseItemAnim) {
            notifyDataSetChanged();
        } else {
            int updateIndex = startIndex(position);
            notifyItemChanged(updateIndex);
            notifyItemRangeChanged(updateIndex, mList.size());
        }
    }

    public void cleanData() {
        if (!isCloseItemAnim) {
            mList.clear();
            notifyDataSetChanged();
        } else {
            int cleanIndex = mHeaderViews.size();
            notifyItemRangeRemoved(cleanIndex, mList.size());
            mList.clear();
            notifyItemRangeChanged(cleanIndex, mList.size());
        }
    }

    public void removeData(int position) {
        if (mList.size() <= position) return;

        if (isCloseItemAnim) {
            mList.remove(position);
            notifyDataSetChanged();
        } else {
            notifyItemRemoved(startIndex(position));
            mList.remove(position);
            notifyItemRangeChanged(startIndex(position), mList.size());
        }
    }

    public void addDataLs(final int position, @NonNull List<T> list) {
        mList.addAll(position, list);

        if (isCloseItemAnim) {
            notifyDataSetChanged();
        } else {
            int addIndex = startIndex(position);
            notifyItemRangeInserted(addIndex, list.size());
            notifyItemRangeChanged(addIndex, mList.size());
        }
    }

    public void addDataLs(@NonNull List<T> list) {
        addDataLs(startIndex(mList.size()), list);
    }

    public void addData(int position, T object) {
        int startIndex = startIndex(position);
        mList.add(position, object);

        if (isCloseItemAnim) {
            notifyDataSetChanged();
        } else {
            notifyItemInserted(startIndex);
            notifyItemRangeChanged(startIndex, mList.size());
        }
    }

    public void addData(T object) {
        addData(startIndex(mList.size()), object);
    }

    public void addHeaderViews(@NonNull List<View> headerViews) {
        mHeaderViews.addAll(headerViews);
        notifyDataSetChanged();
    }

    public void addHeaderView(@NonNull View headerView) {
        mHeaderViews.add(headerView);
        notifyDataSetChanged();
    }

    public void addFooterViews(@NonNull List<View> footerViews) {
        mFooterViews.addAll(footerViews);
        notifyDataSetChanged();
    }

    public void addFooterView(@NonNull View footerView) {
        mFooterViews.add(footerView);
        notifyDataSetChanged();
    }

    private int startIndex(int doneIndex) {
        return doneIndex + mHeaderViews.size();
    }

    /**
     * {@link #TYPE_HEADER} 头部列表
     * {@link #TYPE_NORMAL} 正常列表
     * {@link #TYPE_FOOTER} 底部列表
     */
    @Override
    public int getItemViewType(int position) {
        if (!mHeaderViews.isEmpty() && position < mHeaderViews.size()) {
            return TYPE_HEADER + position;
        } else if (!mFooterViews.isEmpty() && position >= mHeaderViews.size() + mList.size()) {
            int beforeSize = mHeaderViews.size() + mList.size();
            return TYPE_FOOTER - (position - beforeSize);
        } else {
            return TYPE_NORMAL;
        }
    }

    @Override
    public int getItemCount() {
        if ((null == mList || mList.isEmpty())) {
            return null == mHeaderViews ? 0 : mHeaderViews.size()
                    + (null == mFooterViews ? 0 : mFooterViews.size());
        } else {
            return mHeaderViews.size() + mList.size() + mFooterViews.size();
        }
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (!mHeaderViews.isEmpty() && viewType > TYPE_NORMAL) {
            return new BaseViewHolder(mHeaderViews.get(viewType - 1));
        } else if (!mFooterViews.isEmpty() && viewType <= TYPE_FOOTER) {
            return new BaseViewHolder(mFooterViews.get(-viewType - 1));
        } else {
            View view = LayoutInflater.from(mContext).inflate(getLayoutId(viewType),
                    parent, false);
            return new BaseViewHolder(view);
        }
    }

    public abstract int getLayoutId(int viewType);

    @Override
    public void onBindViewHolder(final BaseViewHolder holder, int position) {
        if (getItemViewType(position) != TYPE_NORMAL) return;
        onBind(holder, position - mHeaderViews.size());

        if (null != mOnItemClickListener) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mOnItemClickListener.itemSelect(
                            holder.getAdapterPosition() - mHeaderViews.size());
                }
            });
        }
    }

    public abstract void onBind(BaseViewHolder holder, int position);

    @Override
    public void onViewRecycled(final BaseViewHolder holder) {
        super.onViewRecycled(holder);
    }

    @Override
    public void onAttachedToRecyclerView(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) {
                    return getItemViewType(position) != TYPE_NORMAL ? gridManager.getSpanCount() : 1;
                }
            });
        }
    }

    /**
     * @see RecyclerVDivider
     * <ul>判断是否添加分割符的标识
     * <li>true_添加,false_不添加</li></ul>
     * Title:另外使用分割符的情况下可删除该变量
     */
    public boolean isMainItem;

    /**
     * @see RecyclerVDivider
     * 获取GridLayoutManager/StaggeredGridLayoutManager当前Item项所处的列数(行数)
     * 提示:另外使用分割符的情况下可删除该变量
     */
    public int mSpanIndex;

    @Override
    public void onViewAttachedToWindow(BaseViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

        if (null == lp) return;
        isMainItem = getItemViewType(holder.getLayoutPosition()) == TYPE_NORMAL;

        if (lp instanceof GridLayoutManager.LayoutParams) {
            GridLayoutManager.LayoutParams p = (GridLayoutManager.LayoutParams) lp;
            mSpanIndex = p.getSpanIndex();
        } else if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
            mSpanIndex = p.getSpanIndex();
            p.setFullSpan(!isMainItem);
        }
    }

    private OnItemClickListener mOnItemClickListener;

    public void addItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
    }

    public interface OnItemClickListener {
        void itemSelect(int position);
    }
}

本来还想着直接贴重点相关的代码块就行了,毕竟这些实现都大同小异,再说就这些也上不了排场。但是我左思右想觉得不成,这个都得贴上,这样若是出现其他问题“怪我咯~”。大伙重点看onViewAttachedToWindow()就可以了,因为关于分割线绘制与否的数据判定基本上就是根据这里劫取的。

自定义RecyclerView

除此之外因RecyclerView在使用前操作过多嫌麻烦,于是自定义了一个RecyclerView的实现类,并且已在RecyclerVDivider.class当中做了相应的关联和处理,若不嫌弃可一并拿去。

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;

import com.mrv.R;
import com.mrv.utils.UnitConverter;

/**
 * 重定义RecyclerView使用
 */
public class MyRecyclerView extends RecyclerView {

    public static final int TYPE_LIST = 0;
    public static final int TYPE_GRID = 1;
    public static final int TYPE_STAGGERED_GRID = 2;

    /**
     * <ul>列表类型
     * <li>同 ListView :{@link #TYPE_LIST}(默认)</li>
     * <li>同 GridView :{@link #TYPE_GRID}</li>
     * <li>同 StaggeredGridView :{@link #TYPE_STAGGERED_GRID}</li></ul>
     */
    private int mType;

    public static final int VERTICAL = 1;
    public static final int HORIZONTAL = 0;

    /**
     * <ul>列表类型
     * <li>垂直方向 :{@link #VERTICAL}(默认)</li>
     * <li>水平方向 :{@link #HORIZONTAL}</li></ul>
     */
    private int mOrientation;

    /**
     * ?固定大小,默认固定
     */
    private boolean isFixSize = true;

    private static final int DEFAULT_ROW_NUM = 2;

    /**
     * GRID与STAGGERED_GRID显示列数,默认{@link #DEFAULT_ROW_NUM}列
     */
    private int mSpanCount;

    /**
     * @see RecyclerVDivider
     * 分割线,默认从styles中获取
     */
    private Drawable mDividerDraw;

    /**
     * 分割线左侧边距,默认无边距
     */
    private int mDividerLeftSpace;

    /**
     * 分割线右侧边距,默认无边距
     */
    private int mDividerRightSpace;

    /**
     * 分割线顶部边距,默认无边距
     */
    private int mDividerTopSpace;

    /**
     * 分割线底部边距,默认无边距
     */
    private int mDividerBottomSpace;

    /**
     * 分割线间距颜色(即背景色),默认无
     */
    private int mDividerSpaceColor;

    public MyRecyclerView(Context context) {
        super(context);
        initView();
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.recycle_view);
        isFixSize = typedArray.getBoolean(R.styleable.recycle_view_fixSize, true);
        mSpanCount = typedArray.getInteger(R.styleable.recycle_view_spanCount, DEFAULT_ROW_NUM);
        mType = typedArray.getInt(R.styleable.recycle_view_type, TYPE_LIST);
        mOrientation = typedArray.getInt(R.styleable.recycle_view_orientation, VERTICAL);
        mDividerDraw = typedArray.getDrawable(R.styleable.recycle_view_divider);
        mDividerLeftSpace = typedArray.getInt(R.styleable.recycle_view_dividerLeftSpace, 0);
        mDividerRightSpace = typedArray.getInt(R.styleable.recycle_view_dividerRightSpace, 0);
        mDividerTopSpace = typedArray.getInt(R.styleable.recycle_view_dividerTopSpace, 0);
        mDividerBottomSpace = typedArray.getInt(R.styleable.recycle_view_dividerBottomSpace, 0);
        mDividerSpaceColor = typedArray.getColor(R.styleable.recycle_view_dividerSpaceColor, 0);
        typedArray.recycle();
        initView();
    }

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

    private void initView() {
        // 布局样式
        switch (mType) {
            case TYPE_LIST:
                LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
                        getContext(), mOrientation, false);
                this.setLayoutManager(linearLayoutManager);
                break;
            case TYPE_GRID:
                GridLayoutManager gridLayoutManager = new GridLayoutManager(
                        getContext(), mSpanCount, mOrientation, false);
                this.setLayoutManager(gridLayoutManager);
                break;
            case TYPE_STAGGERED_GRID:
                StaggeredGridLayoutManager staggeredGridLayoutManager =
                        new StaggeredGridLayoutManager(mSpanCount, mOrientation);
                this.setLayoutManager(staggeredGridLayoutManager);
                break;
        }

        // 分割线
        if (null != mDividerDraw) {
            RecyclerVDivider divider = new RecyclerVDivider(mDividerDraw);
            divider.setLeftSpace(UnitConverter.dip2px(getContext(), mDividerLeftSpace));
            divider.setRightSpace(UnitConverter.dip2px(getContext(), mDividerRightSpace));
            divider.setTopSpace(UnitConverter.dip2px(getContext(), mDividerTopSpace));
            divider.setBottomSpace(UnitConverter.dip2px(getContext(), mDividerBottomSpace));
            if (0 != mDividerSpaceColor) divider.setSpaceColor(mDividerSpaceColor);

            this.addItemDecoration(divider);
        }

        // 大小固定
        this.setHasFixedSize(isFixSize);
        // Item出入动画
        this.setItemAnimator(new DefaultItemAnimator());
    }

    public int getSpanCount() {
        return mSpanCount;
    }

    public int getOrientation() {
        return mOrientation;
    }

    public int getType() {
        return mType;
    }
}

下面是关于自定义属性的内容与对应实现功能,其中需要注意的是如果启动了分割符的边距设置,这需要相应的设置一下分割符的颜色,从而避免产生的边距露出Item的父容器背景颜色

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="recycle_view">
        <attr name="fixSize" format="boolean" />
        <attr name="spanCount" format="integer" />
        <attr name="divider" format="reference"/>
        <attr name="dividerLeftSpace" format="integer" />
        <attr name="dividerRightSpace" format="integer" />
        <attr name="dividerTopSpace" format="integer" />
        <attr name="dividerBottomSpace" format="integer" />
        <attr name="dividerSpaceColor" format="color" />
        <attr name="type">
            <flag name="list" value="0" />
            <flag name="grid" value="1" />
            <flag name="staggeredGrid" value="2" />
        </attr>
        <attr name="orientation">
            <flag name="vertical" value="1" />
            <flag name="horizontal" value="0" />
        </attr>
    </declare-styleable>
</resources>
Name Format Function
fixSize boolean 设置true提升性能,默认true
spanCount integer 网格以及流布局显示最大列数,默认2
divider reference 分割符资源Id,默认没有分割符
dividerLeftSpace integer 分隔符左边距,默认0,单位dp
dividerRightSpace integer 分隔符左边距,默认0,单位dp
dividerTopSpace integer 分隔符顶部边距,默认0,单位dp
dividerBottomSpace integer 分隔符底部边距,默认0,单位dp
dividerSpaceColor color 分隔符底部颜色,默认无
dividerSpaceColor color 分隔符底部颜色,默认无
type flag list:列表;grid:网格;staggeredGrid:瀑布流
orientation flag vertical:垂直方向延伸; horizontal:水平方向延伸

结尾助力

以上为本篇重点简述内容,剩下的讲讲何为”助力篇“的实际意义。一方面,个人觉得为RecyclerView添加分割符的形式有些毛躁,因为判断太多直接影响性能。另一方面是自己能力尚浅,没有办法将其优化好,包括逻辑处理以及动画效果的延伸。

还可以的RecyclerView

如果你觉得上面的gif演示已经比较nice了,那么再请你看看下面的另一个gif演示:

糟透了的RecyclerView

看完上面两者的比较以后不难发现存在的问题了,当然如果用回notifyDataSetChanged();是可以尽量避免这些问题的。而这里不仅是为了将存在的问题表露出来,更多的是为了能让有实力的小伙伴们帮忙解决问题,或者提供一些已有的解决方案。以下为浮现的问题:

  1. Item进出动画卡顿
  2. 分割线配合动画使用绘制无刷新(可能页面显示无更新,又或者只是绘制逻辑上出现的错误)
  3. 有些情况下使用动画会有闪屏的现象,体验较差。如上面gif从底部scrollToPosition()到顶部时候出现闪屏

Fork助力项目地址:https://github.com/gzejia/URecyclerView

分隔符使用参考:Android RecyclerView 使用完全解析 体验艺术般的控件

添加头部/底部视图参考:RecyclerView添加Header的正确方式

发布了50 篇原创文章 · 获赞 4 · 访问量 8万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章