Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> RecyclerView詳細介紹&使用

RecyclerView詳細介紹&使用

編輯:關於Android編程

<主菜>RecyclerView

簡介

RecyclerViewAndroid 5.0提供的新控件,已經用了很長時間了,但是一直沒有時間去仔細的梳理一下。現在有時間了,決定來整理下。

官方文檔中是這樣介紹的:
A flexible view for providing a limited window into a large data set.

RecyclerView比listview更先進更靈活,對於很多的視圖它就是一個容器,可以有效的重用和滾動。當數據動態變化的時候請使用它。

專業術語:
Adapter: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set. Position: The position of a data item within an Adapter. Index: The index of an attached child view as used in a call to getChildAt(int). Contrast with Position. Binding: The process of preparing a child view to display data corresponding to a position within the adapter. Recycle (view): A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction. Scrap (view): A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty. Dirty (view): A child view that must be rebound by the adapter before being displayed.
RecyclerView中的位置:

RecyclerViewRecyclerView.AdapterRecyclerView.LayoutManager中引進了一個抽象的額外中間層來保證在布局計算的過程中能批量的監聽到數據變化。這樣介紹了LayoutManager追蹤adapter數據變化來計算動畫的時間。因為所有的View綁定都是在同一時間執行,所以這樣也提高了性能和避免了一些非必要的綁定。
因為這個原因,在RecylcerView中有兩種position類型相關的方法:
- layout position: 在最近一次layout計算是item的位置。這是LayoutManager角度中的位置。
- adapter position: itemadapter中的位置。這是從Adapter的角度中的位置。

這兩種position除了在分發adapter.notify*事件與之後計算布局更新的這段時間之內外都是相同的。
可以通過getLayoutPosition(),findViewHolderForLayoutPosition(int)方法來獲取最近一次布局計算的LayoutPosition。這些positions包括從最近一次布局計算的所有改變。你可以根據這些位置來方便的得到用戶當前從屏幕上所看到的。例如,如果在屏幕上有一個列表,用戶請求的是第五個條目,你可以通過該方法來匹配當前用戶正在看的內容。

另一種AdapterPosition相關的方法是getAdapterPosition(),findViewHolderForAdapterPosition(int),當及時一些數據可能沒有來得及被展現到布局上時便需要獲取最新的adapter位置可以使用這些相關的方法。例如,如果你想獲取一個條目的ViewHOlderclick事件時,你應該使用getAdapterPosition()。需要知道這些方法在notifyDataSetChange()方法被調用和新布局還沒有被計算之前是不能使用的。鑒於這個原因,你應該小心的去處理這些方法有可能返回NO_POSITION或者null的情況。

結構

RecyclerView.Adapter: 創建View並將數據集合綁定到View上 ViewHolder: 持有所有的用於綁定數據或者需要操作的View LayoutManager: 布局管理器,負責擺放視圖等相關操作 ItemDecoration: 負責繪制Item附近的分割線,通過RecyclerView.addItemDecoration()使用 ItemAnimator: 為Item的操作添加動畫效果,如,增刪條目等,通過RecyclerView.setItemAnimator(new DefaultItemAnimator());使用

下圖能更直觀的了解:
image

RecyclerView提供這些內置布局管理器:
LinearLayoutManager: 以垂直或水平滾動列表方式顯示項目。 GridLayoutManager: 在網格中顯示項目。 StaggeredGridLayoutManager: 在分散對齊網格中顯示項目。
RecyclerView.ItemDecoration是一個抽象類,可以通過重寫以下三個方法,來實現Item之間的偏移量或者裝飾效果:
public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之前調用,所以這有可能被Item的內容所遮擋 public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之後調用,因此裝飾將浮於Item之上 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會調用該方法,計算出每一個Item的正確尺寸並設置偏移量。
ItemAnimator觸發於以下三種事件:
某條數據被插入到數據集合中 從數據集合中移除某條數據 更改數據集合中的某條數據

在之前的版本中,當時據集合發生改變時通過調用notifyDataSetChanged(),來刷新列表,因為這樣做會觸發列表的重繪,所以並不會出現任何動畫效果,因此需要調用一些以notifyItem*()作為前綴的特殊方法,比如:

public final void notifyItemInserted(int position) 向指定位置插入Item public final void notifyItemRemoved(int position) 移除指定位置Item public final void notifyItemChanged(int position) 更新指定位置Item

使用介紹:

添加依賴庫

dependencies {
compile 'com.android.support:recyclerview-v7:23.4.0'
}

示例代碼

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLayoutManager;
    private RecyclerView.Adapter mAdapter;

    private String [] mDatas = {"Android","ios","jack","tony","window","mac","1234","hehe","495948", "89757", "66666"};


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

    private void findView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.rv);
    }

    private void initView() {
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        // use a linear layout manager
        mLayoutManager = new LinearLayoutManager(this);
        mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mAdapter = new MyAdapter(mDatas);
        mRecyclerView.setAdapter(mAdapter);
    }

    private class MyAdapter extends RecyclerView.Adapter {
        private String[] mData;

        public MyAdapter(String[] data) {
            mData = data;
        }

        @Override
        public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            // create a new view
            View v = LayoutInflater.from(parent.getContext()).inflate(
                    R.layout.item, parent, false);
            MyHolder holder = new MyHolder(v);
            return holder;
        }

        @Override
        public void onBindViewHolder(MyHolder holder, int position) {
            holder.mTitleTv.setText(mData[position]);
        }

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

    static class MyHolder extends RecyclerView.ViewHolder {
        public TextView mTitleTv;

        public MyHolder(View itemView) {
            super(itemView);
            mTitleTv = (TextView) itemView;
        }
    }
}

activity_main的內容如下:

item的內容如下:




點擊事件

之前在使用ListView的時候,設置點擊事件是非常方便的。

mListView.setOnItemClickListener();
mListView.setOnItemLongClickListener();

但是RecylcerView確沒有提供類似的方法。那我們只能是自己去處理。處理的方式也有兩種:

通過itemView.onClickListener()以及onLongClickListener()

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLayoutManager;
    private MyAdapter mAdapter;

    private String[] mDatas = {"Android", "ios", "jack", "tony", "window", "mac", "1234", "hehe", "495948", "89757", "66666"};


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

    private void findView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.rv);
    }

    private void initView() {
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        // use a linear layout manager
        mLayoutManager = new LinearLayoutManager(this);
        mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mAdapter = new MyAdapter(mDatas);
        mAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this, "click " + mDatas[position], Toast.LENGTH_SHORT).show();
            }
        });
        mAdapter.setOnItemLongClickListener(new OnItemLongClickListener() {
            @Override
            public void onItemLongClick(View view, int position) {
                Toast.makeText(MainActivity.this, "long click " + mDatas[position], Toast.LENGTH_SHORT).show();
            }
        });
        mRecyclerView.setAdapter(mAdapter);
    }

    class MyAdapter extends RecyclerView.Adapter {
        private String[] mData;

        public MyAdapter(String[] data) {
            mData = data;
        }

        @Override
        public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            // create a new view
            View v = LayoutInflater.from(parent.getContext()).inflate(
                    R.layout.item, parent, false);
            MyHolder holder = new MyHolder(v);
            return holder;
        }

        @Override
        public void onBindViewHolder(final MyHolder holder, final int position) {
            holder.mTitleTv.setText(mData[position]);
            if (mOnItemClickListener != null) {
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mOnItemClickListener.onItemClick(holder.itemView, position);
                    }
                });
            }
            if (mOnItemLongClickListener != null) {
                holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        mOnItemLongClickListener.onItemLongClick(holder.itemView, position);
                        return true;
                    }
                });
            }
        }

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

        private OnItemClickListener mOnItemClickListener;
        private OnItemLongClickListener mOnItemLongClickListener;

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

        public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
            this.mOnItemLongClickListener = mOnItemLongClickListener;
        }

    }

    static class MyHolder extends RecyclerView.ViewHolder {
        public TextView mTitleTv;

        public MyHolder(View itemView) {
            super(itemView);
            mTitleTv = (TextView) itemView;
        }
    }

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

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

}

使用RecyclerView.OnItemTouchListener

雖然沒有提供現成的監聽器,但是提供了一個內部接口OnItemTouchListener
先來看看它的介紹:

/**
 * An OnItemTouchListener allows the application to intercept touch events in progress at the
 * view hierarchy level of the RecyclerView before those touch events are considered for
 * RecyclerView's own scrolling behavior.
 *
 * 

This can be useful for applications that wish to implement various forms of gestural * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept * a touch interaction already in progress even if the RecyclerView is already handling that * gesture stream itself for the purposes of scrolling.

* * @see SimpleOnItemTouchListener */ public static interface OnItemTouchListener { ... }

說的很明白了,而且還讓你看SimpleOnItemTouchListener,猜也能猜出來是一個默認的實現類。
好了直接上代碼:

public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private MyAdapter mAdapter;

private String[] mDatas = {"Android", "ios", "jack", "tony", "window", "mac", "1234", "hehe", "495948", "89757", "66666"};


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

private void findView() {
    mRecyclerView = (RecyclerView) findViewById(R.id.rv);
}

private void initView() {
    // use this setting to improve performance if you know that changes
    // in content do not change the layout size of the RecyclerView
    mRecyclerView.setHasFixedSize(true);

    // use a linear layout manager
    mLayoutManager = new LinearLayoutManager(this);
    mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mAdapter = new MyAdapter(mDatas);
    mRecyclerView.setAdapter(mAdapter);
    mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new OnItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            Toast.makeText(MainActivity.this, mDatas[position], Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onItemLongClick(View view, int position) {
            Toast.makeText(MainActivity.this, "Long Click " + mDatas[position], Toast.LENGTH_SHORT).show();
        }
    }));
}

class RecyclerViewClickListener extends RecyclerView.SimpleOnItemTouchListener {
    private GestureDetector mGestureDetector;
    private OnItemClickListener mListener;

    public RecyclerViewClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {
        mListener = listener;
        mGestureDetector = new GestureDetector(context,
                new GestureDetector.SimpleOnGestureListener() {
                    @Override
                    public boolean onSingleTapUp(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                        if (childView != null && mListener != null) {
                            mListener.onItemClick(childView, recyclerView.getChildLayoutPosition(childView));
                            return true;
                        }
                        return false;
                    }

                    @Override
                    public void onLongPress(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                        if (childView != null && mListener != null) {
                            mListener.onItemLongClick(childView, recyclerView.getChildLayoutPosition(childView));
                        }
                    }
                });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (mGestureDetector.onTouchEvent(e)) {
            return true;
        } else
            return super.onInterceptTouchEvent(rv, e);
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        super.onTouchEvent(rv, e);
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        super.onRequestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

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

    void onItemLongClick(View view, int position);
}

上面的實現稍微有些缺陷,就是如果我手指按住某個條目一直不抬起,他也會執行Long click事件,這顯然是不合理的,至於怎麼解決,就是可以不用GestureDetector,自己在DOWNUP事件中去判斷處理。

Headerview FooterView

之前在ListView中提供了addHeaderView()addFooterView()等方法,但是在RecyclerView中並沒有提供類似的方法,那我們該如何添加呢? 也很簡單,就是通過Adapter中去添加,利用不同的itemViewType,然後根據不同的類型去在onCreateViewHOlder中創建不同的視圖,通過這種方式來達到headerviewFooterView的效果。

上一段簡單的示例代碼:

    public class HeaderAdapter extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;
    String[] data;

    public HeaderAdapter(String[] data) {
        this.data = data;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_ITEM) {
            //inflate your layout and pass it to view holder
            return new VHItem(null);
        } else if (viewType == TYPE_HEADER) {
            //inflate your layout and pass it to view holder
            return new VHHeader(null);
        }

        throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof VHItem) {
            String dataItem = getItem(position);
            //cast holder to VHItem and set data
        } else if (holder instanceof VHHeader) {
            //cast holder to VHHeader and set data for header.
        }
    }

    @Override
    public int getItemCount() {
        return data.length + 1;
    }

    @Override
    public int getItemViewType(int position) {
        if (isPositionHeader(position))
            return TYPE_HEADER;

        return TYPE_ITEM;
    }

    private boolean isPositionHeader(int position) {
        return position == 0;
    }

    private String getItem(int position) {
        return data[position - 1];
    }

    class VHItem extends RecyclerView.ViewHolder {
        TextView title;

        public VHItem(View itemView) {
            super(itemView);
        }
    }

    class VHHeader extends RecyclerView.ViewHolder {
        Button button;

        public VHHeader(View itemView) {
            super(itemView);
        }
    }
}

上面的代碼對LinearLayoutManger是沒問題的,但是使用GridLayoutManager呢? 如果是兩列,那添加的HeaderView並不是占據上第一行,而是HeaderView與第二個ItemView一起占據第一行。那該怎麼處理呢?
那就是使用setSpanSizeLookup()方法。
比如:

recyclerView.setLayoutManager(new GridLayoutManager(this, 2));

在上面的基本設置中,我們的spanCount為2,每個itemspan size為1,因此一個header需要的span size則為2。在我嘗試著添加header之前,我想先看看如何設置span size。其實很簡單。

final GridLayoutManager manager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(manager);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
  @Override
  public int getSpanSize(int position) {
    return adapter.isHeader(position) ? manager.getSpanCount() : 1;
  }
});

下拉刷新、自動加載

實現下拉刷新

實現下拉刷新也很簡單了,可以使用SwipeRefrshLayout,SwipeRefrshLayoutGoogle官方提供的組件,可以實現下拉刷新的功能。已包含到support.v4包中。

主要方法有:

setOnRefreshListener(OnRefreshListener):添加下拉刷新監聽器 setRefreshing(boolean):顯示或者隱藏刷新進度條 isRefreshing():檢查是否處於刷新狀態 setColorSchemeResources():設置進度條的顏色主題,最多設置四種。

   

具體實現就不寫了。

實現滑動自動加載更多功能

實現方式和ListView的實現方式類似,就是通過監聽scroll時間,然後判斷當前顯示的item

//RecyclerView滑動監聽
mRecylcerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
       super.onScrollStateChanged(recyclerView, newState);
        if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    List newDatas = new ArrayList();
                    for (int i = 0; i< 5; i++) {
                        int index = i +1;
                       newDatas.add("more item" + index);
                    }
                   adapter.addMoreItem(newDatas);
                }
            },1000);
        }
    }
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView,dx, dy);
        lastVisibleItem =linearLayoutManager.findLastVisibleItemPosition();
    }
});

然後再通過結合FooterView以及增加幾種狀態就可以實現自動加載更多了。


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