一、優(yōu)缺點(diǎn)
RecyclerView主要是和ListView進(jìn)行比較,下面列舉幾個優(yōu)缺點(diǎn):
缺點(diǎn):
- listview可以通過addHeaderView()和addFooterView()添加頭視圖和尾視圖。
- listview可以通過android:divider設(shè)置自定義分割線。
- listview可以setOnItemClickListener()和setOnItemLongClickListener()設(shè)置點(diǎn)擊事件和長按事件。
優(yōu)點(diǎn):
- 容易實(shí)現(xiàn)各種布局。
- 默認(rèn)實(shí)現(xiàn)了View的復(fù)用,不需要類似if(convertview==null)的實(shí)現(xiàn),而且回收機(jī)制更加的完善。
- 默認(rèn)支持局部刷新。
- 容易實(shí)現(xiàn)添加item,刪除item的動畫效果。
- 容易實(shí)現(xiàn)拖拽,側(cè)滑刪除等功能
- listview只提供notifyDataSetChanged()更新整個視圖,這是很不合理的。RecyclerView提供了notifyItemInserted(),notifyItemRemove(),notifyItemChanged()等API更新單個或某個范圍的Item視圖。
二、四大基礎(chǔ)部分
RecyclerView屬于新增加的控件,為了讓RecyclerView在所有版本上都能使用,將RecyclerView定義在support庫中,因此,想要使用,首先需要在項(xiàng)目的build.gradle中添加相應(yīng)的依賴庫:
compile 'com.android.support:recyclerview-v7:24.2.0'
添加后,在布局文件中直接添加recyclerview控件。
RecyclerView需要進(jìn)行四大設(shè)置:
1.Adapter(必選)
負(fù)責(zé)提供數(shù)據(jù),一個適配器,繼承自RecyclerView.Adapter。適配器需要一個構(gòu)造函數(shù)將數(shù)據(jù)傳入;重寫三個方法,分別是onCreateViewHolder,onBindViewHolder,getItemCount方法;定義一個內(nèi)部類ViewHolder,繼承RecyclerView.ViewHolder。在構(gòu)造函數(shù)中傳入view參數(shù),這個參數(shù)就是子項(xiàng)的布局。
2.LayoutManager(必選,負(fù)責(zé)布局)
LayoutManager是RecyclerView的一個抽象內(nèi)部類,一般我們使用它都是使用它的子類,常用的有:
- LinearLayoutManager(橫向和縱向)
- GridLayoutManager(網(wǎng)格式)
- StaggeredGridLayoutManager(瀑布式)
縱向?yàn)槟J(rèn);
橫向?qū)崿F(xiàn)方式:
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
網(wǎng)格式實(shí)現(xiàn)方式:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this,4);
第二個參數(shù)為每一行的個數(shù)。
瀑布流式實(shí)現(xiàn)方式:
StaggeredGridLayoutManager layoutmanager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
除此之外還需要在adapter的onBindViewHolder中設(shè)置一個隨機(jī)高度。
ViewGroup.LayoutParams layoutParams = viewHolder.linearLayout.getLayoutParams(); layoutParams.height = 300+(int)(Math.random()*100);
實(shí)現(xiàn)效果:
3.Item Decoration(可選,默認(rèn)為空)
負(fù)責(zé)Item之間的間隔,RecyclerView通過addItemDecoration()方法添加item之間的分割線。android并沒有提供實(shí)現(xiàn)好的Decoration,因此任何分割樣式都需要自己實(shí)現(xiàn)。
ItemDecoration類主要是三個方法:
public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
getItemOffsets實(shí)現(xiàn)的就是類似padding的效果;
onDraw實(shí)現(xiàn)類似背景繪制,內(nèi)容在上面;
onDrawOver可以繪制在內(nèi)容的上面,覆蓋內(nèi)容;
舉個例子,實(shí)現(xiàn)分割線:
要實(shí)現(xiàn)分割線效果需要 getItemOffsets()和 onDraw()2個方法,首先用 getItemOffsets給item下方空出一定高度的空間(例子中是1dp),然后用onDraw繪制這個空間
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = dividerHeight;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount - 1; i++) {
View view = parent.getChildAt(i);
float top = view.getBottom();
float bottom = view.getBottom() + dividerHeight;
c.drawRect(left, top, right, bottom, dividerPaint);
}
}
實(shí)現(xiàn)效果:
實(shí)現(xiàn)標(biāo)簽:
現(xiàn)在很多電商app會給商品加上一個標(biāo)簽,比如“推薦”,“熱賣”,“秒殺”等等,可以看到這些標(biāo)簽都是覆蓋在內(nèi)容之上的,這就可以用onDrawOver()來實(shí)現(xiàn),我們這里簡單實(shí)現(xiàn)一個有趣的標(biāo)簽。
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int pos = parent.getChildAdapterPosition(child);
boolean isLeft = pos % 2 == 0;
if (isLeft) {
float left = child.getLeft();
float right = left + tagWidth;
float top = child.getTop();
float bottom = child.getBottom();
c.drawRect(left, top, right, bottom, leftPaint);
} else {
float right = child.getRight();
float left = right - tagWidth;
float top = child.getTop();
float bottom = child.getBottom();
c.drawRect(left, top, right, bottom, rightPaint);
}
}
}
實(shí)現(xiàn)效果:
4.Item Animator(可選,默認(rèn)為 DefaultItemAnimator)
負(fù)責(zé)增加,刪除item的動畫 。
RecyclerView.setItemAnimator(new DefaultItemAnimator());
注意,這里更新數(shù)據(jù)集不是用adapter.notifyDataSetChanged()而是
notifyItemInserted(position)與notifyItemRemoved(position)
否則沒有動畫效果。
三、拓展功能
1.萬能適配器
這里我們只針對RecyclerView,聊聊萬能適配器出現(xiàn)的原因。為了創(chuàng)建一個RecyclerView的Adapter,每次我們都需要去做重復(fù)勞動,包括重寫onCreateViewHolder(),getItemCount()、創(chuàng)建ViewHolder,并且實(shí)現(xiàn)過程大同小異,因此萬能適配器出現(xiàn)了,他能通過以下方式快捷地創(chuàng)建一個Adapter:
public abstract class QuickAdapter<T> extends RecyclerView.Adapter<QuickAdapter.VH> {
private List<T> mData;
public QuickAdapter(List<T> mData){
this.mData = mData;
}
public abstract int getLayoutId(int viewType);
@Override
public VH onCreateViewHolder(ViewGroup parent,int viewType){
return VH.get(parent,getLayoutId(viewType));
}
@Override
public void onBindViewHolder(VH holder,int position){
convert(holder,mData.get(position),position);
}
@Override
public int getItemCount(){
return mData.size();
}
public abstract void convert(VH holder,T data,int position);
static class VH extends RecyclerView.ViewHolder{
private SparseArray<View> mViews;
private View mConvertView;
private VH(View view){
super(view);
mConvertView = view;
mViews = new SparseArray<>();
}
public static VH get(ViewGroup parent,int layoutid){
View convertView = LayoutInflater.from(parent.getContext()).inflate(layoutid,parent,false);
return new VH(convertView);
}
public <T extends View> T getView(int id){
View view = mViews.get(id);
if(view == null){
view = mConvertView.findViewById(id);
mViews.put(id,view);
}
return (T)view;
}
public void setText(int id,String values){
TextView textView = getView(id);
textView.setText(values);
}
}
}
使用:
QuickAdapter<String> adapter = new QuickAdapter<String>(mData) {
@Override
public int getLayoutId(int viewType) {
return R.layout.item;
}
@Override
public void convert(VH holder, String data, int position) {
holder.setText(R.id.item_text,data);
}
};
2.添加setOnItemClickListener接口
RecyclerView沒有像ListView一樣提供onItemClickListener卻讓人比較難過,之前都是通過給每個item添加onClickListener來模仿一個偽onItemClickListener,這種為每個item添加點(diǎn)擊監(jiān)聽的解決方案不用多想也知道是浪費(fèi)性能的方法,這里參照了網(wǎng)上的一種方法。
使用:
recyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerView) {
@Override
public void onItemClick(RecyclerView.ViewHolder vh) {
//item點(diǎn)擊事件
}
});
OnRecyclerItemClickListener是自定義的一個觸摸監(jiān)聽器,代碼如下:
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener{
private GestureDetectorCompat mGestureDetector;
private RecyclerView recyclerView;
public OnRecyclerItemClickListener(RecyclerView recyclerView){
this.recyclerView = recyclerView;
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(),new ItemTouchHelperGestureListener());
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child!=null) {
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
onItemClick(vh);
}
return true;
}
//長點(diǎn)擊事件,本例不需要不處理
//@Override
//public void onLongPress(MotionEvent e) {
// View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
// if (child!=null) {
// RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
// onItemLongClick(vh);
// }
//}
public abstract void onItemClick(RecyclerView.ViewHolder vh);
//public abstract void onItemLongClick(RecyclerView.ViewHolder vh);
}
查閱RecyclerView的api發(fā)現(xiàn)雖然沒有提供onItemClickListener但是提供了addOnItemTouchListener方法:
3.添加HeaderView和FooterView
這里引入裝飾器(Decorator)設(shè)計(jì)模式,該設(shè)計(jì)模式通過組合的方式,在不破話原有類代碼的情況下,對原有類的功能進(jìn)行擴(kuò)展。
public class ExtendAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
enum ITEM_TYPE{
HEADER,
NORMAL,
FOOTER
}
private MyAdapter adapter;
private View mHeaderView;
private View mFooterView;
public ExtendAdapter(MyAdapter adapter){
this.adapter = adapter;
}
@Override
public int getItemViewType(int position){
if (position==0){
return ITEM_TYPE.HEADER.ordinal();
}else if (position == adapter.getItemCount()+1){
return ITEM_TYPE.FOOTER.ordinal();
}else{
return ITEM_TYPE.NORMAL.ordinal();
}
}
@Override
public int getItemCount(){
return adapter.getItemCount()+2;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder,int position){
if (position==0){
return;
}else if (position==adapter.getItemCount()+1){
return;
}else {
adapter.onBindViewHolder((MyAdapter.MyViewHolder) holder,position-1);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType){
if (viewType==ITEM_TYPE.HEADER.ordinal()){
return new RecyclerView.ViewHolder(mHeaderView){};
}else if (viewType==ITEM_TYPE.FOOTER.ordinal()){
return new RecyclerView.ViewHolder(mFooterView) {};
}else {
return adapter.onCreateViewHolder(parent,viewType);
}
}
public void addHeaderView(View view){
this.mHeaderView = view;
}
public void addFooterView(View view){
this.mFooterView = view;
}
}
使用:
MyAdapter myAdapter = new MyAdapter(mData);
ExtendAdapter extendAdapter = new ExtendAdapter(myAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
View headerView = LayoutInflater.from(this).inflate(R.layout.item_header, recyclerView, false);
View footerView = LayoutInflater.from(this).inflate(R.layout.item_footer, recyclerView, false);
extendAdapter.addHeaderView(headerView);
extendAdapter.addFooterView(footerView);
recyclerView.setAdapter(myAdapter);
效果:4.拖拽、側(cè)滑刪除
Android提供了ItemTouchHelper類,使得RecyclerView能夠輕易地實(shí)現(xiàn)滑動和拖拽,此處我們要實(shí)現(xiàn)上下拖拽和側(cè)滑刪除。首先創(chuàng)建一個繼承自ItemTouchHelper.Callback的類,并重寫以下方法:
- getMovementFlags(): 設(shè)置支持的拖拽和滑動的方向,此處我們支持的拖拽方向?yàn)樯舷拢瑒臃较驗(yàn)閺淖蟮接液蛷挠业阶螅瑑?nèi)部通過makeMovementFlags()設(shè)置。
- onMove(): 拖拽時回調(diào)。
- onSwiped(): 滑動時回調(diào)。
- onSelectedChanged(): 狀態(tài)變化時回調(diào),一共有三個狀態(tài),分別是ACTION_STATE_IDLE(空閑狀態(tài)),ACTION_STATE_SWIPE(滑動狀態(tài)),ACTION_STATE_DRAG(拖拽狀態(tài))。此方法中可以做一些狀態(tài)變化時的處理,比如拖拽的時候修改背景色。
- clearView(): 用戶交互結(jié)束時回調(diào)。此方法可以做一些狀態(tài)的清空,比如拖拽結(jié)束后還原背景色。
- isLongPressDragEnabled(): 是否支持長按拖拽,默認(rèn)為true。如果不想支持長按拖拽,則重寫并返回false。
實(shí)現(xiàn):
public class MyItemTouchCallback extends ItemTouchHelper.Callback {
private MyAdapter adapter;
private List<String> mData;
public MyItemTouchCallback(MyAdapter adapter,List<String> mData){
this.adapter = adapter;
this.mData = mData;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //s上下拖拽
int swipeFlag = ItemTouchHelper.START | ItemTouchHelper.END; //左->右和右->左滑動
return makeMovementFlags(dragFlag,swipeFlag);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
Collections.swap(mData, from, to);
adapter.notifyItemMoved(from, to);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int pos = viewHolder.getAdapterPosition();
mData.remove(pos);
adapter.notifyItemRemoved(pos);
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){
MyAdapter.MyViewHolder holder = (MyAdapter.MyViewHolder)viewHolder;
holder.itemView.setBackgroundColor(0xffbcbcbc); //設(shè)置拖拽和側(cè)滑時的背景色
}
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
MyAdapter.MyViewHolder holder = (MyAdapter.MyViewHolder) viewHolder;
holder.itemView.setBackgroundColor(0xffeeeeee); //背景色還原
}
}
使用:
MyAdapter myAdapter = new MyAdapter(mData);
recyclerView.setAdapter(myAdapter);
MyItemTouchCallback itemTouchCallback = new MyItemTouchCallback(myAdapter,mData);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
效果:
參考文章: