RecyclerView比listview更先進更靈活,對於不少的視圖它就是一個容器,能夠有效的重用和滾動。css
1.能夠經過設置LayoutManager能夠實現Listview和橫向Listview,GridView,橫向Gridview和瀑布流等效果。java
2.能夠經過addItemDecoration添加Item分割線。android
3.能夠經過setItemAnimator()設置Item的增長和移除動畫。api
(1)、LinearLayoutManager,相似Listview
他有兩個構造函數:
LinearLayoutManager(Context context)//默認方向爲垂直方向。
LinearLayoutManager(Context context, int orientation, boolean reverseLayout)
其中第二個參數orientation表示佈局的方向,能夠取兩個值:垂直和水平。分別是縱向Listview的效果和橫向Listview的效果。第三個參數reverseLayout表示是否反向佈局(即縱向Listview上下顛倒),若爲true,縱向Listview默認在最底部,並且第一項在最低下。
(2)、GridLayoutManager,相似GridView
三種構造函數:
GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) //能夠直接在XMl中設置RecyclerView 屬性」layoutManager」.
GridLayoutManager(Context context, int spanCount) //spanCount爲列數,默認方向vertical
GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)
//spanCount爲列數,orientation爲佈局方向,reverseLayout決定佈局是否反向。
(3)、StaggeredGridLayoutManager流式佈局
兩個構造函數:
StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
StaggeredGridLayoutManager(int spanCount, int orientation) //spanCount爲列數,orientation爲佈局方向ide
RecyclerView可以經過mRecyclerView.setItemAnimator(ItemAnimator animator)
設置添加、刪除、移動、改變的動畫效果。函數
RecyclerView提供了默認的ItemAnimator實現類:DefaultItemAnimator。若是沒有特殊的需求,默認使用這個動畫便可。佈局
RecyclerView經過addItemDecoration()
方法添加item之間的分割線。Android並無提供實現好的Divider,所以任何分割線樣式都須要本身實現。性能
自定義間隔樣式須要繼承RecyclerView.ItemDecoration
類,該類是個抽象類,官方目前並無提供默認的實現類,主要有三個方法。動畫
onMesure()
中會調用該方法。onDraw()
和onDrawOver()
這兩個方法都是用於繪製間隔樣式,咱們只須要複寫其中一個方法便可。ui
對於RecyclerView的Item Animator,有一個常見的坑就是「閃屏問題」。
這個問題的描述是:當Item視圖中有圖片和文字,當更新文字並調用notifyItemChanged()
時,文字改變的同時圖片會閃一下。這個問題的緣由是當調用notifyItemChanged()
時,會調用DefaultItemAnimator的animateChangeImpl()
執行change動畫,該動畫會使得Item的透明度從0變爲1,從而形成閃屏。
解決辦法很簡單,在rv.setAdapter()
以前調用((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(false)
禁用change動畫。
RecyclerView並無像ListView同樣暴露出Item點擊事件或者長按事件處理的api,也就是說使用RecyclerView時候,須要咱們本身來實現Item的點擊和長按等事件的處理。
實現方法有不少:
RecylerView相對於ListView的優勢羅列以下:
可是關於Item的點擊和長按事件,須要用戶本身去實現。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.raphets.recyclerview.MainActivity" > <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:background="#0099ff" android:orientation="vertical" > <TextView android:id="@+id/textView" android:layout_width="120dp" android:layout_height="match_parent" android:layout_gravity="center" android:gravity="center" /> </LinearLayout>
public class MainActivity extends Activity { private RecyclerView mRecyclerView; private List<String> mDatas; private MyRecylerViewAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化數據 initData(); mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); adapter = new MyRecylerViewAdapter(this, mDatas); //綁定適配器 mRecyclerView.setAdapter(adapter); // 給每一個item添加分割線 mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); // 設置item增長和移除的動畫 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); // 設置佈局管理器 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(linearLayoutManager); } /* * 初始化數據 */ private void initData() { mDatas = new ArrayList<String>(); for (int i = 0; i <= 50; i++) { mDatas.add("item---" + i); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case R.id.listview: mRecyclerView.setLayoutManager(new LinearLayoutManager(this, OrientationHelper.VERTICAL, false)); break; case R.id.gridView: mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3)); break; case R.id.horizonalListview: mRecyclerView.setLayoutManager(new LinearLayoutManager(this, OrientationHelper.HORIZONTAL, false)); break; case R.id.horizonalGridview: mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5, OrientationHelper.HORIZONTAL, false)); break; case R.id.add: adapter.notifyItemInserted(1); break; case R.id.delete: adapter.notifyItemRemoved(1); break; default: break; } return super.onOptionsItemSelected(item); } }
public class MyRecylerViewAdapter extends Adapter<MyViewHolder> { private Context mContext; private List<String> mDatas; public MyRecylerViewAdapter(Context context, List<String> datas) { this.mContext = context; this.mDatas = datas; } @Override public int getItemCount() { // TODO Auto-generated method stub return mDatas.size(); } @Override public void onBindViewHolder(MyViewHolder arg0, int arg1) { arg0.textView.setText(mDatas.get(arg1)); } @Override public MyViewHolder onCreateViewHolder(ViewGroup arg0, int arg1) { View view = LayoutInflater.from(mContext).inflate(R.layout.item, arg0, false); MyViewHolder holder = new MyViewHolder(view); return holder; } } class MyViewHolder extends ViewHolder { // Item子佈局上的一個元素 TextView textView; public MyViewHolder(View itemView) { super(itemView); // 關聯引動該元素 ,在item.xml中findView,注意不要忘寫(itemview.) textView = (TextView) itemView.findViewById(R.id.textView); } }
(在demo中是名爲PullDownRefresh的module)
下拉從上端刷新,這個比較簡單。在佈局文件裏,用SwipeRefreshLayout把RecyclerView包在裏面,而後再在java代碼裏面寫下拉的響應事件就行了。下面直接寫代碼:
1.佈局文件,把RecyclerView放在SwipeRefreshLayout裏:
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/srl" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="match_parent"/>
2.java代碼:
//列表 recyclerView= (RecyclerView) findViewById(R.id.rv); recyclerView.setLayoutManager(new LinearLayoutManager(this)); //添加數據 list=new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add("第"+i+"項"); } adapter=new ItemAdapter(list,this); recyclerView.setAdapter(adapter); //下拉加載控件 swipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.srl); swipeRefreshLayout.setColorSchemeColors(Color.BLUE);//設置旋轉圈的顏色 //下拉監聽 swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { list.add(0,"下拉加載出現的:"+i++); adapter.notifyDataSetChanged(); swipeRefreshLayout.setRefreshing(false);//設置成true的話,下拉事後就會一直在那裏轉 } });
(在demo中是名爲PullUpRefresh的module)
設置一個監聽器,在上拉到開始顯示最下面一項時,加載更多項。
監聽器EndLessOnScrollListener代碼:
public abstract class EndLessOnScrollListener extends RecyclerView.OnScrollListener { private static final String TAG = "EndLessOnScrollListener"; LinearLayoutManager linearLayoutManager; //當前所在頁 private int currentPage=0; //已經加載出來的item數 private int totalItemCount=0; //用來存儲上一個totalItemCount private int previousTotal=0; //屏幕可見的item數量 private int visibleItemCount; //屏幕可見第一個Item的位置 private int firstVisibleItem; //是否上拉數據 private boolean loading=true; public EndLessOnScrollListener(LinearLayoutManager linearLayoutManager) { this.linearLayoutManager = linearLayoutManager; } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); visibleItemCount=recyclerView.getChildCount(); totalItemCount=linearLayoutManager.getItemCount(); firstVisibleItem=linearLayoutManager.findFirstVisibleItemPosition(); //去掉loading也能夠,可是性能會降低,在每次滑動時都會判斷,因此的加上 if(loading){ Log.d(TAG, "firstVisibleItem: " + firstVisibleItem); Log.d(TAG, "totalItemCount:" + totalItemCount); Log.d(TAG, "visibleItemCount:" + visibleItemCount); Log.d(TAG, "currentPage:" + currentPage); if(totalItemCount>previousTotal){ //說明數據項已經加載結束 loading=false; previousTotal=totalItemCount; } } //實際效果是滑動到已加載頁最後一項可見的瞬間,添加下一頁 if(!loading&&totalItemCount-visibleItemCount<=firstVisibleItem){ currentPage++; onLoadMore(currentPage); loading=true; } } /** * 提供一個抽閒方法,在Activity中監聽到這個EndLessOnScrollListener * 而且實現這個方法 * 這個方法在可見的頁的最後一項,可見時調用 * currentPage是加載到的頁面編號 */ public abstract void onLoadMore(int currentPage);
給recyclerview添加上拉監聽事件便可,這裏我讓它每次加5項:
recyclerView.addOnScrollListener(new EndLessOnScrollListener(linearLayoutManager) { @Override public void onLoadMore(int currentPage) { for (int i = count; i < 5+count; i++) { list.add("上拉加載"+i); } adapter.notifyDataSetChanged(); count+=5; } });
(在demo中是名爲HeaderAndFooter的module)
實現方法,主要是在適配器裏實現。要在適配器必須寫的方法裏面和getItemViewType()方法裏,考慮可能最前和最後一項分別是header和footer狀況。
1.temAdapter裏的代碼
private static final int TYPE_HEADER = 0; private static final int TYPE_FOOTER = 1; private static final int TYPE_NORMAL = 2; public ItemAdapter(List<String> list, Context context) { this.list = list; this.context = context; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (headerView != null && viewType == TYPE_HEADER) { return new MyViewHolder(headerView); } if (footerView != null && viewType == TYPE_FOOTER) { return new MyViewHolder(footerView); } MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context). inflate(R.layout.item_layout, parent, false)); return holder; } @Override public void onBindViewHolder(MyViewHolder holder, final int position) { if (getItemViewType(position) == TYPE_NORMAL) { holder.tv.setText(list.get(position - 1)); return; } else if (getItemViewType(position) == TYPE_HEADER) { return; } else return; } /** * 重寫這個方法,很重要,是加入Header和Footer的關鍵,咱們經過判斷item的類型,從而綁定不一樣的view */ @Override public int getItemViewType(int position) { if (headerView == null && footerView == null) { return TYPE_NORMAL; } if (position == 0) { //第一個item應該加載Header return TYPE_HEADER; } if (position == getItemCount() - 1) { //最後一個,應該加載Footer return TYPE_FOOTER; } return TYPE_NORMAL; } @Override public int getItemCount() { if (headerView == null && footerView == null) { return list.size(); } else if (headerView == null && footerView != null) { return list.size() + 1; } else if (headerView != null && footerView == null) { return list.size() + 1; } else { return list.size() + 2; } } public View getHeaderView() { return headerView; } public void setHeaderView(View headerView) { this.headerView=headerView; notifyItemInserted(0); } public View getFooterView() { return footerView; } public void setFooterView(View footerView) { this.footerView=footerView; notifyItemInserted(getItemCount()-1); } class MyViewHolder extends RecyclerView.ViewHolder { TextView tv; public MyViewHolder(View itemView) { super(itemView); tv = itemView.findViewById(R.id.tv); } }
活動裏面的代碼:
RecyclerView recyclerView;
ItemAdapter adapter;
List<String>list;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { list= new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add("第"+i+"項"); } adapter=new ItemAdapter(list,this); recyclerView= (RecyclerView) findViewById(R.id.rv); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); //注意,如下兩個方法必須在setAdapter()以後調用,不然長和寬會變成wrap_content addHeader(); addFooter(); } private void addHeader(){ View header= LayoutInflater.from(this).inflate(R.layout.header_layout,recyclerView,false); adapter.setHeaderView(header); } private void addFooter(){ View footer= LayoutInflater.from(this).inflate(R.layout.footer_layout,recyclerView,false); adapter.setFooterView(footer); }
ItemTouchHelper是一個處理RecyclerView的滑動刪除和拖拽的輔助類,RecyclerView 的item拖拽移動和滑動刪除就靠它來實現。
ItemTouchHelper的監聽以下
itemTouchHelper=new ItemTouchHelper(new ItemTouchHelper.Callback() { //用於設置拖拽和滑動的方向 @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags=0,swipeFlags=0; if(recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager||recyclerView.getLayoutManager() instanceof GridLayoutManager){ //網格式佈局有4個方向 dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT; }else if(recyclerView.getLayoutManager() instanceof LinearLayoutManager){ //線性式佈局有2個方向 dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN; swipeFlags = ItemTouchHelper.START|ItemTouchHelper.END; //設置側滑方向爲從兩個方向均可以 } return makeMovementFlags(dragFlags,swipeFlags);//swipeFlags 爲0的話item不滑動 } //長摁item拖拽時會回調這個方法 @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { int from=viewHolder.getAdapterPosition(); int to=target.getAdapterPosition(); Meizi moveItem=meizis.get(from); meizis.remove(from); meizis.add(to,moveItem);//交換數據鏈表中數據的位置 mAdapter.notifyItemMoved(from,to);//更新適配器中item的位置 return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { //這裏處理滑動刪除 } @Override public boolean isLongPressDragEnabled() { return false;//返回true則爲全部item都設置能夠拖拽 } });
itemTouchHelper須要與recyclerView綁定纔有效果,在recyclerView初始化的時候調用
itemTouchHelper.attachToRecyclerView(recyclerview);
由於我想要網格佈局中的圖片item可拖拽,而頁數item不可拖拽,因此我isLongPressDragEnabled()方法返回的false,而在item的長點擊事件監聽中作具體處理。
mAdapter.setOnItemClickListener(new GridAdapter.OnRecyclerViewItemClickListener() { @Override public void onItemClick(View view) { } @Override public void onItemLongClick(View view) { itemTouchHelper.startDrag(recyclerview.getChildViewHolder(view));//設置拖拽item } });
若是你想爲item設置拖拽和滑動時的響應動畫效果,能夠利用ItemTouchHelper的下面三個方法。用線性佈局示例:
//當item拖拽開始時調用 @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { super.onSelectedChanged(viewHolder, actionState); if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){ viewHolder.itemView.setBackgroundColor(Color.LTGRAY);//拖拽時設置背景色爲灰色 } } //當item拖拽完成時調用 @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); viewHolder.itemView.setBackgroundColor(Color.WHITE);//拖拽中止時設置背景色爲白色 } //當item視圖變化時調用 @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); //根據item滑動偏移的值修改item透明度。screenwidth是我提早得到的屏幕寬度 viewHolder.itemView.setAlpha(1-Math.abs(dX)/screenwidth); }