Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> RecyclerView使用完全指南,是時候體驗新控件了(一)

RecyclerView使用完全指南,是時候體驗新控件了(一)

編輯:關於Android編程

1. 概述

官方介紹,RecyclerView用於在有限的窗口展現大量的數據,其實早已經有了類似的控件,如ListView、GridView,那麼相比它們,RecyclerView有什麼樣優勢呢?
RecyclerView標准化了ViewHolder,而且異常的靈活,可以輕松實現ListView實現不了的樣式和功能,通過布局管理器LayoutManager可控制Item的布局方式,通過設置Item操作動畫自定義Item添加和刪除的動畫,通過設置Item之間的間隔樣式,自定義間隔。

設置布局管理器以控制Item的布局方式,橫向、豎向以及瀑布流方式。 可設置Item操作的動畫(刪除或者添加等) 可設置Item的間隔樣式(可繪制)

但是關於Item的點擊和長按事件,需要用戶自己去實現。

在使用RecyclerView時候,必須指定一個適配器Adapter和一個布局管理器LayoutManager。適配器繼承RecyclerView.Adapter類,具體實現類似ListView的適配器,取決於數據信息以及展示的UI。布局管理器用於確定RecyclerView中Item的展示方式以及決定何時復用已經不可見的Item,避免重復創建以及執行高成本的findViewById()方法。

來看一下用法。

mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// 設置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 設置adapter
mRecyclerView.setAdapter(mAdapter);
// 設置Item添加和移除的動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// 設置Item之間間隔樣式
mRecyclerView.addItemDecoration(mDividerItemDecoration);

可以看見RecyclerView相比ListView會多出許多操作,這也是RecyclerView靈活的地方,它將許多動能暴露出來,用戶可以選擇性的自定義屬性以滿足需求。

RecyclerView提供了三種布局管理器:

LinerLayoutManager 以垂直或者水平列表方式展示Item GridLayoutManager 以網格方式展示Item StaggeredGridLayoutManager 以瀑布流方式展示Item

2. 基本使用

在build.gradle文件中引入該類。

    compile 'com.android.support:recyclerview-v7:23.4.0'

Activity代碼

public class MDRvActivity extends MDBaseActivity {

    private RecyclerView mRecyclerView;

    private RecyclerView.Adapter mAdapter;

    private RecyclerView.LayoutManager mLayoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rv);
        initData();
        initView();
    }

    private void initData() {
        mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        mAdapter = new MyAdapter(getData());
    }

    private void initView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
        // 設置布局管理器
        mRecyclerView.setLayoutManager(mLayoutManager);
        // 設置adapter
        mRecyclerView.setAdapter(mAdapter);
    }

    private ArrayList getData() {
        ArrayList data = new ArrayList<>();
        String temp = " item";
        for(int i = 0; i < 20; i++) {
            data.add(i + temp);
        }

        return data;
    }
}

Activity布局文件activity_rv.xml



    

RecyclerView適配器Adapter代碼

public class MyAdapter extends RecyclerView.Adapter{

    private ArrayList mData;

    public MyAdapter(ArrayList data) {
        this.mData = data;
    }

    public void updateData(ArrayList data) {
        this.mData = data;
        notifyDataSetChanged();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 實例化展示的view
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
        // 實例化viewholder
        ViewHolder viewHolder = new ViewHolder(v);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // 綁定數據
        holder.mTv.setText(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTv;

        public ViewHolder(View itemView) {
            super(itemView);
            mTv = (TextView) itemView.findViewById(R.id.item_tv);
        }
    }
}

Item的布局文件view_rv_item.xml



    

運行結果如下:


圖-1 RecyclerView無間隔

可以看見展示效果和ListView基本上無差別,但是Item之間並沒有分割線,在xml去找divider屬性的時候,發現RecyclerView沒有divider屬性,當然也可以在Item布局中加上分割線,但是這樣做並不是很優雅。前文說過,RecyclerView可是支持自定義間隔樣式的。通過mRecyclerView.addItemDecoration()來設置我們定義好的間隔樣式。

3. 間隔樣式

自定義間隔樣式需要繼承RecyclerView.ItemDecoration類,該類是個抽象類,主要有三個方法。

onDraw(Canvas c, RecyclerView parent, State state),在Item繪制之前被調用,該方法主要用於繪制間隔樣式 onDrawOver(Canvas c, RecyclerView parent, State state),在Item繪制之前被調用,該方法主要用於繪制間隔樣式 getItemOffsets(Rect outRect, View view, RecyclerView parent, State state),設置item的偏移量,偏移的部分用於填充間隔樣式,在RecyclerView的onMesure()中會調用該方法

onDraw()和onDrawOver()這兩個方法都是用於繪制間隔樣式,我們只需要復寫其中一個方法即可。直接來看一下自定義的間隔樣式的實現吧,參考官方實例。

public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };
    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    /**
     * 用於繪制間隔樣式
     */
    private Drawable mDivider;
    /**
     * 列表的方向,水平/豎直
     */
    private int mOrientation;


    public MyDividerItemDecoration(Context context, int orientation) {
        // 獲取默認主題的屬性
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        // 繪制間隔
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

    private void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    /**
     * 繪制間隔
     */
    private void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();
        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.getBottom() + params.bottomMargin +
                    Math.round(ViewCompat.getTranslationY(child));
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    /**
     * 繪制間隔
     */
    private void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();
        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 left = child.getRight() + params.rightMargin +
                    Math.round(ViewCompat.getTranslationX(child));
            final int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }
}

然後在代碼中設置RecyclerView的間隔樣式。

mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));

來看一下展示效果。


圖-2 RecyclerView有間隔

既然RecyclerView還支持水平列表,簡單改一下屬性,看看水平列表的顯示效果。

修改Item的布局文件view_rv_item.xml



    

修改LayoutManager的初始化和間隔樣式初始化。

mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
// 設置Item之間間隔樣式
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.HORIZONTAL));

看一下水平列表效果。


圖-3 RecyclerView水平列表

3. 動畫設置

前面說過,RecyclerView可以設置列表中Item刪除和添加的動畫,在v7包中給我們提供了一種默認的Item刪除和添加的動畫,如果沒有特殊的需求,默認使用這個動畫即可。

// 設置Item添加和移除的動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

下面就添加一下刪除和添加Item的動作。在Adapter裡面添加方法。

public void addNewItem() {
    if(mData == null) {
        mData = new ArrayList<>();
    }
    mData.add(0, "new Item");
    notifyItemInserted(0);
}

public void deleteItem() {
    if(mData == null || mData.isEmpty()) {
        return;
    }
    mData.remove(0);
    notifyItemRemoved(0);
}

添加事件的處理。

public void onClick(View v) {
    int id = v.getId();
    if(id == R.id.rv_add_item_btn) {
        mAdapter.addNewItem();
        // 由於Adapter內部是直接在首個Item位置做增加操作,增加完畢後列表移動到首個Item位置
        mLayoutManager.scrollToPosition(0);
    } else if(id == R.id.rv_del_item_btn){
        mAdapter.deleteItem();
        // 由於Adapter內部是直接在首個Item位置做刪除操作,刪除完畢後列表移動到首個Item位置
        mLayoutManager.scrollToPosition(0);
    }
}

准備工作完畢後,來看一下運行的效果。


圖-4 RecyclerView動畫

4. 點擊事件

RecyclerView並沒有像ListView一樣暴露出Item點擊事件或者長按事件處理的api,也就是說使用RecyclerView時候,需要我們自己來實現Item的點擊和長按等事件的處理。實現方法有很多,可以監聽RecyclerView的Touch事件然後判斷手勢做相應的處理,也可以通過在綁定ViewHolder的時候設置監聽,然後通過Apater回調出去,我們選擇第二種方法,更加直觀和簡單。

看一下Adapter的完整代碼。

public class MyAdapter extends RecyclerView.Adapter{

    /**
     * 展示數據
     */
    private ArrayList mData;

    /**
     * 事件回調監聽
     */
    private MyAdapter.OnItemClickListener onItemClickListener;

    public MyAdapter(ArrayList data) {
        this.mData = data;
    }

    public void updateData(ArrayList data) {
        this.mData = data;
        notifyDataSetChanged();
    }

    /**
     * 添加新的Item
     */
    public void addNewItem() {
        if(mData == null) {
            mData = new ArrayList<>();
        }
        mData.add(0, "new Item");
        notifyItemInserted(0);
    }

    /**
     * 刪除Item
     */
    public void deleteItem() {
        if(mData == null || mData.isEmpty()) {
            return;
        }
        mData.remove(0);
        notifyItemRemoved(0);
    }

    /**
     * 設置回調監聽
     * 
     * @param listener
     */
    public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
        this.onItemClickListener = listener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 實例化展示的view
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
        // 實例化viewholder
        ViewHolder viewHolder = new ViewHolder(v);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        // 綁定數據
        holder.mTv.setText(mData.get(position));

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

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemLongClick(holder.itemView, pos);
                }
                //表示此事件已經消費,不會觸發單擊事件
                return true;
            }
        });
    }

    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTv;

        public ViewHolder(View itemView) {
            super(itemView);
            mTv = (TextView) itemView.findViewById(R.id.item_tv);
        }
    }

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

設置Adapter的事件監聽。

mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MDRvActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MDRvActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
    }
});

最後的實現效果。


圖-5 RecyclerView點擊效果

5. 總結

可以看見相比於ListView,RecyclerView非常靈活,但其實這篇文章只是介紹了RecyclerView的基本使用,並沒有深入,比如像網格展示和瀑布流展示都沒有介紹,而且這篇文章為了詳細的介紹使用方法,貼了大量的源代碼,導致篇幅過長,不得以要將RecyclerView的使用分好幾篇來介紹。就目前而言,我們已經知道RecyclerView的一些功能如下。

水平列表展示,設置LayoutManager的方向性 豎直列表展示,設置LayoutManager的方向性 自定義間隔,RecyclerView.addItemDecoration() Item添加和刪除動畫,RecyclerView.setItemAnimator()

所以在項目中如果再遇見列表類的布局,就可以優先考慮使用RecyclerView,更靈活更快捷的使用方式會給編碼帶來不一樣的體驗。如果你以為這些就是RecyclerView相比ListView/GridView優勢的話,那就大錯特錯了,關於RecyclerView還有更多靈活的功能,在後面文章會慢慢介紹。

附上Demo地址:鏈接

在下一篇會主要介紹RecyclerView的其他兩種展示方式,網格和瀑布流。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved