Android RecyclerView 的簡便寫法

RecyclerView 現(xiàn)在可以說是很常用了吧?RecyclerView 自然是很方便的控件朗若,但用多了有時(shí)候?qū)σ恍┲貜?fù)性代碼也是感覺挺麻煩的创肥,于是乎我就將一些重復(fù)性代碼封裝了起來矿酵,從而使 RecyclerView 的使用更加的簡便

本篇博客包含的內(nèi)容有:

  • 通用的單布局 RecyclerView.Adapter
  • 通用的多布局 RecyclerView.Adapter
  • 通用的 RecyclerView.ItemDecoration
  • RecyclerView 的單擊和長按事件監(jiān)聽
  • 帶頭部與底部View的 RecyclerView
  • SnapHelper 的使用

一浪箭、通用 RecyclerView.Adapter

CommonRecyclerViewAdapter 是一個(gè)抽象類,利用泛型構(gòu)造了一個(gè)通用的Adapter下梢,并通過 MultiTypeSupport 接口來實(shí)現(xiàn)對多布局的支持客蹋。
此外,有時(shí)候我們在刷新數(shù)據(jù)時(shí)孽江,改變的數(shù)據(jù)可能只是List集合中的單個(gè)數(shù)據(jù)讶坯,如果都采用 Adapter.notifyDataSetChanged() 來刷新整個(gè)視圖,無疑是浪費(fèi)資源的岗屏,此處采用了 DiffUtil 來對比前后兩個(gè)數(shù)據(jù)集辆琅,尋找出舊數(shù)據(jù)集與新數(shù)據(jù)集的最小變化量,從而對數(shù)據(jù)進(jìn)行定向刷新这刷,可以只刷新相應(yīng)的Item婉烟,使得視圖的刷新過程更為高效,且增添和刪除數(shù)據(jù)時(shí)都伴隨有相應(yīng)的動(dòng)畫效果

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 21:50
 * 說明:通用RecyclerView Adapter
 */
public abstract class CommonRecyclerViewAdapter<T> extends RecyclerView.Adapter<CommonRecyclerViewHolder> {

    //省略一些代碼
    ......

    private DiffUtil.Callback callback = new DiffUtil.Callback() {

        @Override
        public int getOldListSize() {
            return getItemCount();
        }

        @Override
        public int getNewListSize() {
            return newDataList.size();
        }

        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            return CommonRecyclerViewAdapter.this.areItemsTheSame(oldItemPosition, newItemPosition);
        }

        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            return CommonRecyclerViewAdapter.this.areContentsTheSame(oldItemPosition, newItemPosition);
        }

        @Nullable
        @Override
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return CommonRecyclerViewAdapter.this.getChangePayload(oldItemPosition, newItemPosition);
        }
    };
    
    public void setData(final List<T> dataList) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                newDataList.clear();
                newDataList = CommonRecyclerViewAdapter.this.clone(dataList);
                DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback, true);
                Message message = new Message();
                message.what = DIFF_UTIL_UPDATE;
                message.obj = diffResult;
                handler.sendMessage(message);
            }
        }).start();
    }

    @Override
    public int getItemViewType(int position) {
        if (multiTypeSupport != null) {
            return multiTypeSupport.getLayoutId(dataList.get(position), position);
        }
        return DEFAULT_ITEM_VIEW_TYPE;
    }

    @Override
    public CommonRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (multiTypeSupport != null) {
            layoutId = viewType;
        }
        return new CommonRecyclerViewHolder(layoutInflater.inflate(layoutId, parent, false));
    }

    @Override
    public void onBindViewHolder(CommonRecyclerViewHolder holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            partialBindData(holder, bundle);
        }
    }

    @Override
    public void onBindViewHolder(CommonRecyclerViewHolder holder, int position) {
        entirelyBindData(holder, dataList.get(position));
        if (clickListener != null) {
            holder.setClickListener(clickListener);
        }
        if (longClickListener != null) {
            holder.setLongClickListener(longClickListener);
        }
    }

    //省略一些代碼
    ......

    private List<T> clone(List<T> dataList) {
        List<T> tempDataList = new ArrayList<>(dataList.size());
        for (T data : dataList) {
            tempDataList.add(clone(data));
        }
        return tempDataList;
    }

    /**
     * clone 指定對象暇屋,以此獲得對象副本
     *
     * @param data 要復(fù)制的對象
     * @return 對象副本
     */
    protected abstract T clone(T data);

    /**
     * 判斷數(shù)據(jù)列表刷新前后指定索引的位置是否指向同一條數(shù)據(jù)
     * 此處只對比兩者是否指向同一條數(shù)據(jù)似袁,而不關(guān)心其數(shù)據(jù)內(nèi)容是否有變化
     *
     * @param oldItemPosition 更新前的數(shù)據(jù)索引
     * @param newItemPosition 更新后的數(shù)據(jù)索引
     * @return 是否指向同一條數(shù)據(jù)
     */
    protected abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);

    /**
     * 此處來判斷指向同一條數(shù)據(jù)的前后兩個(gè)索引位置,其數(shù)據(jù)內(nèi)容是否相同
     * 只在 areItemsTheSame 返回 true 時(shí)才會(huì)調(diào)用本方法
     *
     * @param oldItemPosition 更新前的數(shù)據(jù)索引
     * @param newItemPosition 更新后的數(shù)據(jù)索引
     * @return 數(shù)據(jù)內(nèi)容是否有變化
     */
    protected abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);

    /**
     * 獲取同條數(shù)據(jù)在刷新前后是哪些數(shù)據(jù)內(nèi)容發(fā)生了變化
     * 只在 areContentsTheSame 返回 false 時(shí)才會(huì)調(diào)用本方法
     *
     * @param oldItemPosition 更新前的數(shù)據(jù)索引
     * @param newItemPosition 更新后的數(shù)據(jù)索引
     * @return 數(shù)據(jù)變化內(nèi)容
     */
    @NonNull
    protected abstract Bundle getChangePayload(int oldItemPosition, int newItemPosition);

    /**
     * 對刷新前后的數(shù)據(jù)進(jìn)行定向更新咐刨,即只更新數(shù)據(jù)發(fā)生了變化的View
     *
     * @param holder Holder
     * @param bundle getChangePayload 方法的返回值
     */
    protected abstract void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle);

    /**
     * 對數(shù)據(jù)進(jìn)行完全綁定
     *
     * @param holder Holder
     * @param data   Data
     */
    protected abstract void entirelyBindData(CommonRecyclerViewHolder holder, T data);

}

此處還需要使用到一個(gè)通用的 RecyclerView.ViewHolder

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 21:52
 * 說明:通用RecyclerView ViewHolder
 */
public class CommonRecyclerViewHolder extends RecyclerView.ViewHolder {

    public interface OnClickListener {
        void onClick(int position);
    }

    public interface OnLongClickListener {
        void onLongClick(int position);
    }

    private OnClickListener clickListener;

    private OnLongClickListener longClickListener;

    //用來存放View以減少findViewById的次數(shù)
    private SparseArray<View> viewSparseArray;

    CommonRecyclerViewHolder(View view) {
        super(view);
        viewSparseArray = new SparseArray<>();
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (clickListener != null) {
                    clickListener.onClick(getAdapterPosition());
                }
            }
        });
        view.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (longClickListener != null) {
                    longClickListener.onLongClick(getAdapterPosition());
                }
                return true;
            }
        });
    }

    void setClickListener(OnClickListener clickListener) {
        this.clickListener = clickListener;
    }

    void setLongClickListener(OnLongClickListener longClickListener) {
        this.longClickListener = longClickListener;
    }

    /**
     * 根據(jù) ID 來獲取 View
     *
     * @param viewId viewID
     * @param <T>    泛型
     * @return 將結(jié)果強(qiáng)轉(zhuǎn)為 View 或 View 的子類型
     */
    private <T extends View> T getView(@IdRes int viewId) {
        // 先從緩存中找昙衅,找到的話則直接返回
        // 如果找不到則findViewById,再把結(jié)果存入緩存中
        View view = viewSparseArray.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            if (view != null) {
                viewSparseArray.put(viewId, view);
            }
        }
        return (T) view;
    }

    public CommonRecyclerViewHolder setText(@IdRes int viewId, CharSequence text) {
        TextView textView = getView(viewId);
        if (textView != null) {
            textView.setText(text);
        }
        return this;
    }

    //省略一些代碼
    ......
    
}

二所宰、通用單布局 Adapter 使用示例

先來新建一個(gè)Model

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 21:55
 * 說明:
 */
public class New {

    private int index;

    private String title;

    private String content;

    public New(int index, String title, String content) {
        this.index = index;
        this.title = title;
        this.content = content;
    }

    //省略一些代碼
    ......

}

每個(gè)子項(xiàng)的布局包含一個(gè)索引TextView绒尊、一個(gè)標(biāo)題TextView、一個(gè)內(nèi)容TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#439af1"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_index"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="6dp"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:padding="5dp"
        android:textSize="28sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="start"
        android:padding="10dp"
        android:textSize="22sp" />

</LinearLayout>

之后就是繼承 CommonRecyclerViewAdapter 仔粥,泛型指定為 New ,在構(gòu)造函數(shù)里直接指定要使用的布局為 R.layout.item_new蟹但,再實(shí)現(xiàn)幾個(gè)抽象方法即可躯泰。

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 21:55
 * 說明:單個(gè)布局
 */
public class NewCommonAdapter extends CommonRecyclerViewAdapter<New> {

    public NewCommonAdapter(Context context, List<New> dataList) {
        super(context, dataList, R.layout.item_new);
    }

    @Override
    protected New clone(New data) {
        return new New(data.getIndex(), data.getTitle(), data.getContent());
    }

    @Override
    protected boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return dataList.get(oldItemPosition).getIndex() == newDataList.get(newItemPosition).getIndex();
    }

    @Override
    protected boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        String title = dataList.get(oldItemPosition).getTitle();
        String content = dataList.get(oldItemPosition).getContent();
        String newTitle = newDataList.get(newItemPosition).getTitle();
        String newContent = newDataList.get(newItemPosition).getContent();
        return title.equals(newTitle) && content.equals(newContent);
    }

    @NonNull
    @Override
    protected Bundle getChangePayload(int oldItemPosition, int newItemPosition) {
        Bundle bundle = new Bundle();
        String title = dataList.get(oldItemPosition).getTitle();
        String content = dataList.get(oldItemPosition).getContent();
        String newTitle = newDataList.get(newItemPosition).getTitle();
        String newContent = newDataList.get(newItemPosition).getContent();
        if (!title.equals(newTitle)) {
            bundle.putString("Title", newTitle);
        }
        if (!content.equals(newContent)) {
            bundle.putString("Content", newContent);
        }
        return bundle;
    }

    @Override
    protected void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle) {
        for (String key : bundle.keySet()) {
            switch (key) {
                case "Title":
                    holder.setText(R.id.tv_title, bundle.getString(key));
                    break;
                case "Content":
                    holder.setText(R.id.tv_content, bundle.getString(key));
                    break;
            }
        }
    }

    @Override
    protected void entirelyBindData(CommonRecyclerViewHolder holder, New data) {
        holder.setText(R.id.tv_title, data.getTitle())
                .setText(R.id.tv_content, data.getContent());
    }

}

這里省略 Activity 中對 RecyclerView 的各種初始化操作,只展現(xiàn)最終效果华糖,具體代碼看最下方 GitHub 地址

單布局.gif

三麦向、通用多布局 Adapter 使用示例

在 CommonRecyclerViewAdapter 類當(dāng)中,為了支持多布局客叉,通過 MultiTypeSupport 接口來返回相應(yīng)的布局文件ID诵竭,這里除了使用 單布局 中使用的 R.layout.item_new 布局文件外话告,同時(shí)使用另外一個(gè)布局文件 R.layout.item_new_multi ,僅僅是背景色不同而已

想要讓子 Adapter 支持多布局卵慰,只要實(shí)現(xiàn) CommonRecyclerViewAdapter 的另外一個(gè)構(gòu)造函數(shù)即可沙郭。這里在索引為單數(shù)時(shí)使用 R.layout.item_new 布局,雙數(shù)時(shí)使用 R.layout.item_new_multi 布局

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 21:56
 * 說明:多布局
 */
public class NewCommonMultiAdapter extends CommonRecyclerViewAdapter<New> {

    public NewCommonMultiAdapter(Context context, List<New> dataList) {
        super(context, dataList, new CommonRecyclerViewAdapter.MultiTypeSupport<New>() {
            @Override
            public int getLayoutId(New item, int position) {
                return item.getIndex() % 2 == 0 ? R.layout.item_new_multi : R.layout.item_new;
            }
        });
    }
    
    //省略一些代碼
    ......

    @Override
    protected void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle) {
        if (bundle.size() > 0) {
            int index = bundle.getInt("Index");
            for (String key : bundle.keySet()) {
                switch (key) {
                    case "Title":
                        if (index % 2 == 0) {
                            holder.setText(R.id.tv_multi_title, bundle.getString(key));
                        } else {
                            holder.setText(R.id.tv_title, bundle.getString(key));
                        }
                        break;
                    case "Content":
                        if (index % 2 == 0) {
                            holder.setText(R.id.tv_multi_content, bundle.getString(key));
                        } else {
                            holder.setText(R.id.tv_content, bundle.getString(key));
                        }
                        break;
                }
            }
        }
    }

    @Override
    protected void entirelyBindData(CommonRecyclerViewHolder holder, New data) {
        if (data.getIndex() % 2 == 0) {
            holder.setText(R.id.tv_multi_title, data.getTitle())
                    .setText(R.id.tv_multi_content, data.getContent())
                    .setText(R.id.tv_multi_index, String.valueOf(data.getIndex()));
        } else {
            holder.setText(R.id.tv_title, data.getTitle())
                    .setText(R.id.tv_content, data.getContent())
                    .setText(R.id.tv_index, String.valueOf(data.getIndex()));
        }
    }

}
多布局.gif

四裳朋、添加頭部與底部 View

為了實(shí)現(xiàn)帶頭部與底部View的 RecyclerView 病线,需要自定義一個(gè)Adapter來包裹實(shí)際的Adapter,重寫 getItemViewType(int position) 方法鲤嫡,返回不同的值以區(qū)分頭部View與底部View送挑,并向外開放添加和移除頭部底部View的方法

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 21:53
 * 說明:可添加頭部View與底部View的RecyclerView Adapter
 */
public class WrapRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private RecyclerView.Adapter<RecyclerView.ViewHolder> innerAdapter;

    private SparseArray<View> headerViewArray;

    private SparseArray<View> footerViewArray;

    //頭部View類型開始位置,用于viewType
    private static int BASE_VIEW_TYPE_HEADER = 1000;

    //底部View類型開始位置,用于viewType
    private static int BASE_VIEW_TYPE_FOOTER = 2000;

    private RecyclerView.AdapterDataObserver dataObserver = new RecyclerView.AdapterDataObserver() {

        @Override
        public void onChanged() {
            super.onChanged();
            notifyDataSetChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            super.onItemRangeChanged(positionStart, itemCount);
            notifyItemRangeChanged(positionStart + getHeaderViewCount(), itemCount);
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            super.onItemRangeInserted(positionStart, itemCount);
            notifyItemRangeInserted(positionStart + getHeaderViewCount(), itemCount);
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            super.onItemRangeRemoved(positionStart, itemCount);
            notifyItemRangeRemoved(positionStart + getHeaderViewCount(), itemCount);
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            super.onItemRangeMoved(fromPosition, toPosition, itemCount);
            int headerViewsCountCount = getHeaderViewCount();
            notifyItemRangeChanged(fromPosition + headerViewsCountCount, toPosition + headerViewsCountCount + itemCount);
        }
    };

    public WrapRecyclerViewAdapter(RecyclerView.Adapter innerAdapter) {
        headerViewArray = new SparseArray<>();
        footerViewArray = new SparseArray<>();
        setAdapter(innerAdapter);
    }

    private void setAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
        innerAdapter = adapter;
        innerAdapter.registerAdapterDataObserver(dataObserver);
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)) {
            return headerViewArray.keyAt(position);
        }
        if (isFooterPosition(position)) {
            return footerViewArray.keyAt(position - headerViewArray.size() - getDataItemCount());
        }
        return innerAdapter.getItemViewType(position - headerViewArray.size());
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (isHeaderView(viewType)) {
            return new ViewHolder(headerViewArray.get(viewType));
        }
        if (isFooterView(viewType)) {
            return new ViewHolder(footerViewArray.get(viewType));
        }
        return innerAdapter.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int headerViewsCountCount = getHeaderViewCount();
        if (position >= headerViewsCountCount && position < headerViewsCountCount + innerAdapter.getItemCount()) {
            innerAdapter.onBindViewHolder(holder, position - headerViewsCountCount);
        }
    }

    @Override
    public int getItemCount() {
        return getHeaderViewCount() + getDataItemCount() + getFooterViewCount();
    }

    private int getDataItemCount() {
        return innerAdapter == null ? 0 : innerAdapter.getItemCount();
    }

    public int getHeaderViewCount() {
        return headerViewArray.size();
    }

    public int getFooterViewCount() {
        return footerViewArray.size();
    }

    public RecyclerView.Adapter getInnerAdapter() {
        return innerAdapter;
    }

    /**
     * 判斷是否是頭部View
     *
     * @param viewType ViewType
     * @return 是否是頭部View
     */
    private boolean isHeaderView(int viewType) {
        return headerViewArray.indexOfKey(viewType) > -1;
    }

    /**
     * 判斷是否是底部View
     *
     * @param viewType ViewType
     * @return 是否是底部View
     */
    private boolean isFooterView(int viewType) {
        return footerViewArray.indexOfKey(viewType) > -1;
    }

    /**
     * 判斷是否是頭部View
     *
     * @param view View
     * @return 是否是頭部View
     */
    public boolean isHeaderView(View view) {
        return headerViewArray.indexOfValue(view) > -1;
    }

    /**
     * 判斷是否是底部View
     *
     * @param view View
     * @return 是否是底部View
     */
    public boolean isFooterView(View view) {
        return footerViewArray.indexOfValue(view) > -1;
    }

    /**
     * 根據(jù)索引判斷該位置的View是否是頭部View
     *
     * @param position 索引
     * @return 是否是頭部View
     */
    private boolean isHeaderPosition(int position) {
        return position >= 0 && position < getHeaderViewCount();
    }

    /**
     * 根據(jù)索引判斷該位置的View是否是底部View
     *
     * @param position 索引
     * @return 是否是底部View
     */
    private boolean isFooterPosition(int position) {
        return position >= (getHeaderViewCount() + getDataItemCount())
                && position < (getHeaderViewCount() + getDataItemCount() + getFooterViewCount());
    }

    /**
     * 添加頭部View
     *
     * @param view 頭部View
     */
    public void addHeaderView(View view) {
        if (headerViewArray.indexOfValue(view) < 0) {
            headerViewArray.put(BASE_VIEW_TYPE_HEADER++, view);
            notifyItemInserted(headerViewArray.size() - 1);
        }
    }

    /**
     * 添加底部View
     *
     * @param view 底部View
     */
    public void addFooterView(View view) {
        if (footerViewArray.indexOfValue(view) < 0) {
            footerViewArray.put(BASE_VIEW_TYPE_FOOTER++, view);
            notifyItemInserted(getHeaderViewCount() + getDataItemCount() + getFooterViewCount() - 1);
        }
    }

    /**
     * 移除頭部View
     *
     * @param view View
     */
    public void removeHeaderView(View view) {
        int index = headerViewArray.indexOfValue(view);
        if (index > -1) {
            headerViewArray.removeAt(index);
            notifyItemRemoved(index);
        }
    }

    /**
     * 移除底部View
     *
     * @param view View
     */
    public void removeFooterView(View view) {
        int index = footerViewArray.indexOfValue(view);
        if (index > -1) {
            footerViewArray.removeAt(index);
            notifyItemRemoved(getHeaderViewCount() + getDataItemCount() + index);
        }
    }

    private class ViewHolder extends RecyclerView.ViewHolder {

        ViewHolder(View itemView) {
            super(itemView);
        }

    }

}

WrapRecyclerViewAdapter 的使用方法也很簡單,只要將實(shí)際的Adapter作為參數(shù)來構(gòu)造 WrapRecyclerViewAdapter 對象暖眼,然后再傳給 RecyclerView 即可惕耕,之后就可以通過 addHeaderView 和 addFooterView 方法來添加頭部和底部View

        RecyclerView rv_wrapDataList = (RecyclerView) findViewById(R.id.rv_wrapDataList);
        NewCommonAdapter adapter = new NewCommonAdapter(this, newList);
        WrapRecyclerViewAdapter wrapRecyclerViewAdapter = new WrapRecyclerViewAdapter(adapter);
        rv_wrapDataList.setLayoutManager(new LinearLayoutManager(this));
        rv_wrapDataList.setAdapter(wrapRecyclerViewAdapter);
帶頭部和底部View.gif

五、單擊和長按事件監(jiān)聽

RecyclerView 的單擊和長按事件一直是一個(gè)比較麻煩的地方诫肠,畢竟沒有官方提供的接口赡突,不過此處 CommonRecyclerViewAdapter 也已經(jīng)提供了相應(yīng)的設(shè)置方法

        NewCommonMultiAdapter newCommonMultiAdapter = new NewCommonMultiAdapter(this, newList);
        newCommonMultiAdapter.setClickListener(new CommonRecyclerViewHolder.OnClickListener() {
            @Override
            public void onClick(int position) {
                toast("單擊" + "\n" + newList.get(position).getTitle() + "\n" + newList.get(position).getContent());
            }
        });
        newCommonMultiAdapter.setLongClickListener(new CommonRecyclerViewHolder.OnLongClickListener() {
            @Override
            public void onLongClick(int position) {
                toast("長按" + "\n" + newList.get(position).getTitle() + "\n" + newList.get(position).getContent());
            }
        });
點(diǎn)擊事件.gif

六、通用 RecyclerView.ItemDecoration

想要實(shí)現(xiàn)Item之間的分割線区赵,需要繼承 RecyclerView.ItemDecoration 在相應(yīng)的位置進(jìn)行繪制惭缰,這里提供一個(gè)通用的分隔線

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 21:52
 * 說明:通用分隔線
 */
public class CommonItemDecoration extends RecyclerView.ItemDecoration{

    private int orientation = LinearLayoutManager.HORIZONTAL;

    private Drawable drawable;

    public CommonItemDecoration(Context context, int orientation) {
        this.orientation = orientation;
        int[] attrs = new int[]{android.R.attr.listDivider};
        TypedArray typedArray = context.obtainStyledAttributes(attrs);
        drawable = typedArray.getDrawable(0);
        typedArray.recycle();
    }

    public CommonItemDecoration(Drawable drawable, int orientation) {
        this.drawable = drawable;
        this.orientation = orientation;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (orientation == LinearLayoutManager.HORIZONTAL) {
            outRect.set(0, 0, drawable.getIntrinsicWidth(), 0);
        } else if (orientation == LinearLayoutManager.VERTICAL) {
            outRect.set(0, 0, 0, drawable.getIntrinsicHeight());
        }
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (orientation == LinearLayoutManager.HORIZONTAL) {
            drawVerticalDivider(c, parent);
        } else if (orientation == LinearLayoutManager.VERTICAL) {
            drawHorizontalDivider(c, parent);
        }
    }

    private void drawVerticalDivider(Canvas c, RecyclerView parent) {
        for (int i = 0; i < parent.getChildCount(); i++) {
            View child = parent.getChildAt(i);
//            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
//            //受 child layout_marginEnd 屬性的影響
//            int left = child.getRight() + params.rightMargin;
            //不受 child layout_marginEnd 屬性的影響,會(huì)直接繪制在 child 右側(cè)
            int left = child.getRight();
            int top = child.getTop();
            int right = left + drawable.getIntrinsicWidth();
            int bottom = child.getBottom();
            drawable.setBounds(left, top, right, bottom);
            drawable.draw(c);
        }
    }

    private void drawHorizontalDivider(Canvas c, RecyclerView parent) {
        for (int i = 0; i < parent.getChildCount(); i++) {
            View child = parent.getChildAt(i);
            int left = child.getLeft();
            //不受 child layout_marginBottom 屬性的影響笼才,會(huì)直接繪制在 child 底部
            int top = child.getBottom();
            int right = child.getRight();
            int bottom = top + drawable.getIntrinsicHeight();
            //會(huì)受 child layout_marginBottom 屬性的影響
            //RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            //int top = child.getBottom() + params.bottomMargin;
            drawable.setBounds(left, top, right, bottom);
            drawable.draw(c);
        }
    }

}

在之前的幾張效果圖中漱受,其實(shí)是已經(jīng)為 RecyclerView 添加了一條白色分隔線的

RecyclerView rv_commonMultiDataList = (RecyclerView) findViewById(R.id.rv_commonMultiDataList);
CommonItemDecoration commonItemDecoration = new CommonItemDecoration(ContextCompat.getDrawable(this, R.drawable.divider), LinearLayoutManager.VERTICAL);
        rv_commonMultiDataList.addItemDecoration(commonItemDecoration);

這里也可以傳入不同的 Drawable 對象,從而實(shí)現(xiàn)多種風(fēng)格的分隔線

分隔線.gif

七骡送、SnapHelper的使用

順帶在這里對 SnapHelper 進(jìn)行一個(gè)簡單的介紹昂羡,SnapHelper 是在 Android 24.2.0 的support 包中新添加的一個(gè)支持庫,是對RecyclerView的拓展摔踱。SnapHelper旨在支持RecyclerView的對齊方式虐先,通過計(jì)算對齊RecyclerView中TargetView 的指定點(diǎn)或者容器中的任何像素點(diǎn),可以使RecyclerView實(shí)現(xiàn)類似于 ViewPager 的切換效果

SnapHelper是一個(gè)抽象類派敷,官方提供了 LinearSnapHelper 和 PagerSnapHelper 兩個(gè)具體實(shí)現(xiàn)蛹批,這里來實(shí)現(xiàn)類似于第一次使用App時(shí)顯示的引導(dǎo)頁的效果

/**
 * 作者:葉應(yīng)是葉
 * 時(shí)間:2017/12/21 22:02
 * 說明:
 */
public class SnapRecyclerViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_snap_recycler_view);
        RecyclerView rv_snap = (RecyclerView) findViewById(R.id.rv_snap);
        SnapAdapter snapAdapter = new SnapAdapter(this, getData());
        rv_snap.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
//        rv_snap.setLayoutManager(new LinearLayoutManager(this));
        rv_snap.setAdapter(snapAdapter);
//        LinearSnapHelper snapHelper = new LinearSnapHelper();
//        snapHelper.attachToRecyclerView(rv_snap);
        PagerSnapHelper pagerSnapHelper=new PagerSnapHelper();
        pagerSnapHelper.attachToRecyclerView(rv_snap);
    }

    private List<Image> getData() {
        List<Image> imageList = new ArrayList<>();
        imageList.add(new Image(0, R.drawable.drawable_1));
        imageList.add(new Image(1, R.drawable.drawable_0));
        imageList.add(new Image(2, R.drawable.drawable_1));
        imageList.add(new Image(3, R.drawable.drawable_0));
        imageList.add(new Image(4, R.drawable.drawable_1));
        imageList.add(new Image(5, R.drawable.drawable_0));
        imageList.add(new Image(6, R.drawable.drawable_1));
        imageList.add(new Image(7, R.drawable.drawable_0));
        return imageList;
    }

}
image

源代碼我也已經(jīng)放到了GitHub上,點(diǎn)擊查看:Android RecyclerView 的簡便寫法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末篮愉,一起剝皮案震驚了整個(gè)濱河市腐芍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌试躏,老刑警劉巖猪勇,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颠蕴,居然都是意外死亡泣刹,警方通過查閱死者的電腦和手機(jī)助析,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椅您,“玉大人外冀,你說我怎么就攤上這事〗缶冢” “怎么了锥惋?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長开伏。 經(jīng)常有香客問我膀跌,道長,這世上最難降的妖魔是什么固灵? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任捅伤,我火速辦了婚禮,結(jié)果婚禮上巫玻,老公的妹妹穿的比我還像新娘丛忆。我一直安慰自己,他們只是感情好仍秤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布熄诡。 她就那樣靜靜地躺著,像睡著了一般诗力。 火紅的嫁衣襯著肌膚如雪凰浮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天苇本,我揣著相機(jī)與錄音袜茧,去河邊找鬼。 笑死瓣窄,一個(gè)胖子當(dāng)著我的面吹牛笛厦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俺夕,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼裳凸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了啥么?” 一聲冷哼從身側(cè)響起登舞,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悬荣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疙剑,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡氯迂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年践叠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚼蚀。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡禁灼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轿曙,到底是詐尸還是另有隱情弄捕,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布导帝,位于F島的核電站守谓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏您单。R本人自食惡果不足惜斋荞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望虐秦。 院中可真熱鬧平酿,春花似錦、人聲如沸悦陋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俺驶。三九已至幸逆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痒钝,已是汗流浹背秉颗。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留送矩,地道東北人蚕甥。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像栋荸,于是被迫代替她去往敵國和親菇怀。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,144評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,762評(píng)論 22 665
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,940評(píng)論 6 472
  • 心者晌块,君主之官也爱沟,神明出焉。肺者匆背,相傅之官呼伸,治節(jié)出焉。肝者钝尸,將軍之官括享,謀慮出焉搂根。膽者,中正之官铃辖,決斷出焉剩愧。膻中者,...
    GokoRuri閱讀 772評(píng)論 0 0