前言:
前面介紹了瀑布流的基本實現,實際上瀑布流還有一些事件需要監聽。比如點擊事件,下拉和上拉事件。
這裏接着上次的 android—UI—RecyclerView實現瀑布流(1)
添加Item點擊事件:
參考文章:爲RecyclerView添加item的點擊事件
RecyclerView側重的是佈局的靈活性,雖說可以替代ListView但是連基本的點擊事件都沒有,這篇文章就來詳細講解如何爲RecyclerView的item添加點擊事件,順便複習一下觀察者模式。
最終目的
所以我們的目的就是要模擬ListView的setOnItemClickListener()方法,調用者只須調用類似於setOnItemClickListener的東西就能獲得被點擊item的相關數據。
實現原理:
爲RecyclerView的每個子item設置setOnClickListener,然後在onClick中再調用一次對外封裝的接口,將這個事件傳遞給外面的調用者。而“爲RecyclerView的每個子item設置setOnClickListener”在Adapter中設置。其實直接在onClick中也能完全處理item的點擊事件,但是這樣會破壞代碼的邏輯。
實現步驟:
在這裏爲了不影響前面的數據,重寫一個適配器adapter:
MyAdapter.java
具體步驟如下:
1.繼承接口 OnClickListener
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener{ }
2.在MyAdapter中定義如下接口,模擬ListView的OnItemClickListener:
//定義接口
public static interface OnRecyclerViewItemClickListener {
void onItemClick(View view , String data);
}
3.聲明一個這個接口的變量:
private OnRecyclerViewItemClickListener mOnItemClickListener = null;
4.和前面的ViewHolder不同,這裏使用了自定義的ViewHolder
ViewHoider的作用就是一個儲存器,所以自定義的ViewHolder繼承於
RecyclerView.ViewHolder
—RecyclerView強制使用ViewHolder,而在ListView裏面,ViewHolder只是作爲一個優化的選項。
//自定義的ViewHolder,持有每個Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ImageView mImgView;
public ViewHolder(View view){
super(view);
mTextView = (TextView) view.findViewById(R.id.masonry_item_title);
mImgView=(ImageView) view.findViewById(R.id.masonry_item_img);
}
}
5.在onCreateViewHolder()中爲每個item添加點擊事件
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
ViewHolder vh = new ViewHolder(view);
//將創建的View註冊點擊事件
view.setOnClickListener(this);
return vh;
}
6.將點擊事件轉移給外面的調用者:(這也是繼承接口OnClickListener必須實現的方法)
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
//注意這裏使用getTag方法獲取數據
mOnItemClickListener.onItemClick(v,(String)v.getTag());
}
}
7.前面一步有個getTag方法獲取數據,那就有個setTag方法對應。在onBindViewHolder()方法裏面。
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.mTextView.setText(products.get(position).getTitle());
viewHolder.mImgView.setImageResource(products.get(position).getImg());
//將數據保存在itemView的Tag中,以便點擊時進行獲取
viewHolder.itemView.setTag(products.get(position).getTitle());
}
8.最後暴露給外面的調用者,定義一個設置Listener的方法():
public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
this.mOnItemClickListener = listener;
}
總的MyAdapter代碼:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener{
private List<Product> products;
public MyAdapter(List<Product> list) {
products = list;
}
private OnRecyclerViewItemClickListener mOnItemClickListener = null;
//define interface
public static interface OnRecyclerViewItemClickListener {
void onItemClick(View view , String data);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.masonry_item, viewGroup, false);
ViewHolder vh = new ViewHolder(view);
//將創建的View註冊點擊事件
view.setOnClickListener(this);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.mTextView.setText(products.get(position).getTitle());
viewHolder.mImgView.setImageResource(products.get(position).getImg());
//將數據保存在itemView的Tag中,以便點擊時進行獲取
viewHolder.itemView.setTag(products.get(position).getTitle());
}
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
//注意這裏使用getTag方法獲取數據
mOnItemClickListener.onItemClick(v,(String)v.getTag());
}
}
public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
this.mOnItemClickListener = listener;
}
//獲取數據的數量
@Override
public int getItemCount() {
return products.size();
}
//自定義的ViewHolder,持有每個Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ImageView mImgView;
public ViewHolder(View view){
super(view);
mTextView = (TextView) view.findViewById(R.id.masonry_item_title);
mImgView=(ImageView) view.findViewById(R.id.masonry_item_img);
}
}
}
然後在Activity中調用即可
adapter.setOnItemClickListener(new MyAdapter.OnRecyclerViewItemClickListener(){
@Override
public void onItemClick(View view , String data){
Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
}
});
測試:
當然還可以添加點擊樣式:drawable添加顏色選擇器:
item_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorAccent" android:state_pressed="true"></item>
<item android:drawable="@color/white"></item>
</selector>
設置item的背景色爲item_bg即可
總結:
以上所有步驟都發生在自定義的adapter中,典型的觀察者模式,有點繞的地方在於,這裏涉及到兩個觀察者模式的使用,view的setOnClickListener本來就是觀察者模式,我們將這個觀察者模式的事件監聽傳遞給了我們自己的觀察者模式。
上拉和下拉事件的監聽:
在下拉刷新,上拉加載的事件中,第一步就是要監聽上啦和下拉事件。
這個很簡單,由於自帶了OnScrollListener方法
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.d("ScrollListener",dx+","+dy+"");
}
});
位置變化:
可以看出上拉爲負下拉爲正。
上拉加載:
首先判斷是否已經到底部:
判斷方法1:使用StaggeredGridLayoutManager獲取最後一個的位置,判斷是否在屏幕底部。
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
if (lastPositions == null) {
lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
}
staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
lastVisibleItemPosition = findMax(lastPositions);
判斷方法2: RecyclerView每加載一個item都會調用一次onBindViewHolder方法,並且只在item由不可見變爲可見的時候纔會調用此方法。我們可以通過onBindViewHolder方法來判斷是否已經到達列表的底部。–>RecyclerView滑動到底部自動加載
public void onBindViewHolder(CollectionViewHolder holder, int position) {
holder.fillData(mData.get(position));
if(position == getItemCount()-1){//已經到達列表的底部
loadMoreData();
}
}
這裏採用第一種方法:
具體如下:
設置兩個變量:
//最後一個的位置
private int[] lastPositions;
//最後一個可見的item的位置
private int lastVisibleItemPosition;
滑動事件監聽:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
Log.i("onScrollStateChanged", "visibleItemCount" + visibleItemCount);
Log.i("onScrollStateChanged", "lastVisibleItemPosition" + lastVisibleItemPosition);
Log.i("onScrollStateChanged", "totalItemCount" + totalItemCount);
Log.i("newstate","newstate"+newState);
if (visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == totalItemCount - 2) {
Log.d("----------","到底了");
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//Log.d("ScrollListener",dx+","+dy+"");
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
if (lastPositions == null) {
lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
}
staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
lastVisibleItemPosition = findMax(lastPositions);
}
});
首先在onScrolled方法裏監聽滾動事件的最後一個Item的位置。
1.獲取View的layoutManager。
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
taggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
2.getSpanCount()返回所包含的 Item 總個數—爲什麼是個數組?
經過測試,lastPositions的長度和設置的列有關,設置3列,數組長度也爲三。但是測試時候,在findLastVisibleItemPositions
方法之前,值都爲0.經過findLastVisibleItemPositions
方法之後就能找到每一列的最大位置。—這裏是27,28,29。然後比較,輸出最大的位置。
if (lastPositions == null) {
lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
}
3.通過位置判斷哪個纔是真正的最後一個
staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
lastVisibleItemPosition = findMax(lastPositions);
自定義findMax方法
private int findMax(int[] lastPositions) {
int max = lastPositions[0];
for (int value : lastPositions) {
if (value > max) {
max = value;
}
}
return max;
}
繼續onScrollStateChanged()方法
1.同樣先
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
2.通過佈局管理器獲取:總Item數和可見item數
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
3.判斷是否到底:lastVisibleItemPosition == totalItemCount - 1
因爲是位置是從0開始的,所以比總數小1.
if (visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == totalItemCount - 1) {
Log.d("----------","到底了");
}
測試–
數據加載:
在適配器MyAdapter中添加兩個方法:
//增加item
public void addData(int position,String text,int Bitmap) {
products.add(new Product(Bitmap, text));
notifyItemInserted(position);
}
//刪除item
public void removeData(int position) {
products.remove(position);
notifyItemInserted(position);
}
在拉到底部的時候調用:
if (visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == totalItemCount - 1) {
Log.d("----------","到底了");
adapter.addData(totalItemCount,"添加",R.drawable.ic_9);
}
測試:
下拉刷新:
下拉刷新要判斷是否在頂部:使用SwipeRefreshLayout控件不需要判斷是否在頂部,因爲SwipeRefreshLayout控件默認在頂部纔會刷新數據。
所以下面的方法可以作廢。
同樣的道理:findFirstVisibleItemPositions方法可以返回當前視圖每一列可見的第一個item的位置。判斷返回最小的是否等於0,等於就是到頂部了。
private int[] firstPositions;
private int firstVisibleItemPosition;
if (firstPositions == null) {
firstPositions = new int[staggeredGridLayoutManager.getSpanCount()];
}
staggeredGridLayoutManager.findFirstVisibleItemPositions(firstPositions);
firstVisibleItemPosition = findMin(firstPositions);
private int findMin(int[] firstPositions) {
int min = firstPositions[0];
for (int value : firstPositions) {
Log.d("xxxx",value+"");
if (value < min) {
min = value;
}
}
if(firstVisibleItemPosition==0){
Log.d("----------","到頂了");
}
實現刷新功能:
SwipeRefreshLayout 是谷歌公司推出的用於下拉刷新的控件.
更改主佈局文件爲:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipe_refresh_widget"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
關於SwipeRefreshLayout的使用:
SwipeRefreshLayout的使用