Android RecyclerView

1、RechclerView簡介。

RecyclerView比listview更先進更靈活,對於不少的視圖它就是一個容器,能夠有效的重用和滾動。css

1.能夠經過設置LayoutManager能夠實現Listview和橫向Listview,GridView,橫向Gridview和瀑布流等效果。java

2.能夠經過addItemDecoration添加Item分割線。android

3.能夠經過setItemAnimator()設置Item的增長和移除動畫。api

2、RecyclerView相關類介紹。

一、RecyclerView.Adapter:負責託管數據集,爲每一項Item建立佈局並綁定數據。

二、RecyclerView.ItemDecoration,給Item添加分割線。須要繼承該類自定義一個類。

三、RecyclerView.ItemAnimator負責處理Item增長或刪除時的動畫效果,系統提供了一個默認的動畫類DefaultItemAnimator()。

四、RecyclerView.ViewHolder:負責承載Item視圖的子佈局。

五、RecyclerView.LayoutManager:佈局管理器,負責Item視圖的佈局的顯示管理。分爲:

(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

Item Animator動畫

RecyclerView可以經過mRecyclerView.setItemAnimator(ItemAnimator animator)設置添加、刪除、移動、改變的動畫效果函數

RecyclerView提供了默認的ItemAnimator實現類:DefaultItemAnimator。若是沒有特殊的需求,默認使用這個動畫便可。佈局

Item Decoration間隔樣式

RecyclerView經過addItemDecoration()方法添加item之間的分割線。Android並無提供實現好的Divider,所以任何分割線樣式都須要本身實現性能

自定義間隔樣式須要繼承RecyclerView.ItemDecoration類,該類是個抽象類,官方目前並無提供默認的實現類,主要有三個方法。動畫

  • onDraw(Canvas c, RecyclerView parent, State state),在Item繪製以前被調用,該方法主要用於繪製間隔樣式。
  • onDrawOver(Canvas c, RecyclerView parent, State state),在Item繪製以前被調用,該方法主要用於繪製間隔樣式。
  • getItemOffsets(Rect outRect, View view, RecyclerView parent, State state),設置item的偏移量偏移的部分用於填充間隔樣式,即設置分割線的寬、高;在RecyclerView的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的點擊和長按等事件的處理。
實現方法有不少:

  • 能夠監聽RecyclerView的Touch事件而後判斷手勢作相應的處理,
  • 也能夠經過在綁定ViewHolder的時候設置監聽,而後經過Apater回調出去

RecylerView相對於ListView的優勢羅列以下:

  • RecyclerView封裝了viewholder的回收複用,也就是說RecyclerView標準化了ViewHolder編寫Adapter面向的是ViewHolder而再也不是View了,複用的邏輯被封裝了,寫起來更加簡單。
    直接省去了listview中convertView.setTag(holder)和convertView.getTag()這些繁瑣的步驟。
  • 提供了一種插拔式的體驗高度的解耦,異常的靈活,針對一個Item的顯示RecyclerView專門抽取出了相應的類,來控制Item的顯示,使其的擴展性很是強。
  • 設置佈局管理器以控制Item佈局方式橫向豎向以及瀑布流方式
  • 可設置Item的間隔樣式(可繪製)經過繼承RecyclerView的ItemDecoration這個類,而後針對本身的業務需求去書寫代碼。
  • 能夠控制Item增刪的動畫,能夠經過ItemAnimator這個類進行控制,固然針對增刪的動畫,RecyclerView有其本身默認的實現。

     可是關於Item的點擊和長按事件,須要用戶本身去實現。

3、基本使用

一、導入android-support-v7-recyclerview

二、Activity佈局文件

<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>

三、Item的佈局文件

<?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>

四、Activity類,RecyclerView的主要代碼

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); } }

五、適配器Adapter

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)

3.上拉從下端刷新

設置一個監聽器,在上拉到開始顯示最下面一項時,加載更多項。 
監聽器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; } });

4.添加尾部首部分別添加footer和Head

(在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); }

tem拖拽和滑動刪除

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);
 }

 


拖拽和滑動刪除.gif
相關文章
相關標籤/搜索