RecyclerView簡介
RecyclerView是一種新的檢視組,目標是為任何基於介面卡的檢視提供相似的渲染方式。它被作為ListView和GridView控制元件的繼承者,具有更優的靈活性與可替代性。在最新的support-v7版本中提供支援。本文將講解RecyclerView的簡單實現,新增刪除條目,點選事件新增與瀑布流的實現。
相關原理與簡單實現
新增依賴
在AndroidStudio的build.gradle中新增依賴:
dependencies {
...
compile `com.android.support:recyclerview-v7:25.3.0`
}複製程式碼
在佈局中使用
新增完依賴後就可以在佈局中使用RecyclerView了:
<android.support.v7.widget.RecyclerView
android:id="@+id/main_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />複製程式碼
RecyclerView.Adapter
RecyclerView封裝了一種新型的介面卡,與現在使用的介面卡大同小異。它強制使用者使用RecyclerView提供的ViewHolder,使用時主要需要重寫onCreateViewHolder與onBindViewHolder方法。前者用來展現檢視及其持有者,且只有真正需要一個新view時才會被回撥,不需要檢查是否已經被回收。後者用來繫結資料到View上。
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private List<String> list;
private LayoutInflater inflater;
public MyAdapter(Context context, List<String> list) {
this.context = context;
this.list = list;
inflater = LayoutInflater.from(context);
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_recyclerview, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(list.get(position));
}
@Override
public int getItemCount() {
return list.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.item_textview);
}
}
}複製程式碼
LayoutManager
RecyclerView通過佈局管理器LayoutManager控制每一個item如何進行排列擺放,何時展示和隱藏。回收或重用一個View時LayoutManager會向介面卡請求新的資料來替換舊的資料,這種機制避免了建立過多的View和頻繁的呼叫findViewById方法,目前其自帶的主要有以下三種:
- LinearLayoutManager:ListView樣式
- GridLayoutManager:GridView樣式
- StaggeredGridLayoutManager:瀑布流樣式
ItemDecoration
RecyclerView並不能像ListView一樣直接在xml佈局中修改item分割線樣式。需要在Activity動態設定,當然更推薦在單個條目佈局中設定margin或者padding來實現分割線效果,這裡提供一個分割線。
“`java
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerItemDecoration 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 DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public 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;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public 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;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}複製程式碼
}
#### ItemAnimator
ItemAnimator會根據介面卡上收到的通知來動畫顯示檢視組的修改,比如item的新增與刪除。DefaultItemAnimator已經能很好的展現動畫效果了。
#### RecyclerView初始化
若想初始化一個RecyclerView使其進入工作狀態,你需要在Activity中做以下的操作:
```java
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view_main);
MyAdapter adapter = new MyAdapter(this, list);
//設定RecyclerView保持固定的大小
recyclerView.setHasFixedSize(true);
//設定介面卡
recyclerView.setAdapter(adapter);
//設定RecyclerView ListView樣式佈局管理
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
//設定RecyclerView的Item分割線
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
//設定RecyclerView的動畫
recyclerView.setItemAnimator(new DefaultItemAnimator());
//設定RecyclerView GridView樣式
//recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this, 3));
//設定RecyclerView 水平GridView樣式
//recyclerView.setLayoutManager(new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.HORIZONTAL));
//設定RecyclerView 瀑布流樣式
//recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));複製程式碼
點選事件與新增刪除Item
美中不足的是RecyclerView並沒有提供像ListView一樣的Item點選與Item長點選事件,不提供我們們就自己造,通過介面回撥來實現。
Adapter中
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private LayoutInflater inflater;
protected ArrayList<String> datas;
private onItemClickedListener onItemClickedListener;
public MyAdapter(Context context, ArrayList<String> datas) {
this.context = context;
this.datas = datas;
inflater = LayoutInflater.from(context);
}
public void setOnItemClickedListener(MyAdapter.onItemClickedListener onItemClickedListener) {
this.onItemClickedListener = onItemClickedListener;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(datas.get(position));
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
@Override
public int getItemCount() {
return datas.size();
}
/**
* 新增條目
*/
public void addItem(int position) {
datas.add(position, "xulei");
// notifyDataSetChanged();
notifyItemInserted(position);//呼叫這個才有動畫效果
}
/**
* 移除條目
*/
public void removeItem(int position) {
datas.remove(position);
notifyItemRemoved(position);
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
//初始化控制元件
textView = (TextView) itemView.findViewById(R.id.item_textview);
//設定當前條目單擊監聽
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (onItemClickedListener != null)
onItemClickedListener.onClick(view, getAdapterPosition());//可立刻獲取到當前position
// onItemClickedListener.onClick(view, getLayoutPosition());//需等當前檢視更新完才能獲取到當前position,<16ms。
}
});
//設定當前條目長按監聽
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (onItemClickedListener != null)
onItemClickedListener.onLongClick(view, getAdapterPosition());
return false;
}
});
}
}
/**
* 點選回撥的介面
*/
interface onItemClickedListener {
void onClick(View view, int position);
void onLongClick(View view, int position);
}
}複製程式碼
Activity中
在Activity中例項化Adapter之後新增如下程式碼:
adapter.setOnItemClickedListener(new MyAdapter.onItemClickedListener() {
@Override
public void onClick(View view, int position) {
adapter.addItem(position);
Toast.makeText(MainActivity.this, "點選click:" + position, Toast.LENGTH_SHORT).show();
}
@Override
public void onLongClick(View view, int position) {
adapter.removeItem(position);
Toast.makeText(MainActivity.this, "長按click:" + position, Toast.LENGTH_SHORT).show();
}
});複製程式碼
瀑布流的實現
想實現瀑布流的樣式通過使用RecyclerView也很容易就能實現。
首先在Activity中設定LayoutManager時選擇:
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));複製程式碼
修改Adapter
瀑布流自然是每個條目的高度不同才能出現瀑布的效果(水平佈局則是寬度不同),那麼只需在每個條目繫結資料時動態改變下其高度即可,貼出瀑布流Adapter程式碼:
import android.content.Context;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class StaggerAdapter extends MyAdapter {
private List<Integer> heights;
public StaggerAdapter(Context context, ArrayList<String> datas) {
super(context, datas);
heights = new ArrayList<>();
for (int i = 0; i < datas.size(); i++) {
heights.add((int) (100 + Math.random() * 300));
}
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
layoutParams.height = heights.get(position);
// layoutParams.width = heights.get(position);
holder.itemView.setLayoutParams(layoutParams);
holder.textView.setText(datas.get(position));
}
}複製程式碼
總結
通過實踐發現RecyclerView相較於ListView與GridView確實強大很多,更加的靈活與方便,提高了開發效率。但也有不足之處,如並未封裝點選事件的回撥,確實是比較頭疼。期待Google的完善。
附上GitHub原始碼:
RecyclerViewDemo
RecyclerViewDevelop