編輯:關於Android編程
前面介紹了瀑布流的基本實現,實際上瀑布流還有一些事件需要監聽。比如點擊事件,下拉和上拉事件。
這裡接著上次的 android—UI—RecyclerView實現瀑布流(1)
參考文章:為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.Adapterimplements 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 implements View.OnClickListener{
private List products;
public MyAdapter(List 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
設置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+"");
}
});
位置變化:
可以看出上拉為負下拉為正。<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvY29kZT48L3A+DQo8aDIgaWQ9"上拉加載">
上拉加載:
首先判斷是否已經到底部:
判斷方法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 是谷歌公司推出的用於下拉刷新的控件.
更改主布局文件為:
Fragment必須總是被嵌入到一個Activity中,並且它的生命周期直接受宿主Activity生命周期的影響。本文內容可以分為下面的幾部分:使用支持庫創建
RatingBar介紹 RatingBar作為評分組件,它在實現打分功能的時候確實很方便,並結合了手勢觸摸事件;RatingBar 的實質是 ProgressBar ,可
在我們開發Android項目的時候,常常需要對安裝來自同一個項目但是版本不同的app到手機上,這就存在覆蓋問題,通過修改Android的包名可以解決這個問題,步驟如下:1
原因:有時候我們需要當沒有文字的時候背景顯示一個圖文混合的背景提示,這時候如果采用控件疊加的做法效率會很低,所以我們可以采用重載View的onDraw方法解決方案:這個是