【Android開發(fā)】RecyclerView應(yīng)用總結(jié)

一、優(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)效果:


覆蓋在內(nèi)容上

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

效果:
header

footer

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

效果:


滑動效果

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容