RecyclerView使用

翔媽不會飛發表於2019-04-08

RecyclerView使用

1. 基礎使用

RecyclerView需要自己繼承RecyclerView.Adapter以及RecyclerView.ViewHolder。

  1. 實現ViewHolder,作用就是初始Item中要用的子控制元件,其作為Adapter的內部類即可。

        static class MyViewHolder extends RecyclerView.ViewHolder {
            TextView titleTv;
            TextView contentTv;
            TextView desTv;
            public MyViewHolder(View itemView) {
                super(itemView);
                titleTv=itemView.findViewById(R.id.tv_title);
                contentTv=itemView.findViewById(R.id.tv_content);
                desTv=itemView.findViewById(R.id.tv_des);
            }
        }
    複製程式碼
  2. 實現Adapter,這裡有三個重要的方法,onCreateViewHolder建立ViewHolder例項,onBindViewHolder將資料繫結到ViewHolder例項中,getItemCount獲取列表的數量。

    public class VerticalRecyclerViewAdapter extends RecyclerView.Adapter<VerticalRecyclerViewAdapter.MyViewHolder> {
        private ArrayList<Book> data;
        private Context mContext;
    
        public void setData(ArrayList<Book> data) {
            this.data = data;
            notifyDataSetChanged();
        }
    
        public VerticalRecyclerViewAdapter(Context context) {
            mContext = context;
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view= LayoutInflater.from(mContext).inflate(R.layout.view_item1,parent,false);
            return new MyViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.title.setText(data.get(position).getName());
        }
    
        @Override
        public int getItemCount() {
            return data == null ? 0 : data.size();
        }
    
        static class MyViewHolder extends RecyclerView.ViewHolder {
            public TextView title;
            public TextView content;
            public TextView des;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                title = itemView.findViewById(R.id.tv_title);
                content = itemView.findViewById(R.id.tv_content);
                des = itemView.findViewById(R.id.tv_des);
            }
        }
    }
    複製程式碼
  3. 在Activity或者Fragment中配置控制元件,可以設定item橫向佈局、豎向佈局、網格佈局

         // 配置佈局方式
         LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
         linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
         mRecyclerView.setLayoutManager(linearLayoutManager);
         // 初始化adapter
         mAdapter = new VerticalRecyclerViewAdapter(this);
         mAdapter.setData(list);
         // 為RecyclerView設定Adapter
         mRecyclerView.setAdapter(mAdapter);
    
         // 設定網格佈局
         GridLayoutManager gridLayoutManager=new GridLayoutManager(this,3);
         // 重新設定列數
         gridLayoutManager.setSpanCount(2);
    複製程式碼

2. 進階使用

2.1 點選事件

要監聽RecyclerView中Item的點選事件一般有兩種實現方式,第一種是在Adapter中進行點選事件的處理,第二種是在外部實現點選事件處理。

    // 定義介面
    public interface OnItemClickListener {
        void onItemClick(View view, Book book);
    }

    // 監聽器的注入,可使用構造時注入,也可以使用setter注入。
    private Context mContext;
    private OnItemClickListener mOnItemClickListener;
    public ClickRecyclerViewAdapter(Context context, OnItemClickListener onItemClickListener) {
        mContext = context;
        mOnItemClickListener = onItemClickListener;
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }
    // 資料繫結及具體控制元件的事件監聽
    public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
        holder.titleBtn.setText("按鈕:" + (position + 1));
        holder.titleBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "在adapter中實現點選處理邏輯", Toast.LENGTH_SHORT).show();
            }
        });

        holder.desTv.setText(data.get(position).toString());
        holder.desTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mOnItemClickListener.onItemClick(v, data.get(position));
            }
        });
    }

    // 在初始化Adapter時可以注入事件處理邏輯
    ClickRecyclerViewAdapter adapter=new ClickRecyclerViewAdapter(this, new ClickRecyclerViewAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, Book book) {
                Snackbar.make(view,"外部注入的點選邏輯:"+view.getId(),Snackbar.LENGTH_SHORT).setAction("Action",null).show();
            }
    });

複製程式碼

2.2 分組

分組的實現有多種方式,最簡單的是header和body寫在一個佈局中,繫結資料時進行比較,若和上一個位置的資料是同一組的,即隱藏本Item的header。 注意:這要求傳入的資料就是分組好的。主要實現如下: Item佈局如下

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".group.Item5Activity">

    <TextView
        android:id="@+id/tv_group_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:background="@color/blueviolet"
        android:textSize="20sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_group_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:background="@color/beige"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_group_title" />

</android.support.constraint.ConstraintLayout>

複製程式碼

按照資料分組的邏輯如下:

    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.titleTv.setText(data.get(position).getName());
        holder.contentTv.setText(data.get(position).getContent());
        // title 就是header
        if (position == 0) {
            holder.titleTv.setVisibility(View.VISIBLE);
        } else {
            if (data.get(position).getName().equals(data.get(position - 1).getName())) {
                holder.titleTv.setVisibility(View.GONE);
            } else {
                holder.titleTv.setVisibility(View.VISIBLE);
            }
        }
    }
複製程式碼

2.3 懸浮吸頂

懸浮吸頂的實現同樣需要用到分組,具體思路是在RecyclerView最上方固定一個header佈局,當列表中的header滾動到此處時,將固定的header向上滑動。當header滑出之後更換為下一個header

  1. RecyclerView佈局,將RecyclerView和header放在一起。
<android.support.constraint.ConstraintLayout 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=".sticky.Item7Activity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_item7_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>

    <include layout="@layout/view_item7_sticky_header_item" />

</android.support.constraint.ConstraintLayout>
複製程式碼
  1. Item佈局檔案,和分組的實現一樣。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <include layout="@layout/view_item7_sticky_header_item" />

    <TextView
        android:id="@+id/tv_sticky_body"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
複製程式碼
  1. 分組顯示併為Item設定標記位
    public static final int FIRST_STICKY_TAG = 1;// 是否是第一個元素
    public static final int HAS_STICKY_TAG = 2;// 該Item是帶有Header
    public static final int NONE_STICKY_TAG = 3;// 該Item沒有Header

    /**
     * 分組顯示的核心思路就是,header和body 分開顯示,header的顯示取決於當前item的頭部是否和上一個item的頭部一致。
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Book book = mBookList.get(position);
        holder.bodyTv.setText(book.getContent());

        if (position == 0) {
            // 首位元素的顯示邏輯
            holder.headerTv.setText(book.getName());
            holder.headerTv.setVisibility(View.VISIBLE);
            holder.itemView.setTag(FIRST_STICKY_TAG);
        } else {
            if (TextUtils.equals(book.getName(), mBookList.get(position - 1).getName())) {
                // 如果相等,說明Name有一致的情況,隱藏頭部標題,而這個itemView標記為沒有粘結頭部
                holder.headerTv.setVisibility(View.GONE);
                holder.itemView.setTag(NONE_STICKY_TAG);
            } else {
                // 如果不一致,說明需要一個新的頭部
                holder.headerTv.setText(book.getName());
                holder.headerTv.setVisibility(View.VISIBLE);
                holder.itemView.setTag(HAS_STICKY_TAG);
            }
        }
        // 為itemView新增描述內容,用於為列表頂部展示的那個header賦值
        holder.itemView.setContentDescription(book.getName());
    }
複製程式碼
  1. 在Activity中滑動邏輯
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
           @Override
           public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
               super.onScrolled(recyclerView, dx, dy);
               // 按畫素位置取到列表中 最上面的那個itemView
               View firstView = recyclerView.findChildViewUnder(headerTv.getMeasuredWidth() / 2, 5);
               // 根據該itemView的ContentDes 來為最上面固定的那個header設定內容。
               // 這也就是在繫結資料時,用header資料為itemview設定contentDES的作用
               if (firstView != null && firstView.getContentDescription() != null) {
                   headerTv.setText(String.valueOf(firstView.getContentDescription()));
               }
               View secondView = recyclerView.findChildViewUnder(headerTv.getMeasuredWidth() / 2, headerTv.getMeasuredHeight() + 2);
               if (secondView != null && secondView.getTag() != null) {
                   // 第二個itemview是否有header
                   int secondViewStatus = (int) secondView.getTag();
                   // 這個距離是 第二個帶有header的item距離佈局中固定的header的底部的距離。
                   // 大於0說明兩個header還未接觸,小於0說明需要將固定的header向上移動了。
                   // 因為已經是第二個header的列表的天下了。
                   int dealtY = secondView.getTop() - headerTv.getMeasuredHeight(); 
                   if (secondViewStatus==StickyGroupRecyclerViewAdapter.HAS_STICKY_TAG){
                       // 如果有header,就需要處理item自帶的header和固定的header的位置關係。
                       if (secondView.getTop()>0){
                           // 在有header的情況下,top>0說明當前item自帶的header還沒有移動到list頂部。此時需要把固定的那個header 往上推一些top-headerHeight
                           headerTv.setTranslationY(dealtY);
                       }else {
                           // 如果item的header已經有一部分移出去了,固定的header就不要動了,已經完全遮住item的header了
                           headerTv.setTranslationY(0);
                       }
                   }else if (secondViewStatus==StickyGroupRecyclerViewAdapter.NONE_STICKY_TAG){
                       // 如果listitem沒有header,那固定的header就不要動了
                       headerTv.setTranslationY(0);
                   }
               }
           }
       });
複製程式碼

2.4 ItemTouchHelper介紹

RecyclerView中涉及到拖拽就需要使用ItemTouchHelper,這是support v7包提供的處理關於在RecyclerView上新增拖動排序與滑動刪除的非常強大的工具類。

ItemTouchHelper的使用主要可分為兩步: 第一步是繼承ItemTouchHelper.Callback這個抽象類,並實現其中關於拖拽操作的抽象回撥方法。 第二步是建立自定義的callback例項物件,並用該例項物件建立itemTouchHelper物件,再通過itemTouchHelper.attachToRecyclerView(mRecyclerView);將 itemTouchHelper和我們的RecyclerView例項物件繫結在一起。

關鍵點就是 抽象回撥方法的實現。

下面我們來看下有哪些常用的回撥方法:

    /**
    * 設定滑動型別標記
    * 如果是線性佈局,則一般drag可讓其支援UP|DOWN
    * 如果是網格佈局,則一般drag可讓其支援UP|DOWN|LEFT|RIGHT 四個方向
    * swipe狀態可以設定LEFT|RIGHT
    * 不允許該類操作則 可設定為0
    * @param recyclerView
    * @param viewHolder
    * 上面兩個入參,可用於根據列表型別或ITEM型別來讓其支援不同的操作。
    * @return
    */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = 0;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    /***
     * 當drag操作發生時會回撥該方法,位置變動可交由adapter去處理,自己實現位置變動的邏輯。
     *
     * @param recyclerView
     * @param viewHolder 正在拖動的item
     * @param target 在移動方向上最近的那個item
     * @return 如果有位置變動返回true 沒有變動則返回false
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        mDragViewAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    /**
     * 當item滑動達到指定距離或者達到指定速度,都會回撥該方法。
     * 一般可在此處處理滑動刪除的邏輯,需要自己在adapter中實現。
     * @param viewHolder
     * @param direction
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
         mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }
複製程式碼

以上是三個最常用的方法,還有其他一些會用到的方法這裡也羅列一下:

    /**
     * 在長按時是否進入drag狀態,當為false時,可以在外部呼叫mItemTouchHelper.startDrag(viewHolder);
     * 來進入drag狀態,比如說:通過item的一個按鈕的點選事件來觸發starDrag方法來進入drag狀態。
     */
    public boolean isLongPressDragEnabled() {
            return true;
    }

    /**
     * 當item被drag或swipe選中時回撥
     * 此處可以對選中的item做一些狀態的更改。
     * @param viewHolder
     * @param actionState
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        // 只要item不是閒置狀態,就為其設定背景
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            viewHolder.itemView.setBackgroundResource(R.drawable.item_selected);
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    /**
     * 標記當drag中的item移動到target正上方時,target是否變動位置。
     * 預設為true,如果為false則target位置不會變動,拖拽結束原item會回到原來的位置
     * @param recyclerView
     * @param current
     * @param target
     * @return
     */
    @Override
    public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
        return true;
    }

    /**
     * 當拖拽或滑動完畢後會呼叫該方法,我們可以在此處還原一些狀態。
     * @param recyclerView 
     * @param viewHolder 拖拽或滑動的那個item
     */
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        Log.d(TAG,"clearView item:"+viewHolder.getAdapterPosition());
        viewHolder.itemView.setBackgroundResource(R.color.aqua);
        super.clearView(recyclerView, viewHolder);
    }

    /**
     * 當拖拽的item移動到下方item多少位置的時候觸發onMove 方法,自己測試貌似至少要100% 才會觸發移動,小於1f的值無效。
     * 當大於1f時,需要達到指定比例的位置才會觸發onMove 方法。
     */
    @Override
    public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
        return 1.5f;
    }

複製程式碼

2.5 拖動Item

瞭解了ItemTouchHelper,實現拖動就簡單了。首先我們需要定義一個介面,傳入拖動的id和target的id

    public interface ItemTouchHelperListener {
        void onItemMove(int fromPosition, int toPosition);
    }
複製程式碼

讓我們的Adapter實現該介面

    // 交換資料,只更新指定位置item
    @Override
    public void onItemMove(int fromPosition, int toPosition) {
        Collections.swap(mBookList,fromPosition,toPosition);
        notifyItemMoved(fromPosition, toPosition);
    }
複製程式碼

實現我們自己定義的ItemTouchHelper

public class DragItemTouchHelper extends ItemTouchHelper.Callback {
    private static final String TAG = DragItemTouchHelper.class.getSimpleName();
    private DragViewAdapter mDragViewAdapter;
    public DragItemTouchHelper(DragViewAdapter dragViewAdapter) {
        mDragViewAdapter = dragViewAdapter;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = 0;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        Log.d(TAG, "onMove: " + viewHolder.toString() + "  " + target.toString());
        mDragViewAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return false;
    }

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        // 只要item不是閒置狀態,就為其設定背景
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            viewHolder.itemView.setBackgroundResource(R.drawable.item_selected);
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        viewHolder.itemView.setBackgroundResource(R.color.aqua);
        super.clearView(recyclerView, viewHolder);
    }
}
複製程式碼

最後在Activity或Fragment中實現控制元件的初始化邏輯。

    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    DragViewAdapter adapter = new DragViewAdapter(this);
    ItemTouchHelper.Callback callback = new DragItemTouchHelper(adapter);
    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
    itemTouchHelper.attachToRecyclerView(mRecyclerView);
    mRecyclerView.setAdapter(adapter);
    adapter.setBookList(mData);
複製程式碼

2.6 滑動刪除

滑動刪除的實現方式和拖拽基本一樣,關鍵步驟如下:

    // 回撥介面定義
    public interface OnItemTouchHelperListener {
        void onItemDelete(int position);
    }

    // 在adapter中實現刪除item的回撥
    @Override
    public void onItemDelete(int position) {
        if (position < 0 || position > getItemCount()) {
            return;
        }
        data.remove(position);
        notifyItemRemoved(position);
    }

    // ItemTouchHelper.Callback中的關鍵實現
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = 0;
        int swipeFlags = ItemTouchHelper.LEFT;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        mSwipeAdapter.onItemDelete(viewHolder.getAdapterPosition());
    }

    // 設定item選中時的背景色
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            viewHolder.itemView.setBackgroundResource(R.drawable.item_selected);
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        viewHolder.itemView.setBackgroundResource(R.drawable.item_normal);
        super.clearView(recyclerView, viewHolder);
    }
複製程式碼

2.7 下拉重新整理

關於下拉重新整理官方提供了SwipeRefreshLayout可用,在support.v4.widget中,使用也很簡單,SwipeRefreshLayout直接包含列表或者ScrollView。

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/srl_down_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_down_refresh"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"></android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>
複製程式碼

以下程式碼模擬了下載事件

    mSwipeRefreshLayout = findViewById(R.id.srl_down_refresh);
    mSwipeRefreshLayout.setColorSchemeResources(
            android.R.color.holo_red_light,
            android.R.color.holo_orange_light,
            android.R.color.holo_green_light,
            android.R.color.holo_blue_light,
            android.R.color.holo_purple
    );
    mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    mSwipeRefreshLayout.setRefreshing(false);
                }
            },6000);
        }
    });
複製程式碼

2.8 雙向滑動

雙向滑動是指一個RecyclerView中包含一個豎向滾動的RecyclerView和一個橫向滾動的RecyclerView。實現思路是將Item作為一個List。 主RecyclerView中包含兩個元素,一個橫向滾動的item,一個豎向滾動的item。一個列表中同時存在兩種型別的item,就得有兩種ViewHolder。 還需要建立型別標記。

public class SlideAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final String TAG = SlideAdapter.class.getSimpleName();

    private static final int TYPE_HORIZONTAL = 0;
    private static final int TYPE_VERTICAL = 1;

    private Context mContext;
    private List<Integer> mTypeList = new ArrayList<>();

    private List<String> mHorizontalList = new ArrayList<>();
    private List<String> mVerticalList = new ArrayList<>();

    public SlideAdapter(Context context, List<Integer> typeList) {
        mContext = context;
        mTypeList = typeList;
    }

    public void setHorizontalDataList(List<String> horizontalDataList) {
        mHorizontalList = horizontalDataList;

        notifyDataSetChanged();
    }

    public void setVerticalDataList(List<String> verticalDataList) {
        mVerticalList = verticalDataList;

        notifyDataSetChanged();
    }

    /**
     * 多種型別的item就需要重寫該方法,根據資料型別決定item型別。
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        if (mTypeList.get(position) == 0) {         // 橫向
            return TYPE_HORIZONTAL;
        } else if (mTypeList.get(position) == 1) {  // 縱向
            return TYPE_VERTICAL;
        } else {
            return super.getItemViewType(position);
        }
    }

    /**
     * 根據viewType建立viewholder,
     * @param parent
     * @param viewType 此處的type也是來自於getItemViewType方法
     * @return
     */
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == TYPE_HORIZONTAL) {
            View viewHorizontal = LayoutInflater.from(mContext).inflate(R.layout.slide_horizontal_include, parent, false);
            return new HorizontalViewHolder(viewHorizontal);
        } else if (viewType == TYPE_VERTICAL) {
            View viewVertical = LayoutInflater.from(mContext).inflate(R.layout.slide_vertical_include, parent, false);
            return new VerticalViewHolder(viewVertical);
        }
        return null;
    }

    /**
     * 根據不同型別繫結資料,其實就是建立子列表的過程。
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof HorizontalViewHolder) {
            if (mHorizontalList != null) {
                // 這裡就是建立橫向滾動的子RecyclerView 的過程。
                SlideHorizontalAdapter horizontalAdapter = new SlideHorizontalAdapter(mContext, mHorizontalList);
                LinearLayoutManager manager = new LinearLayoutManager(mContext);
                manager.setOrientation(LinearLayoutManager.HORIZONTAL);
                ((HorizontalViewHolder) holder).rcvHorizontal.setLayoutManager(manager);
                ((HorizontalViewHolder) holder).rcvHorizontal.setHasFixedSize(true);
                ((HorizontalViewHolder) holder).rcvHorizontal.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.HORIZONTAL));
                ((HorizontalViewHolder) holder).rcvHorizontal.setAdapter(horizontalAdapter);

                horizontalAdapter.notifyDataSetChanged();
            }
        } else if (holder instanceof VerticalViewHolder) {
            if (mVerticalList != null) {
                SlideVerticalAdapter verticalAdapter = new SlideVerticalAdapter(mContext, mVerticalList);
                ((VerticalViewHolder) holder).rcvVertical.setLayoutManager(new LinearLayoutManager(mContext));
                ((VerticalViewHolder) holder).rcvVertical.setHasFixedSize(true);
                ((VerticalViewHolder) holder).rcvVertical.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL));
                ((VerticalViewHolder) holder).rcvVertical.setAdapter(verticalAdapter);

                verticalAdapter.notifyDataSetChanged();
            }
        }
    }

    @Override
    public int getItemCount() {
        return mTypeList.size();
    }

    public class HorizontalViewHolder extends RecyclerView.ViewHolder {

        RecyclerView rcvHorizontal;

        public HorizontalViewHolder(View itemView) {
            super(itemView);
            rcvHorizontal = itemView.findViewById(R.id.rcv_slide_horizontal);
        }
    }

    public class VerticalViewHolder extends RecyclerView.ViewHolder {

        RecyclerView rcvVertical;

        public VerticalViewHolder(View itemView) {
            super(itemView);
            rcvVertical = itemView.findViewById(R.id.rcv_slide_vertical);
        }
    }
}
複製程式碼

2.9 收縮展開

item同樣分為header和body,點選頭部展開body,具體實現如下: expandedPosition 記錄處於展開狀態的item位置。 mViewHolder 儲存處於展開狀態的item。 isExpanded 記錄當前item是否是展開的。

    public void onBindViewHolder(@NonNull final ExpandCollapseViewHolder holder, int position) {
        holder.tvTeam.setText(mList.get(position));
        holder.tvTeamChild.setText(mList.get(position) + "的子內容");
        // 判斷展開的位置和當前位置是否相同,相同則認定為展開的true
        final boolean isExpanded = position == expandedPosition;
        // 如果是展開的則顯示body區域,否則gone
        holder.rlChild.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
        holder.rlParent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 若有展開的holder,則讓其關閉
                if (mViewHolder != null) {
                    mViewHolder.rlChild.setVisibility(View.GONE);
                    // 更新展開的item
                    notifyItemChanged(expandedPosition);
                }
                // 如果本item是展開的,則重置標記;否則記錄位置及holder
                expandedPosition = isExpanded ? -1 : holder.getAdapterPosition();
                mViewHolder = isExpanded ? null : holder;
                // 更新當前item
                notifyItemChanged(holder.getAdapterPosition());
            }
        });
    }
複製程式碼

相關文章