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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章