#Android# 關于RecyclerView,你需要知道這些

知識框架(腦圖)

RecyclerView腦圖

技術出現的背景

  • ListView沒有強制要求ViewHolder
  • ListView不能快速實現線性、網格和瀑布流效果
  • ListView和GridView設計上重合度高
  • MD設計的流行

解決的思路

  • 使用RecyclerView統一ListView和GridView
  • RecyclerView內部提供Adapter并強制要求提供ViewHolder
  • RecyclerView內部提供LayoutManager并提供線性、網格和瀑布流的實現
  • RecyclerView內部提供ItemAnimator并默認實現列表項刪除和添加動畫
RecyclerView設計思路

理念:簡化數據的顯示操作和處理大數據集

Adapter:提供view來顯示數據集中的元素
LayoutManager:放置元素到布局中
ItemAnimator:設置添加和移除元素的動畫

具體步驟

1. 添加依賴庫并在layout文件中引入RecyclerView

(1)添加依賴

compile 'com.android.support:recyclerview-v7:21.0.+'

(2) 引入RecyclerView

<android.support.v7.widget.RecyclerView
    android :id= "@+id/recycler_news"
    android :layout_width= "match_parent"
    android :layout_height= "match_parent" />

2. 指定RecyclerView是否固定大小

如果Adapter的變化不會影響RecyclerView的大小,也就是說RecyclerView的大小跟里面的列表項的多少和大小無關的話,請設置setHasFixedSize( true),這樣可以提高性能,因為不同去動態計算RecyclerView的大小。

mRecyclerView.setHasFixedSize( true) ;

3. 新建并設置LayoutManager

private RecyclerView.LayoutManager mLayoutManager;
// 線性布局,可以設置方向
mLayoutManager = new LinearLayoutManager(this);
// or 網格布局,可以設置列數和方向,是否反向顯示
mLayoutManager = new GridLayoutManager(this,2,LinearLayoutManager.HORIZONTAL,false);
// or 瀑布流布局,可以設置列數和方向
mLayoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);

4. 繼承并設置Adapter

(1)新建Adapter類,繼承自RecyclerView.Adapter
(2)新建ViewHolder內部類,繼承自RecyclerView.ViewHolder并讓Adapter的泛型設為該內部類
(3)實現Adapter的方法
1. 構造方法:一般用于接收數據集
2. onCreateViewHolder:用于從列表項布局文件中裝載布局并新建一個ViewHolder裝入列表項視圖
3. onBindViewHolder:用于數據和ViewHolder(視圖 )的綁定
4. getItemCount:用于指定數據項的多少

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private String[] mDataset;

    // 提供視圖和數據項之間的引用
    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public TextView mTextView;
        public ViewHolder(TextView v) {
            super(v);
            mTextView = v;
        }
    }

    // 依賴于數據集的構造方法
    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    // 創建一個新的View (由布局管理器調用)
    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                  int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                              .inflate(R.layout.my_text_view, parent, false);
        ...
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    // 替換View的內容 (由布局管理器調用)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // - 獲取數據集中該position的元素
        // - 使用該元素替換View的內容
        holder.mTextView.setText(mDataset[position]);

    }

    // 返回數據集的大小 (由布局管理器調用)
    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

Q&A

問題1:如何設置列表項點擊事件?

(1) 解決思路

使用Java的回調機制

(2) 具體解決方法

在Adapter中提供OnItemClickListener接口和設置接口的方法

public interface OnItemClickListener {
    void onItemClick(View View, int position);
}

private OnItemClickListener onItemClickListener;

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
}

在onBindViewHolder方法中設置點擊監聽器并調用接口方法

if (onItemClickListener != null) {
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onItemClickListener.onItemClick(holder.itemView, newPosition);
        }
    });
}

在Activity/Fragment中實現接口方法

@Override
public void onNewsItemClick(String newsId) {
    // do sth.
}

問題2:如何實現上拉加載?

(1)解決思路

給RecyclerView添加OnScrollListener,判定是否到達底部,然后執行加載更多操作

(2)具體步驟

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        //得到當前顯示的最后一個item的view
        View lastChildView = recyclerView.getLayoutManager().getChildAt(recyclerView.getLayoutManager().getChildCount()-1);
        //得到lastChildView的bottom坐標值
        int lastChildBottom = lastChildView.getBottom();
        //得到Recyclerview的底部坐標減去底部padding值,也就是顯示內容最底部的坐標
        int recyclerBottom =  recyclerView.getBottom()-recyclerView.getPaddingBottom();
        //通過這個lastChildView得到這個view當前的position值
        int lastPosition  = recyclerView.getLayoutManager().getPosition(lastChildView);

        //判斷lastChildView的bottom值跟recyclerBottom
        //判斷lastPosition是不是最后一個position
        //如果兩個條件都滿足則說明是真正的滑動到了底部
        if(lastChildBottom == recyclerBottom && lastPosition == recyclerView.getLayoutManager().getItemCount()-1 ){
            Toast.makeText(getContext(), "滑動到底了", Toast.LENGTH_SHORT).show();
        }
    }
});

問題3:如何實現下拉刷新?

(1)解決思路

使用SwipeRefreshLayout包裹RecyclerView,并設置OnRefreshListener

(2)具體步驟
// 略

問題4:如何添加Header和Footer?

(1)解決思路

重寫Adapter的getItemViewType方法,使得不同索引位置的ItemView定義為不同的類型。
要修改onCreateViewHolder、onBindViewHolder、getItemCount方法以及ViewHolder的構造方法。

(2)具體步驟

提供HeaderView的獲取和設置方法

public void setHeaderView(View headerView) {
    mHeaderView = headerView;
    notifyItemInserted(0);
}

public View getHeaderView() {
    return mHeaderView;
}

定義兩種列表項類型

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

重寫getItemViewType方法

@Override
public int getItemViewType(int position) {
    if(mHeaderView == null) return TYPE_NORMAL;
    if(position == 0) return TYPE_HEADER;
    return TYPE_NORMAL;
}

修改onCreateViewHolder、onBindViewHolder、getItemCount方法以及ViewHolder的構造方法

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if(mHeaderView != null && viewType == TYPE_HEADER) return new Holder(mHeaderView);
    View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
    return new Holder(layout);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
    if(getItemViewType(position) == TYPE_HEADER) return;
    final int pos = getRealPosition(viewHolder);
    final String data = mDatas.get(pos);
    if(viewHolder instanceof Holder) {
        ((Holder) viewHolder).text.setText(data);
    }
}

public int getRealPosition(RecyclerView.ViewHolder holder) {
    int position = holder.getLayoutPosition();
    return mHeaderView == null ? position : position - 1;
}

@Override
public int getItemCount() {
    return mHeaderView == null ? mDatas.size() : mDatas.size() + 1;
}

class Holder extends RecyclerView.ViewHolder {
    TextView text;
    public Holder(View itemView) {
        super(itemView);
        if(itemView == mHeaderView) return;
        text = (TextView) itemView.findViewById(R.id.text);
    }
}

問題5:如何添加列表項的分隔線?

(1)解決思路

RecyclerView內部提供ItemDecoration抽象類,用于實現列表項之間的分割線、高亮或分組。

  • 提供getItemOffsets方法用于設置列表項之間的間隔;
  • 提供onDraw方法在列表項之前繪制分割線;
  • 提供onDrawOver方法在列表下之后繪制分割線。

使用addItemDecoration方法可以給RecyclerView添加ItemDecoration。

(2)使用步驟

以設置列表項之間的間隔為例

mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (parent.getChildAdapterPosition(view) != 0) { //排除第一個
            outRect.top = getContext().getResources().getDimensionPixelSize(R.dimen.list_item_vertical_margin);
        }
    }
});

問題6:如何自定義添加和刪除動畫?

(1)解決思路

RecyclerView默認使用DefaultItemAnimator,要自定義的話,需要繼承ItemAnimator,并調用RecyclerView的addItemAnimator方法。

(2)具體步驟

//todo

問題7:StaggeredGridLayoutManager為什么不需要傳入Context?

三個LayoutManager之間的關系

LinearLayoutManager中雖然要求要Context,但是實際上內部并沒有使用,可能是留待以后擴展用,GridLayoutManager繼承自LinearLayoutManager,所以也需要Context;StaggeredGridLayoutManager在設計時就不要求Context。

參考文檔

  1. 創建列表和卡片
  2. RecyclerView用法介紹
  3. RecyclerView添加Header的正確方式
  4. Android中Recyclerview監聽是否滑動到底部
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容