天天看點

Android RecyclerView使用簡述

RecyclerView使用簡述

  • ​​前言​​
  • ​​正文​​
  • ​​一、建立項目​​
  • ​​二、RecyclerView基本使用​​
  • ​​① item布局和擴充卡​​
  • ​​② 顯示資料​​
  • ​​③ 添加Item點選事件​​
  • ​​④ 添加Item子控件點選事件​​
  • ​​⑤ 添加長按事件​​
  • ​​⑥ 多個子控件點選事件​​
  • ​​三、RecyclerView + ViewBinding使用​​
  • ​​① 擴充卡​​
  • ​​② 顯示資料​​
  • ​​③ 添加控件點選和長按​​
  • ​​四、RecyclerView + DataBinding使用​​
  • ​​① Activity使用DataBinding​​
  • ​​② item布局​​
  • ​​③ 擴充卡​​
  • ​​④ 添加item點選和長按事件​​
  • ​​五、RecyclerView下拉重新整理和上拉加載​​
  • ​​① 添加依賴庫​​
  • ​​② 下拉重新整理資料​​
  • ​​③ 上拉加載更多​​
  • ​​六、RecyclerView多布局使用​​
  • ​​① 建立布局Item​​
  • ​​② 建立資料Bean​​
  • ​​③ 擴充卡​​
  • ​​七、RecyclerView多級清單使用​​
  • ​​① 建立布局Item​​
  • ​​② 建立資料Bean​​
  • ​​③ 擴充卡​​
  • ​​八、RecyclerView動态更改資料​​
  • ​​① 建立布局item和資料Bean​​
  • ​​② 擴充卡和顯示資料​​
  • ​​③ 重新整理選中位置資料​​
  • ​​九、RecyclerView左右滑動和上下拖動​​
  • ​​① 顯示資料​​
  • ​​② ItemTouchHelper​​
  • ​​③ Item左右滑動​​
  • ​​④ Item上下拖動​​
  • ​​十、源碼​​

前言

  RecyclerView是Android中非常受歡迎的控件,RecyclerView是官方在Android5.0之後新添加的控件,推出用來替代傳統的ListView和GridView清單控件,是以如果你還在使用ListView的話可以替換為RecyclerView了。

文章的功能可以先運作看看效果,​​APK下載下傳​​

正文

  對于RecyclerView的使用根據實際項目進行說明,一些功能可能是你現在正在做的,對你有幫助也說不定。

一、建立項目

  建立一個名為RecyclerViewDemo的Android項目。注意Android Studio的版本使用4.2.1以上或者最新的版本。

Android RecyclerView使用簡述

  點選Finish完成項目建立,然後等待項目建構完成,在之前的Android中​

​RecyclerView​

​​是需要引入依賴庫的,會有v4,v7版本的庫,而現在都遷移到​

​androidx​

​​下了,目前在項目建構的時候也會自動添加這個​

​material​

​庫,裡面擁有很多的控件,當然也包括RecyclerView。

  下面我們首先配置一下​

​app​

​​下的​

​build.gradle​

​​,在android{}閉包裡面添加​

​ViewBinding​

​​和​

​DataBinding​

​的啟用,代碼如下:

{
        viewBinding true
        dataBinding true
    }      

  配置完之後記得Sync Now點選一下,其他的就沒有什麼需要配置了,現在就開始使用RecyclerView了,為了友善示範一些,我需要先建立一個基類,在com.llw.recyclerviewdemo包下建立一個BasicActivity,裡面的代碼如下:

public class BasicActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    protected void back(MaterialToolbar toolbar) {
        toolbar.setNavigationOnClickListener(v -> onBackPressed());
    }

    protected void jumpActivity(final Class<?> clazz) {
        startActivity(new Intent(this, clazz));
    }

    protected void showMsg(CharSequence msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    /**
     * 擷取字元串清單
     */
    protected List<String> getStrings() {
        List<String> lists = new ArrayList<>();
        int num = (int) (1 + Math.random() * (50 - 10 + 1));
        for (int i = 0; i < num; i++) {
            lists.add("第 " + i + " 條資料");
        }
        return lists;
    }

    protected List<BasicBean> getBasicBeans() {
        List<BasicBean> lists = new ArrayList<>();
        int num = (int) (1 + Math.random() * (50 - 10 + 1));
        for (int i = 0; i < num; i++) {
            lists.add(new BasicBean("第 " + i + " 條标題","第 " + i + " 條内容"));
        }
        return lists;
    }
}      

也沒有什麼很複雜的代碼,就是為了友善子類的時候,後面你就會明白為什麼這麼做了,這裡有一個BasicBean類,一個簡單的實體類,在com.llw.recyclerviewdemo下建立一個bean包,包下建立BasicBean類,代碼如下:

public class BasicBean {

    private String title;
    private String content;

    public BasicBean(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}      

那麼現在我們的基本工作就做好了,最後把系統預設的ActionBar去掉,如下圖所示改動就好了。

Android RecyclerView使用簡述

  下面要做的就是顯示一個基本的RecyclerView,因為除了基本使用還有其他的使用方式,我們現在隻有一個MainActivity,可以作為其他使用方式的入口,是以我們先修改一下activity_main.xml布局代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:title="RecyclerView使用說明"
        app:titleTextColor="@color/white" />

    <Button
        android:id="@+id/btn_rv_basic_use"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="6dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView基本使用"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />

</androidx.constraintlayout.widget.ConstraintLayout>      

這裡隻是添加了一個Toolbar和一個按鈕,然後修改MainActivity中的代碼,如下所示:

public class MainActivity extends BasicActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.btnRvBasicUse.setOnClickListener(v ->
                jumpActivity(RvBasicUseActivity.class));
    }
}      

  在com.llw.recyclerviewdemo下建立一個RvBasicUseActivity,對應的布局activity_rv_basic_use.xml,下面我們進入RecyclerView基本使用環節。

二、RecyclerView基本使用

首先我們修改一下activity_rv_basic_use.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".RvBasicUseActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/ic_back"
        app:title="RecyclerView基本使用"
        app:titleTextColor="@color/white" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>      

  布局内容很簡單,就是toolbar 和 RecyclerView,這裡的toolbar中用到一個圖示,用于Toolbar點選傳回,在drawable檔案夾下建立一個ic_back.xml,代碼如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:autoMirrored="true"
    android:tint="@color/white"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">

    <path
        android:fillColor="@android:color/white"
        android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42l0,0c-0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0l0,0c0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1l0,0C20,11.45 19.55,11 19,11z" />

</vector>      

這裡我打算做一個清單,那麼需要一個清單item的布局,然後就是通過擴充卡去渲染item布局的内容。

① item布局和擴充卡

在layout檔案夾下建立一個item_text_rv.xml檔案,裡面的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="1dp"
    android:foreground="?attr/selectableItemBackground"
    android:background="@color/white"
    android:padding="16dp"
    android:textColor="@color/black" />      

很簡單,就一個TextView,顯示一下文本即可,下面我們來建立一個擴充卡,首先在com.llw.recyclerviewdemo包下建立一個adapter包,包下建立一個StringAdapter類,代碼如下:

public class StringAdapter extends RecyclerView.Adapter<StringAdapter.ViewHolder> {
    
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 0;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }
}      

  這應該資料标準的模闆了,下面說一下這個擴充卡是怎麼渲染資料的,首先執行的是onCreateViewHolder,意思很明顯建立一個視圖,這裡需要傳回一個ViewHolder,注意到這裡我們有一個靜态内部類ViewHolder ,繼承自RecyclerView.ViewHolder,重寫裡面的ViewHolder構造方法,擷取一個Item的視圖View,建立完成之後就是綁定視圖,執行onBindViewHolder,綁定時就會渲染視圖View,最後執行getItemCount,你可以得到有多少個Item視圖。每渲染一個item就會執行一輪。

這個擴充卡還需要完善,首先要有資料,資料怎麼來呢?可以通過類構造方法,在StringAdapter中建立一個變量,然後寫一個構造方法,代碼如下所示:

private List<String> lists;
    
    public StringAdapter(List<String> lists) {
        this.lists = lists;
    }      

然後我們修改onCreateViewHolder中的内容,代碼如下:

@NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_rv, parent, false);
        return new ViewHolder(view);
    }      

  這裡通過LayoutInflater得到item_text_rv的視圖View,注意inflate方法傳入的三個參數,其中第二個很多人使用的是null,而我這裡用的是parent,如果用null會使你的item視圖自适應大小,哪怕你設定了match_parent也不行,你可以試試看,得到view之後,通過new ViewHolder(view)的方式建立了一個ViewHolder。

下面是擷取item布局中的控件,修改一下内部類ViewHolder中的代碼,如下所示:

public static class ViewHolder extends RecyclerView.ViewHolder {

        public TextView tvText;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvText = itemView.findViewById(R.id.tv_text);
        }
    }      

這裡的方式就和之前在Activity中有一些類似,通過findViewById找到控件擷取執行個體。然後進入到onBindViewHolder,代碼如下:

@Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.tvText.setText(lists.get(position));
    }      

  在這個回調裡面渲染視圖,這裡通過holder得到裡面的tvText,然後設定TextView的文字内容,這裡可以通過position擷取目前的視圖位置,也就是資料下标,lists.get(position)就得到目前這個下标所需要渲染到視圖的具體資料,最後在getItemCount()回調中,傳回資料的長度即可,代碼如下:

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

那麼擴充卡的渲染就到此為止了,下面怎麼讓這個擴充卡生效呢?

② 顯示資料

  修改RvBasicUseActivity中的代碼,如下所示:

public class RvBasicUseActivity extends BasicActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rv_basic_use);

        initView();
    }

    private void initView() {
        MaterialToolbar toolbar = findViewById(R.id.materialToolbar);
        RecyclerView rvText = findViewById(R.id.rv_text);
        back(toolbar);

        //擷取擴充卡執行個體
        StringAdapter stringAdapter = new StringAdapter(getStrings());
        //配置擴充卡
        rvText.setAdapter(stringAdapter);
        //配置布局管理器
        rvText.setLayoutManager(new LinearLayoutManager(this));
    }
}      

  這裡使用了最原始的方式,在onCreate()執行時會調用initView(),initView()方法中,采用findViewById擷取toolbar和RecyclerView的執行個體,然後設定傳回事件,之後就是new StringAdapter(getStrings())的方式得到一個stringAdapter ,再設定到RecyclerView中,最後設定布局管理器,這決定你的RecyclerView的内容是如何滾動的,預設是縱向的,也就是上下滑動。

現在運作一下吧。

Android RecyclerView使用簡述

OK,顯示資料沒有問題。

③ 添加Item點選事件

  現在我們得到了資料,那麼怎麼通過點選item,顯示該條item的資料呢?首先我們定義一個接口,在com.llw.recyclerviewdemo包下建立一個OnItemClickListener接口,代碼如下:

public interface OnItemClickListener {
    void onItemClick(View view, int position);
}      

下面回到StringAdapter中,添加如下代碼:

private OnItemClickListener listener;

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }      

然後修改onCreateViewHolder()方法中的内容,代碼如下:

@NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_rv, parent, false);
        ViewHolder viewHolder = new ViewHolder(view);
        view.setOnClickListener(v -> {
            if(listener != null){
                listener.onItemClick(v, viewHolder.getAdapterPosition());
            }
        });
        return viewHolder;
    }      

就是給view添加點選事件,然後通過viewHolder得到目前位置,設定回調接口。下面我們在RvBasicUseActivity中使用點選事件,而實作監聽有兩種方式,先看第一種:

public class RvBasicUseActivity extends BasicActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rv_basic_use);

        initView();
    }

    private void initView() {
        MaterialToolbar toolbar = findViewById(R.id.materialToolbar);
        RecyclerView rvText = findViewById(R.id.rv_text);
        back(toolbar);

        List<String> strings = getStrings();
        //擷取擴充卡執行個體
        StringAdapter stringAdapter = new StringAdapter(strings);
        stringAdapter.setOnItemClickListener((view, position) -> showMsg(strings.get(position)));
        //配置擴充卡
        rvText.setAdapter(stringAdapter);
        //配置布局管理器
        rvText.setLayoutManager(new LinearLayoutManager(this));
    }
}      

這種方式就是直接通過匿名接口形式使用,核心就是

.setOnItemClickListener((view, position) -> showMsg(strings.get(position)));      

這是通過Lambda簡化之後的,原始的是這樣。

.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                showMsg(strings.get(position));
            }
        });      

第二種方式如下所示:

public class RvBasicUseActivity extends BasicActivity implements OnItemClickListener {

    private List<String> strings;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rv_basic_use);

        initView();
    }

    private void initView() {
        MaterialToolbar toolbar = findViewById(R.id.materialToolbar);
        RecyclerView rvText = findViewById(R.id.rv_text);
        back(toolbar);

        strings = getStrings();
        //擷取擴充卡執行個體
        StringAdapter stringAdapter = new StringAdapter(strings);
        stringAdapter.setOnItemClickListener(this);
        //配置擴充卡
        rvText.setAdapter(stringAdapter);
        //配置布局管理器
        rvText.setLayoutManager(new LinearLayoutManager(this));
    }

    @Override
    public void onItemClick(View view, int position) {
        showMsg(strings.get(position));
    }
}      

這裡通過具名接口的方式進行實作,可以更直覺。

這兩種方式都行,看個人喜好,取其一使用即可,這裡我使用的是第一種方式,因為簡潔,運作看看效果。

Android RecyclerView使用簡述

④ 添加Item子控件點選事件

如果要添加子控件的話,首先要有子控件才行,修改一下item_text_rv.xml,代碼如下:

<?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="wrap_content"
    android:layout_marginBottom="1dp"
    android:background="@color/white"
    android:gravity="center_vertical"
    android:foreground="?attr/selectableItemBackground"
    android:orientation="horizontal"
    android:padding="16dp">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textColor="@color/black" />
    <Button
        android:id="@+id/btn_test"
        android:text="按鈕"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>      

這裡我們把按鈕作為子控件進行點選操作,然後定義一個接口,在listener包下建立一個OnItemChildClickListener接口,代碼如下:

public interface OnItemChildClickListener {
    void onItemChildClick(View view, int position);
}      

下面回到StringAdapter中,添加代碼,如下所示:

private OnItemChildClickListener childClickListener;

    public void setOnItemChildClickListener(OnItemChildClickListener childClickListener) {
        this.childClickListener = childClickListener;
    }      

然後修改onCreateViewHolder方法,添加代碼如下:

//添加子控件點選事件
        view.findViewById(R.id.btn_test).setOnClickListener(v -> {
            if (childClickListener != null) {
                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());
            }
        });      

添加位置如下圖所示:

Android RecyclerView使用簡述

好了,回到RvBasicUseActivity中,在initView()方法中添加如下代碼:

//設定擴充卡Item子控件點選事件
        stringAdapter.setOnItemChildClickListener((view, position) -> showMsg(strings.get(position) + "的按鈕"));      

添加位置如下圖所示:

Android RecyclerView使用簡述

就是依葫蘆畫瓢,下面我們示範一下。

Android RecyclerView使用簡述

⑤ 添加長按事件

  除了事件處理不同,其他都差不多,是以Item長按和Item子控件長按事件我就一起寫了,這裡需要建立接口,在listener包下建立一個OnItemLongClickListener 接口,代碼如下:

public interface OnItemLongClickListener {

    boolean onItemLongClick(View view, int position);
}      

在listener包下建立一個OnItemChildLongClickListener 接口,代碼如下:

public interface OnItemChildLongClickListener {

    boolean onItemChildLongClick(View view, int position);
}      

下面進入StringAdapter,在onCreateViewHolder中添加如下代碼:

.setOnLongClickListener(v -> {
            if (longClickListener != null) {
                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());
            }
            return false;
        });

        view.findViewById(R.id.btn_test).setOnLongClickListener(v -> {
            if (childLongClickListener != null) {
                return childLongClickListener.onItemChildLongClick(v, viewHolder.getAdapterPosition());
            }
            return false;
        });      

添加位置如下圖所示

Android RecyclerView使用簡述

  現在onCreateViewHolder中的代碼就有一些臃腫了,我們最好不要這樣做,是以我們需要将剛才所添加的事件抽離到一個方法裡面,這個方法專門用來處理view的事件,在StringAdapter中新增一個handlerEvents方法,代碼如下:

private void handlerEvents(View view, ViewHolder viewHolder) {
        //添加視圖點選事件
        view.setOnClickListener(v -> {
            if (listener != null) {
                listener.onItemClick(v, viewHolder.getAdapterPosition());
            }
        });
        //添加子控件點選事件
        view.findViewById(R.id.btn_test).setOnClickListener(v -> {
            if (childClickListener != null) {
                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());
            }
        });
        //添加視圖長按事件
        view.setOnLongClickListener(v -> {
            if (longClickListener != null) {
                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());
            }
            return false;
        });
        //添加視圖子控件長按事件
        view.findViewById(R.id.btn_test).setOnLongClickListener(v -> {
            if (childLongClickListener != null) {
                return childLongClickListener.onItemChildLongClick(v, viewHolder.getAdapterPosition());
            }
            return false;
        });
    }      

然後我們在onCreateViewHolder()方法中調用handlerEvents()方法即可,修改onCreateViewHolder()方法代碼,如下所示:

@NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_rv, parent, false);
        ViewHolder viewHolder = new ViewHolder(view);
        handlerEvents(view, viewHolder);
        return viewHolder;
    }      

  你是否注意到長按事件有一個傳回值,boolean類型,如果回調消耗了長按,則為 true,否則為 false。怎麼了解這句話呢?例如一個控件既有點選又有長按,如果你傳回為false,那麼再你觸發長按之後,回調沒有消耗掉,還會再觸發點選事件,而設定為true,就不會觸發後面的點選事件。

下面回到RvBasicUseActivity中,在initView()方法中添加如下代碼:

//設定擴充卡Item長按事件
        stringAdapter.setOnItemLongClickListener((view, position) -> {
            showMsg("長按了");
            return true;
        });
        //設定擴充卡Item子控件長按事件
        stringAdapter.setOnItemChildLongClickListener((view, position) -> {
            showMsg("長按了按鈕");
            return true;
        });      

至于添加在什麼位置,我想不用我再告訴你了吧,下面我們運作一下看看效果。

Android RecyclerView使用簡述

⑥ 多個子控件點選事件

  有時候一個Item裡面會有多個子控件,每一個都需要有點選事件,這是很常見的事情,那麼我們應該怎麼做呢?其實也很簡單,首先我們改動一下item_text_rv.xml,代碼如下:

<?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="wrap_content"
    android:layout_marginBottom="1dp"
    android:background="@color/white"
    android:gravity="center_vertical"
    android:foreground="?attr/selectableItemBackground"
    android:orientation="horizontal"
    android:padding="16dp">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textColor="@color/black" />
    <Button
        android:id="@+id/btn_test"
        android:text="按鈕1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn_test_2"
        android:layout_marginStart="8dp"
        android:text="按鈕2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>      

這裡我多添加了一個按鈕,那麼現在就有兩個子控件了,回到StringAdapter中,在handlerEvents()方法中,添加如下代碼:

.findViewById(R.id.btn_test_2).setOnClickListener(v -> {
            if (childClickListener != null) {
                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());
            }
        });      

然後在RvBasicUseActivity的initView()方法中,修改之前的子控件點選監聽中的代碼,如下所示:

.setOnItemChildClickListener((view, position) -> {
            switch (view.getId()) {
                case R.id.btn_test:
                    showMsg(strings.get(position) + "的按鈕 1");
                    break;
                case R.id.btn_test_2:
                    showMsg(strings.get(position) + "的按鈕 2");
                    break;
            }
        });      

雖然都是觸發這個回調方法,但是view的id不同,是以我們可以通過id得知是那個控件在點選,運作一下:

Android RecyclerView使用簡述

這裡實際上我們寫了一段重複的代碼,隻有控件id不同,是以這段代碼還可以優化一下:

.findViewById(R.id.btn_test).setOnClickListener(v -> {
            if (childClickListener != null) {
                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());
            }
        });
        view.findViewById(R.id.btn_test_2).setOnClickListener(v -> {
            if (childClickListener != null) {
                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());
            }
        });      

我們可以在StringAdapter中添加一個addChildClicks()方法,代碼如下:

private void addChildClicks(int[] ids, View view, ViewHolder viewHolder) {
        for (int id : ids) {
            view.findViewById(id).setOnClickListener(v -> {
                if (childClickListener != null) {
                    childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());
                }
            });
        }
    }      

然後在handlerEvents()方法中添加如下代碼:

//添加多個子控件點選事件
        addChildClicks(new int[]{R.id.btn_test, R.id.btn_test_2}, view, viewHolder);      

添加位置如下圖所示:

Android RecyclerView使用簡述

現在運作起來,效果和之前一樣,這樣做是為了消除重複代碼,關于多個子控件的長按事件,也是類似的處理方式,你可以自己試試哦。

三、RecyclerView + ViewBinding使用

  ViewBinding的作用是什麼相比就不用我介紹了,簡單粗暴一句話,不用手動寫findViewById。在com.llw.recyclerviewdemo包下建立一個RvViewBindingActivity,對應的布局是activity_rv_view_binding.xml。然後在首頁面增加一個按鈕作為進入RvViewBindingActivity的入口,

修改activity_main.xml的代碼,增加一個按鈕,如下所示:

<Button
        android:id="@+id/btn_rv_view_binding"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView + ViewBinding"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rv_basic_use" />      

然後回到MainActivity中,在onCreate()方法中,新增如下代碼:

.btnRvViewBinding.setOnClickListener(v -> jumpActivity(RvViewBindingActivity.class));      

下面我們編輯一下activity_rv_view_binding.xml,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".RvViewBindingActivity">
    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/ic_back"
        app:title="RecyclerView + ViewBinding"
        app:titleTextColor="@color/white" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>      

很簡單,就是一個清單,然後我們寫擴充卡。

① 擴充卡

  對于是否使用ViewBinding來說,擴充卡是關鍵,布局可以和普通的使用同一個,是以這裡需要重新寫一個擴充卡,在adapter包下新增一個StringViewBindingAdapter類,代碼如下:

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

    private final List<String> lists;

    public StringViewBindingAdapter(List<String> lists) {
        this.lists = lists;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemTextRvBinding binding = ItemTextRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.binding.tvText.setText(lists.get(position));
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemTextRvBinding binding;

        public ViewHolder(@NonNull ItemTextRvBinding itemTextRvBinding) {
            super(itemTextRvBinding.getRoot());
            binding = itemTextRvBinding;
        }
    }
}      

這裡可以看到大部分内容和普通的擴充卡一緻,不同的地方就是視圖的生成方式,你可以簡單對比一下就明白了,ViewBinding的使用還是比較簡單的,下面我們同樣需要顯示出來。

② 顯示資料

修改一下RvViewBindingActivity中的代碼:

public class RvViewBindingActivity extends BasicActivity {

    private ActivityRvViewBindingBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityRvViewBindingBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        initView();
    }

    private void initView() {
        back(binding.materialToolbar);

        //擷取擴充卡執行個體
        StringViewBindingAdapter stringAdapter = new StringViewBindingAdapter(getStrings());
        //配置擴充卡
        binding.rvText.setAdapter(stringAdapter);
        //配置布局管理器
        binding.rvText.setLayoutManager(new LinearLayoutManager(this));
    }
}      
Android RecyclerView使用簡述

那麼後面添加視圖的點選事件和子控件的處理,我這裡就一步到位了,一次性寫好。

③ 添加控件點選和長按

修改一下StringViewBindingAdapter的代碼如下所示:

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

    private final List<String> lists;

    private OnItemClickListener listener;//視圖點選

    private OnItemChildClickListener childClickListener;//視圖子控件點選

    private OnItemLongClickListener longClickListener;//視圖長按

    private OnItemChildLongClickListener childLongClickListener;//視圖子控件長按

    public void setOnItemLongClickListener(OnItemLongClickListener longClickListener) {
        this.longClickListener = longClickListener;
    }

    public void setOnItemChildLongClickListener(OnItemChildLongClickListener childLongClickListener) {
        this.childLongClickListener = childLongClickListener;
    }

    public void setOnItemChildClickListener(OnItemChildClickListener childClickListener) {
        this.childClickListener = childClickListener;
    }

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }

    public StringViewBindingAdapter(List<String> lists) {
        this.lists = lists;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemTextRvBinding binding = ItemTextRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);
        ViewHolder viewHolder = new ViewHolder(binding);
        handlerEvents(binding.getRoot(), viewHolder);
        return viewHolder;
    }

    /**
     * 處理事件
     */
    private void handlerEvents(View view, ViewHolder viewHolder) {
        //添加視圖點選事件
        view.setOnClickListener(v -> {
            if (listener != null) {
                listener.onItemClick(v, viewHolder.getAdapterPosition());
            }
        });
        //添加多個子控件點選事件
        addChildClicks(new int[]{R.id.btn_test, R.id.btn_test_2}, view, viewHolder);
        //添加視圖長按事件
        view.setOnLongClickListener(v -> {
            if (longClickListener != null) {
                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());
            }
            return false;
        });
        //添加視圖子控件長按事件
        view.findViewById(R.id.btn_test).setOnLongClickListener(v -> {
            if (childLongClickListener != null) {
                return childLongClickListener.onItemChildLongClick(v, viewHolder.getAdapterPosition());
            }
            return false;
        });
    }

    /**
     * 添加子控件點選事件
     * @param ids 控件id數組
     */
    private void addChildClicks(int[] ids, View view, ViewHolder viewHolder) {
        for (int id : ids) {
            view.findViewById(id).setOnClickListener(v -> {
                if (childClickListener != null) {
                    childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());
                }
            });
        }
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.binding.tvText.setText(lists.get(position));
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemTextRvBinding binding;

        public ViewHolder(@NonNull ItemTextRvBinding itemTextRvBinding) {
            super(itemTextRvBinding.getRoot());
            binding = itemTextRvBinding;
        }
    }
}      

修改一下RvViewBindingActivity中的代碼,如下所示:

public class RvViewBindingActivity extends BasicActivity {

    private ActivityRvViewBindingBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityRvViewBindingBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        initView();
    }

    private void initView() {
        back(binding.materialToolbar);

        List<String> strings = getStrings();
        //擷取擴充卡執行個體
        StringViewBindingAdapter stringAdapter = new StringViewBindingAdapter(strings);
        //設定擴充卡Item點選事件
        stringAdapter.setOnItemClickListener((view, position) -> showMsg(strings.get(position)));
        //設定擴充卡Item子控件點選事件
        stringAdapter.setOnItemChildClickListener((view, position) -> {
            switch (view.getId()) {
                case R.id.btn_test:
                    showMsg(strings.get(position) + "的按鈕 1");
                    break;
                case R.id.btn_test_2:
                    showMsg(strings.get(position) + "的按鈕 2");
                    break;
            }
        });
        //設定擴充卡Item長按事件
        stringAdapter.setOnItemLongClickListener((view, position) -> {
            showMsg("長按了");
            return true;
        });
        //設定擴充卡Item子控件長按事件
        stringAdapter.setOnItemChildLongClickListener((view, position) -> {
            showMsg("長按了按鈕");
            return true;
        });
        //配置擴充卡
        binding.rvText.setAdapter(stringAdapter);
        //配置布局管理器
        binding.rvText.setLayoutManager(new LinearLayoutManager(this));
    }
}      

  你會發現擴充卡和活動的代碼與基本使用大緻一樣,唯一的差別就是視圖生成方式不同,這個運作效果和基本使用的就完全一緻了。

四、RecyclerView + DataBinding使用

  ViewBinding對你來說或許太簡單了,那麼下面我們學習在RecyclerView中使用DataBinding,這個就沒有那麼簡單了,當然這是相對于ViewBinding來說的。ViewBinding如果是視圖的話,那麼DataBinding就是在ViewBinding的基礎上加上了資料渲染,下面我們來看看。

  首先還是那個問題,我們需要建立一個入口,在com.llw.recyclerviewdemo包下建立一個RvDataBindingActivity,對應的布局是activity_rv_data_binding.xml。然後修改activity_main.xml,在裡面增加一個按鈕,代碼如下:

<Button
        android:id="@+id/btn_rv_data_binding"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView + DataBinding"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rv_view_binding" />      

然後修改MainActivity中onCreate()方法中增加代碼,如下所示:

.btnRvDataBinding.setOnClickListener(v -> jumpActivity(RvDataBindingActivity.class));      

① Activity使用DataBinding

  如果你的Activity對應的xml中的某一個控件需要使用DataBinding,那麼你的Activity也需要使用DataBinding,Activity對應的xml中也需要使用DataBinding,首先我們來修改一下activity_rv_data_binding中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".RvDataBindingActivity">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/materialToolbar"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            android:minHeight="?attr/actionBarSize"
            android:theme="?attr/actionBarTheme"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navigationIcon="@drawable/ic_back"
            app:title="RecyclerView + DataBinding"
            app:titleTextColor="@color/white" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_text"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>      

注意看,最外層是layout,然後才是我們的ConstraintLayout,這是DataBinding需要這麼做的,下面我們回到RvDataBindingActivity中,修改代碼如下所示:

public class RvDataBindingActivity extends BasicActivity {

    private ActivityRvDataBindingBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_rv_data_binding);
        initView();
    }

    private void initView() {
        back(binding.materialToolbar);
    }
}      

這裡的寫法就和ViewBinding不同了,注意這一行代碼:

DataBindingUtil.setContentView(this,R.layout.activity_rv_data_binding);      

現在你可以先運作一下,看看是否能在首頁面通過按鈕正常進入RvDataBindingActivity頁面,可以的話再往下繼續進行。

② item布局

這個類還是比較簡單的,下面我們建立一個适配布局,在layout下建立一個item_text_data_rv.xml,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="basicBean"
            type="com.llw.recyclerviewdemo.bean.BasicBean" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="1dp"
        android:foreground="?attr/selectableItemBackground"
        android:background="@color/white"
        android:orientation="vertical"
        android:padding="16dp">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{basicBean.title}"
            android:textColor="@color/black" />

        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{basicBean.content}"
            android:textColor="@color/black" />
    </LinearLayout>
</layout>      

  這裡看到引入了剛才寫的實體Bean,variable表示可變資料,< data >标簽中的除了variable還有import,import用于引入資料,然後我們在看下面的布局的TextView中,android:text=“@{basicBean.title}”,這裡寫法有點像Kotlin,set和get方法都是title。這個寫法的意思就是setText(basicBean.getTitle());如果你之前沒有用過DataBinding的話,那麼這對你來說會充滿新鮮感,當然了修改文本是最簡單的使用了。

③ 擴充卡

在adapter下建立一個StringDataBindingAdapter類,代碼如下:

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

    private final List<BasicBean> lists;

    public StringDataBindingAdapter(List<BasicBean> lists) {
        this.lists = lists;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemTextDataRvBinding binding = ItemTextDataRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ItemTextDataRvBinding binding = DataBindingUtil.getBinding(holder.binding.getRoot());
        if (binding != null) {
            binding.setBasicBean(lists.get(position));
            binding.executePendingBindings();
        }
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemTextDataRvBinding binding;

        public ViewHolder(@NonNull ItemTextDataRvBinding itemTextDataRvBinding) {
            super(itemTextDataRvBinding.getRoot());
            binding = itemTextDataRvBinding;
        }
    }
}      

  這裡大部分内容和StringViewBindingAdapter的初始版一樣,下面我們來看不一樣的地方,也就是這個資料的渲染不同,代碼如下:

@Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ItemTextDataRvBinding binding = DataBindingUtil.getBinding(holder.binding.getRoot());
        if (binding != null) {
            binding.setBasicBean(lists.get(position));
            binding.executePendingBindings();
        }
    }      

  DataBindingUtil是DataBinding中很重要的一個類,在RvDataBindingActivity中也用到了,通過這個方法可以得到ItemTextDataRvBinding 的執行個體,然後設定資料源,你可能會很奇怪binding.setBasicBean的代碼哪裡來的,就是你的variable增加時就會通過編譯時技術生成的,按住Ctrl鍵點選setBasicBean就會進入到xml中variable标簽的位置,這裡的name是basicBean,如果你改成basicBean2,那麼你的setBasicBean就會報紅,需要改成setBasicBean2,可以試試看哦。當然了basicBean改了,那麼text=“”的内容也要改,是以這個name很重要。然後我們再來看executePendingBindings()方法,剛才我們通過setBasicBean設定了可變資料,那麼要使這個設定生效就需要executePendingBindings()函數,一定不要漏掉了。

下面就是顯示資料了,這裡反而很簡單,隻需要修改一下RvDataBindingActivity中的initView()中的代碼即可,代碼如下:

private void initView() {
        back(binding.materialToolbar);

        //擷取擴充卡執行個體
        StringDataBindingAdapter stringAdapter = new StringDataBindingAdapter(getBasicBeans());
        //配置擴充卡
        binding.rvText.setAdapter(stringAdapter);
        //配置布局管理器
        binding.rvText.setLayoutManager(new LinearLayoutManager(this));
    }      

我們運作看看效果

Android RecyclerView使用簡述

添加Item的點選事件,你可以和ViewBinding的使用一樣。

④ 添加item點選和長按事件

這裡修改StringDataBindingAdapter的代碼,如下所示:

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

    private final List<BasicBean> lists;

    private OnItemClickListener listener;//視圖點選

    private OnItemLongClickListener longClickListener;//視圖長按

    public void setOnItemLongClickListener(OnItemLongClickListener longClickListener) {
        this.longClickListener = longClickListener;
    }

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }

    public StringDataBindingAdapter(List<BasicBean> lists) {
        this.lists = lists;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemTextDataRvBinding binding = ItemTextDataRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);
        ViewHolder viewHolder = new ViewHolder(binding);
        handlerEvents(binding.getRoot(), viewHolder);
        return viewHolder;
    }

    /**
     * 處理事件
     */
    private void handlerEvents(View view, ViewHolder viewHolder) {
        //添加視圖點選事件
        view.setOnClickListener(v -> {
            if (listener != null) {
                listener.onItemClick(v, viewHolder.getAdapterPosition());
            }
        });

        //添加視圖長按事件
        view.setOnLongClickListener(v -> {
            if (longClickListener != null) {
                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());
            }
            return false;
        });
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ItemTextDataRvBinding binding = DataBindingUtil.getBinding(holder.binding.getRoot());
        if (binding != null) {
            binding.setBasicBean(lists.get(position));
            binding.executePendingBindings();
        }
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemTextDataRvBinding binding;

        public ViewHolder(@NonNull ItemTextDataRvBinding itemTextDataRvBinding) {
            super(itemTextDataRvBinding.getRoot());
            binding = itemTextDataRvBinding;
        }
    }

}      

然後修改RvDataBindingActivity的代碼,如下所示:

public class RvDataBindingActivity extends BasicActivity {

    private ActivityRvDataBindingBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_rv_data_binding);
        initView();
    }

    private void initView() {
        back(binding.materialToolbar);

        List<BasicBean> basicBeans = getBasicBeans();
        //擷取擴充卡執行個體
        StringDataBindingAdapter stringAdapter = new StringDataBindingAdapter(basicBeans);

        stringAdapter.setOnItemClickListener((view, position) -> {
            showMsg("點選了" + basicBeans.get(position).getTitle());
        });
        stringAdapter.setOnItemLongClickListener((view, position) -> {
            showMsg("長按了" + basicBeans.get(position).getTitle());
            return true;
        });
        //配置擴充卡
        binding.rvText.setAdapter(stringAdapter);
        //配置布局管理器
        binding.rvText.setLayoutManager(new LinearLayoutManager(this));
    }
}      

看看運作效果

Android RecyclerView使用簡述

關于擴充卡中的點選事件的處理還有很多DataBinding的寫法,這裡就不一一說明了,下面進入進階使用。

五、RecyclerView下拉重新整理和上拉加載

  在日常使用中,RecyclerView的資料并不是一次性都加載出來的,會有分頁,重新加載等操作,而手機上操作就是下拉重新整理和上拉加載。

① 添加依賴庫

下拉重新整理的動作可以由一個庫來完成,在app的build.gradle中dependencies{}閉包中添加如下依賴:

implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'      

然後Sync Now,然後在activity_main.xml中增加一個按鈕,作為進入此功能的入口,代碼如下:

<Button
        android:id="@+id/btn_rv_refresh_load"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView 下拉重新整理 + 上拉加載"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rv_data_binding" />      

下面在MainActivity中onCreate()方法中增加如下代碼:

binding.btnRvRefreshLoad.setOnClickListener(v -> jumpActivity(RvRefreshLoadActivity.class));      

這裡我們還沒有這個RvRefreshLoadActivity的,建立它,對應的布局為activity_rv_refresh_load.xml,裡面的代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".RvRefreshLoadActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/ic_back"
        app:title="RecyclerView 下拉重新整理 + 上拉加載"
        app:titleTextColor="@color/white" />

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/refresh"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>      

  這裡的代碼也很簡單,通過一個SwipeRefreshLayout包裹住RecyclerView,這個控件可以感覺下拉的動作,下面我們來完成下拉重新整理。

② 下拉重新整理資料

進入RvRefreshLoadActivity中,修改代碼如下:

public class RvRefreshLoadActivity extends BasicActivity {

    private ActivityRvRefreshLoadBinding binding;

    private final List<String> strings = new ArrayList<>();
    private int lastVisibleItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityRvRefreshLoadBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        initView();
    }

    private void initView() {
        back(binding.materialToolbar);

        strings.addAll(getStrings());
        //擷取擴充卡執行個體
        BasicAdapter stringAdapter = new BasicAdapter(strings);
        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        //配置布局管理器
        binding.rvText.setLayoutManager(linearLayoutManager);
        //配置擴充卡
        binding.rvText.setAdapter(stringAdapter);    }
}      

  這部分的代碼相比你已經很熟悉了,因為前面已經出現過很多次,下面我們添加下拉動作的監聽,就在這個配置擴充卡代碼的下面添加就可以了,代碼如下:

.refresh.setOnRefreshListener(() -> {
            strings.clear();
            strings.addAll(getStrings());
            new Handler().postDelayed(() -> {
                stringAdapter.notifyDataSetChanged();
                binding.refresh.setRefreshing(false);
            }, 1000);

        });      

  這裡面的代碼很簡單,重新整理之前先清除清單的所有資料,然後添加新資料,增加了一個延時渲染資料的動作,渲染資料之後關閉重新整理動作。我們來看看效果圖:

Android RecyclerView使用簡述

③ 上拉加載更多

在下拉重新整理的代碼下面添加上拉加載更多的代碼,如下所示:

.rvText.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == stringAdapter.getItemCount()) {
                    if (stringAdapter.getItemCount() > 50) {
                        showMsg("已加載全部");
                    } else {
                        showMsg("加載更多");
                        new Handler().postDelayed(() -> {
                            strings.addAll(getStrings());
                            stringAdapter.notifyDataSetChanged();
                        }, 1000);
                    }
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //擷取最後一個可見Item的下标
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
            }
        });      

  這一段代碼就比較多了,說明一下,先看onScrolled回調方法,這是表示RecyclerView在滾動的回調,在onScrolled()方法中,我們通過linearLayoutManager可以得到最後一個可見Item的下标,然後回到onScrollStateChanged()回調方法中,這個方法表示滑動狀态改變,這裡判斷RecyclerView是否處于空閑中,同時判斷lastVisibleItem + 1 是否等于清單擴充卡中的Item個數,為什麼要+1?因為下标是從0開始的,這個判斷的意義就是知道目前清單是否滑動到底部了,是的話我們再處理是否需要加載更多資料,這裡我增加了一個條件,如果目前i擴充卡的item個數大于50則表示已經加載了全部,否則再添加新資料進去,經過解釋相信你能了解為什麼這麼做了,下面我們運作一下看看效果。

Android RecyclerView使用簡述

六、RecyclerView多布局使用

  在前面的使用中我們在操作寫擴充卡的代碼時,都是一個item布局,而有時候資料不同需要顯示的布局也不同,就存在多布局的情況,這種情況應該怎麼處理呢?其實也不難,下面我們來學習一下。

首先在com.llw.recyclerviewdemo建立一個RvMultipleLayoutsActivity,對應的布局是activity_rv_multiple_layouts.xml,裡面的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".RvMultipleLayoutsActivity">
    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/ic_back"
        app:title="RecyclerView 多布局使用"
        app:titleTextColor="@color/white" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>      

下面我們同樣在activity_main.xml中增加一個按鈕,代碼如下:

<Button
        android:id="@+id/btn_rv_multiple_layouts"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView 多布局使用"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rv_refresh_load" />      

然後在MainActivity中的onCreate()方法中跳轉到RvMultipleLayoutsActivity中,添加代碼如下:

binding.btnRvMultipleLayouts.setOnClickListener(v -> jumpActivity(RvMultipleLayoutsActivity.class));      

基本的準備工作就做好了,下面我們首先建立布局,例如消息清單,分别人和我自己布局方式。

① 建立布局Item

在layout下建立一個item_other_rv.xml,裡面的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:background="@drawable/ic_launcher_background"
        app:srcCompat="@drawable/ic_launcher_foreground" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:textColor="@color/black"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="@+id/imageView2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageView2"
        app:layout_constraintTop_toTopOf="@+id/imageView2" />
</androidx.constraintlayout.widget.ConstraintLayout>      

這是别人的消息布局,下面建立自己的消息布局,同樣在layout下建立item_myself_rv.xml,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:background="@drawable/ic_launcher_background"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_launcher_foreground" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:gravity="end"
        android:text="TextView"
        android:textColor="@color/black"
        app:layout_constraintBottom_toBottomOf="@+id/imageView2"
        app:layout_constraintEnd_toStartOf="@+id/imageView2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/imageView2" />
</androidx.constraintlayout.widget.ConstraintLayout>      

  這個布局因為消息是在右邊,是以我對TextView的android:gravity屬性做了調整,讓裡面的文字居右顯示,現在布局就建立好了,下面我們需要一個資料Bean。

② 建立資料Bean

在bean包下建立一個Message類,裡面的代碼如下:

public class Message {

    private int type;
    private String content;

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Message(int type, String content) {
        this.type = type;
        this.content = content;
    }
}      

  這個資料Bean還是很簡單的,就是用來區分消息的類型,還有消息的内容,隻是模拟一下而已,同樣我們需要提供一些假消息資料,可以直接在BasicActivity中添加,添加一個方法,代碼如下:

protected List<Message> getMessages() {
        List<Message> messages = new ArrayList<>();
        int num = (int) (1 + Math.random() * (20 - 10 + 1));
        for (int i = 0; i < num; i++) {
            int type = i % 2 == 0 ? 0 : 1;
            String content = type == 0 ? "今天你搞錢了嗎?" : "摸魚的時候就專心摸魚!";
            messages.add(new Message(type, content));
        }
        return messages;
    }      

這裡的代碼還是比較簡單的,就是區分一下别人和自己,顯示不同的類型和内容,下面就到了我們的重頭戲,擴充卡了。

③ 擴充卡

先說一下擴充卡中要做什麼,适配中要區分View類型,要建構不同的ViewHolder,在adapter包下建立一個MessageAdapter,裡面的代碼如下:

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

    private final List<Message> messages;

    private static final int OTHER = 0;
    private static final int MYSELF = 1;

    public MessageAdapter(List<Message> messages) {
        this.messages = messages;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder viewHolder;
        if (viewType == OTHER) {
            viewHolder = new OtherViewHolder(ItemOtherRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        } else {
            viewHolder = new MyselfViewHolder(ItemMyselfRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof OtherViewHolder) {
            ((OtherViewHolder) holder).otherBinding.tvContent.setText(messages.get(position).getContent());
        } else if (holder instanceof MyselfViewHolder) {
            ((MyselfViewHolder) holder).myselfBinding.tvContent.setText(messages.get(position).getContent());
        }
    }

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

    @Override
    public int getItemViewType(int position) {
        if (messages.get(position).getType() == 0) {
            return OTHER;
        } else {
            return MYSELF;
        }
    }

    public static class OtherViewHolder extends RecyclerView.ViewHolder {

        public ItemOtherRvBinding otherBinding;

        public OtherViewHolder(@NonNull ItemOtherRvBinding itemOtherRvBinding) {
            super(itemOtherRvBinding.getRoot());
            otherBinding = itemOtherRvBinding;
        }

    }

    public static class MyselfViewHolder extends RecyclerView.ViewHolder {

        public ItemMyselfRvBinding myselfBinding;

        public MyselfViewHolder(@NonNull ItemMyselfRvBinding itemMyselfRvBinding) {
            super(itemMyselfRvBinding.getRoot());
            myselfBinding = itemMyselfRvBinding;
        }

    }
}      

  這是完整的代碼,下面我來說明一下,首先我們看到有兩個内部類,​

​OtherViewHolder​

​​ 和​

​MyselfViewHolder​

​​ ,用于對應兩個不同的item布局,這個裡面的代碼沒啥說的,然後目前的​

​MessageAdapter​

​​繼承了​

​RecyclerView.Adapter<RecyclerView.ViewHolder>​

​​,這裡是​

​RecyclerView.ViewHolder​

​​而不是我們自己建立的我定義​

​OtherViewHolder​

​​ 和​

​MyselfViewHolder​

​​ ,這是因為我們需要在​

​onCreateViewHolder​

​​中去根據​

​ViewType​

​​不同建立不同的​

​ViewHolder​

​。關鍵代碼如下所示:

@NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder viewHolder;
        if (viewType == OTHER) {
            viewHolder = new OtherViewHolder(ItemOtherRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        } else {
            viewHolder = new MyselfViewHolder(ItemMyselfRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        }
        return viewHolder;
    }      

  下面來看​

​ViewType​

​​的判斷了​

​OTHER​

​​和​

​MYSELF​

​​兩個常量作為類型判斷,在​

​getItemViewType()​

​​回調方法中進行處理,然後傳回不同的​

​ViewType​

​,之前我們一直沒有用到過這個方法,因為item是單一的,現在不是了,所如果你的item無論是多少個類型,都可以這麼去做。關鍵代碼如下所示:

@Override
    public int getItemViewType(int position) {
        if (messages.get(position).getType() == 0) {
            return OTHER;
        } else {
            return MYSELF;
        }
    }      

  現在​

​ViewType​

​​和​

​ViewHolder​

​​都知道了,下面就是資料渲染的處理了,也很簡單。因為在前面建立​

​ViewHolder​

​​時用了不同的内部類,那麼在資料渲染的時候也可以通過這個來判斷,目前渲染的是哪一個​

​ViewHolder​

​中的視圖,關鍵代碼如下所示:

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof OtherViewHolder) {
            ((OtherViewHolder) holder).otherBinding.tvContent.setText(messages.get(position).getContent());
        } else if (holder instanceof MyselfViewHolder) {
            ((MyselfViewHolder) holder).myselfBinding.tvContent.setText(messages.get(position).getContent());
        }
    }      

其他的一些代碼就沒有什麼好解釋的了,下面來顯示資料,修改一下​

​RvMultipleLayoutsActivity​

​的代碼,如下所示:

public class RvMultipleLayoutsActivity extends BasicActivity {

    private ActivityRvMultipleLayoutsBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityRvMultipleLayoutsBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        initView();
    }

    private void initView() {
        back(binding.materialToolbar);
        binding.rvText.setLayoutManager(new LinearLayoutManager(this));
        binding.rvText.setAdapter(new MessageAdapter(getMessages()));
    }
}      

這基本上沒什麼好說明,如果你是一路看下來的話,下面我們運作一下:

Android RecyclerView使用簡述

這裡隻是一個簡單的示範多布局的功能,實際上的功能會比這個複雜,但是邏輯是一樣的。

七、RecyclerView多級清單使用

  RecyclerView的item有時候又會包裹一個RecyclerView,類似于QQ的分組,分組是一個清單,分組的item可以展開,展開後是一個清單,裡面是顯示該分組下的人員的,這個功能我們就可以通過RecyclerView嵌套RecyclerView的方式完成二級清單,下面來看看應該怎麼做。

首先要做的還是有一個入口,在​

​activity_main.xml​

​中新增一個按鈕,代碼如下:

<Button
        android:id="@+id/btn_rv_multilevel_list"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView 多級清單使用"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rv_multiple_layouts" />      

然後在MainActivity的​

​onCreate()​

​方法中增加如下代碼:

.btnRvMultilevelList.setOnClickListener(v -> jumpActivity(RvMultilevelListActivity.class));      

在​

​com.llw.recyclerviewdemo​

​包下建立一個RvMultilevelListActivity,對應的布局是activity_rv_multilevel_list.xml,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".RvMultilevelListActivity">
    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/ic_back"
        app:title="RecyclerView 多級清單使用"
        app:titleTextColor="@color/white" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_group"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>      

① 建立布局Item

這裡我們同樣有兩個item,但是關系上是父子,不是同級的,在layout下建立一個​

​item_group_rv.xml​

​,裡面的代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/group"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/white"
        android:foreground="?attr/selectableItemBackground"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/iv_flag"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:src="@drawable/ic_right" />

        <TextView
            android:id="@+id/tv_group_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ellipsize="end"
            android:singleLine="true"
            android:text="分組名"
            android:textColor="@color/black" />
    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_contacts"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/group" />
</androidx.constraintlayout.widget.ConstraintLayout>      

  這個item布局裡面就是放了RecyclerView,正常情況下這個RecyclerView隐藏,可以通過點選group的布局控制RecyclerView顯示或隐藏,裡面還用了一個圖示來增加顯示和隐藏的效果,在drawable下新增​

​ic_right.xml​

​,代碼如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:autoMirrored="true"
    android:tint="#000000"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M10,17l5,-5 -5,-5v10z" />
</vector>      

再增加一個​

​ic_down.xml​

​,代碼如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:tint="#000000"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M7,10l5,5 5,-5z" />
</vector>      

下面我們在layout下再建立一個​

​item_contacts_rv.xml​

​,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tv_contacts_name"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:layout_marginBottom="1dp"
    android:background="#F8F8F8"
    android:gravity="center_vertical"
    android:paddingStart="24dp"
    android:text="聯系人"
    android:textColor="@color/black"
    tools:ignore="RtlSymmetry" />      

這個聯系人Item就比較簡單了,隻有一個TextView。布局item有了,下面就是資料了。

② 建立資料Bean

這個資料Bean,就比較簡單了,隻要有分組名和聯系人名就可以了,在bean包下建立一個​

​Group​

​類,代碼如下:

public class Group {

    private String name;
    private List<Contacts> contacts;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Contacts> getContacts() {
        return contacts;
    }

    public void setContacts(List<Contacts> contacts) {
        this.contacts = contacts;
    }

    public Group(String name, List<Contacts> contacts) {
        this.name = name;
        this.contacts = contacts;
    }

    public static class Contacts {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Contacts(String name) {
            this.name = name;
        }
    }
}      

然後同樣可以在BasicActivity中增加一個方法,提供分組資料,代碼如下:

protected List<Group> getGroups() {
        List<Group> groups = new ArrayList<>();
        int groupNum = (int) (1 + Math.random() * (20 - 10 + 1));
        for (int i = 0; i < groupNum; i++) {
            List<Group.Contacts> contacts = new ArrayList<>();
            int contactsNum = (int) (1 + Math.random() * (20 - 10 + 1));
            for (int j = 0; j < contactsNum; j++) {
                contacts.add(new Group.Contacts("搞錢" + (j + 1) + "号"));
            }
            groups.add(new Group("搞錢" + (i + 1) + "組", contacts));
        }
        return groups;
    }      

這個代碼就不用說明了吧,下面進入重頭戲,擴充卡。

③ 擴充卡

  這裡的擴充卡有兩個,一個用來顯示分組,一個用來顯示聯系人,從易到難,先來看聯系人的,在​

​adapter​

​​包下建立一個​

​ContactsAdapter​

​類,代碼如下:

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

    private final List<Group.Contacts> contacts;

    public ContactsAdapter(List<Group.Contacts> contacts) {
        this.contacts = contacts;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(ItemContactsRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.binding.tvContactsName.setText(contacts.get(position).getName());
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        ItemContactsRvBinding binding;

        public ViewHolder(@NonNull ItemContactsRvBinding itemContactsRvBinding) {
            super(itemContactsRvBinding.getRoot());
            binding = itemContactsRvBinding;
        }
    }
}      

這裡面就沒什麼好說,你都見過很多次了,下面着重來看分組擴充卡,在adapter包下建立一個GroupAdapter類,裡面的代碼如下:

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

    private final List<Group> groups;

    public GroupAdapter(List<Group> groups) {
        this.groups = groups;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ViewHolder viewHolder = new ViewHolder(ItemGroupRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        viewHolder.binding.group.setOnClickListener(v -> {
            //是否顯示
            boolean isShow = viewHolder.binding.rvContacts.getVisibility() == View.VISIBLE;
            //修改圖示
            viewHolder.binding.ivFlag.setImageDrawable(isShow ?
                    ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_right) : ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_down));
            //顯示或隐藏聯系人清單
            viewHolder.binding.rvContacts.setVisibility(isShow ? View.GONE : View.VISIBLE);
        });
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Group group = groups.get(position);
        holder.binding.tvGroupName.setText(group.getName());
        holder.binding.rvContacts.setAdapter(new ContactsAdapter(group.getContacts()));
        holder.binding.rvContacts.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext()));
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemGroupRvBinding binding;

        public ViewHolder(@NonNull ItemGroupRvBinding itemGroupRvBinding) {
            super(itemGroupRvBinding.getRoot());
            binding = itemGroupRvBinding;
        }
    }
}      

這個分組擴充卡的代碼就着重說明一下,怎麼控制聯系人清單顯示或隐藏,在onCreateViewHolder()方法中,增加了一個點選事件,核心代碼如下:

.binding.group.setOnClickListener(v -> {
            //是否顯示
            boolean isShow = viewHolder.binding.rvContacts.getVisibility() == View.VISIBLE;
            //修改圖示
            viewHolder.binding.ivFlag.setImageDrawable(isShow ?
                    ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_right) : ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_down));
            //顯示或隐藏聯系人清單
            viewHolder.binding.rvContacts.setVisibility(isShow ? View.GONE : View.VISIBLE);
        });      

  在建立分組item布局的時候我設定RecyclerView為隐藏的,在點選group所在的LinearLayout布局時,對RecyclerView是否隐藏做判斷,首先是修改圖示,然後是修改RecyclerView是顯示還是隐藏,也是比較簡單的代碼,但是有效,這裡的點選事件處理在擴充卡中處理會更簡單,是以就直接處理了。下面就是在分組擴充卡加載聯系人清單資料了,核心代碼如下所示:

@Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Group group = groups.get(position);
        holder.binding.tvGroupName.setText(group.getName());
        holder.binding.rvContacts.setAdapter(new ContactsAdapter(group.getContacts()));
        holder.binding.rvContacts.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext()));
    }      

這個代碼是否似曾相似呢?就是這麼簡單,不要把事情想複雜了,擴充卡中其他的就沒有什麼好說的了,下面我們修改一下RvMultilevelListActivity的代碼,如下所示:

public class RvMultilevelListActivity extends BasicActivity {

    private ActivityRvMultilevelListBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityRvMultilevelListBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        initView();
    }

    private void initView() {
        back(binding.materialToolbar);
        binding.rvGroup.setAdapter(new GroupAdapter(getGroups()));
        binding.rvGroup.setLayoutManager(new LinearLayoutManager(this));
    }
}      

最後我們運作看看效果。

Android RecyclerView使用簡述

  其中這種二級清單還有操作方式,就是當你展開其中一個分組時,其他的分組如果有展開的那麼就需要收縮,也就是說同一時間隻有一個分組展開,你可以想想要怎麼做。

八、RecyclerView動态更改資料

  之前我們顯示資料都是直接顯示的,後面在使用過程中并沒有對資料進行更改,那麼下面我們來進行更改試試看。首先還是添加入口,在​

​activity_main.xml​

​中新增一個按鈕,代碼如下:

<Button
        android:id="@+id/btn_rv_dynamically_change_data"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView 動态更改資料"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rv_multilevel_list" />      

然後在MainActivity中​

​onCreate()​

​方法中增加如下代碼:

.btnRvDynamicallyChangeData.setOnClickListener(v -> jumpActivity(RvDynamicallyChangeActivity.class));      

下面在​

​com.llw.recyclerviewdemo​

​​包下建立RvDynamicallyChangeActivity,對應布局​

​activity_rv_dynamically_change.xml​

​,布局代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".RvDynamicallyChangeActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/ic_back"
        app:title="RecyclerView 動态更改資料"
        app:titleTextColor="@color/white">

        <TextView
            android:id="@+id/tv_edit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:gravity="center"
            android:padding="16dp"
            android:text="編輯"
            android:textColor="@color/white" />
    </com.google.android.material.appbar.MaterialToolbar>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/tv_select_num"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />

    <TextView
        android:id="@+id/tv_select_num"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:gravity="center"
        android:visibility="gone"
        android:padding="16dp"
        android:text="選中0個"
        android:textColor="@color/black"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>      

  基本的都準備好了,下面我們進行代碼的編寫,先說一下要做的内容是什麼?首先是一個清單,這個清單中的item可以選中,選中或取消選中,都需要更改選中記錄,聽起來是不是很簡單呢?這裡面涉及到一個Activity和Adapter互動的過程。

① 建立布局item和資料Bean

首先我們還是從建立布局item開始,在layout下建立一個item_select_rv.xml,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/item_select"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:layout_marginBottom="1dp"
    android:background="@color/white"
    android:foreground="?attr/selectableItemBackground"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:text="TextView"
        android:textColor="@color/black"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <CheckBox
        android:id="@+id/cb_select"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="false"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>      

這裡的選中框我是隐藏的,需要在Activity中控制Adapter中的選中框顯示或隐藏,下面建立資料Bean,在bean包下建立SelectBean類,代碼如下:

public class SelectBean {

    private boolean select;
    private String content;

    public boolean isSelect() {
        return select;
    }

    public void setSelect(boolean select) {
        this.select = select;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public SelectBean(boolean select, String content) {
        this.select = select;
        this.content = content;
    }
}      

然後同樣在BasicActivity中增加一個方法,提供資料,代碼如下:

protected List<SelectBean> getSelects() {
        List<SelectBean> selectBeans = new ArrayList<>();
        int num = (int) (1 + Math.random() * (50 - 10 + 1));
        for (int i = 0; i < num; i++) {
            selectBeans.add(new SelectBean(false, "第 " + i + " 條資料"));
        }
        return selectBeans;
    }      

下面我們先顯示資料,然後由Activity去控制Adapter中item布局變化。

② 擴充卡和顯示資料

在adapter包下建立一個SelectAdapter類,裡面的代碼如下所示:

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

    private final List<SelectBean> selectBeans;

    private boolean show;

    public boolean isShow() {
        return show;
    }

    @SuppressLint("NotifyDataSetChanged")
    public void setShow(boolean show) {
        this.show = show;
        notifyDataSetChanged();
    }

    public SelectAdapter(List<SelectBean> selectBeans) {
        this.selectBeans = selectBeans;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(ItemSelectRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        SelectBean selectBean = selectBeans.get(position);
        holder.binding.tvContent.setText(selectBean.getContent());
        holder.binding.cbSelect.setChecked(selectBean.isSelect());
        holder.binding.cbSelect.setVisibility(isShow() ? View.VISIBLE : View.GONE);
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemSelectRvBinding binding;

        public ViewHolder(@NonNull ItemSelectRvBinding itemSelectRvBinding) {
            super(itemSelectRvBinding.getRoot());
            binding = itemSelectRvBinding;
        }
    }
}      

  注意看這個擴充卡中,我增加了一個​

​show​

​​變量,用來控制擴充卡Item的選中框是否顯示,提供了show變量的get和set方法,在set方法中指派之後調用​

​notifyDataSetChanged()​

​​方法對擴充卡進行重新整理,這個方法會觸發​

​onBindViewHolder()​

​,在這個方法中可以看到根據show的狀态顯示還是隐藏選中框。

下面我們修改RvDynamicallyChangeActivity,代碼如下:

public class RvDynamicallyChangeActivity extends BasicActivity {

    private ActivityRvDynamicallyChangeBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityRvDynamicallyChangeBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        initView();
    }

    private void initView() {
        back(binding.materialToolbar);
        SelectAdapter selectAdapter = new SelectAdapter(getSelects());
        binding.rvText.setLayoutManager(new LinearLayoutManager(this));
        binding.rvText.setAdapter(selectAdapter);

        binding.tvEdit.setOnClickListener(v -> {
            boolean show = selectAdapter.isShow();
            selectAdapter.setShow(!show);
            binding.tvSelectNum.setVisibility(show ? View.VISIBLE : View.GONE);
            binding.tvEdit.setText(show ? "取消" : "編輯");
        });
    }
}      

在點選編輯時調用擴充卡​

​setShow()​

​​,然後控制底部的TextView顯示,順便修改一下​

​tvEdit​

​文字,運作一下看看。

Android RecyclerView使用簡述

③ 重新整理選中位置資料

  下面我們需要點選Item時,修改資料,重新整理擴充卡,達到CheckBox選中或者取消選中的目的,首先我們需要修改​

​SelectAdapter​

​代碼,如下所示:

private OnItemClickListener listener;

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }      

增加點選監聽回調方法,然後修改​

​onCreateViewHolder()​

​方法中的代碼,如下所示:

@NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ViewHolder viewHolder = new ViewHolder(ItemSelectRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        viewHolder.binding.itemSelect.setOnClickListener(v -> {
            if (listener != null) {
                listener.onItemClick(v, viewHolder.getAdapterPosition());
            }
        });
        return viewHolder;
    }      

就是添加一個點選事件而已,下面回到RvDynamicallyChangeActivity,首先增加一個變量用來記錄選中的個數,代碼如下:

private int selectNum = 0;      

然後在​

​initView()​

​方法,代碼如下所示:

private void initView() {
        back(binding.materialToolbar);

        List<SelectBean> selects = getSelects();
        SelectAdapter selectAdapter = new SelectAdapter(selects);
        selectAdapter.setOnItemClickListener((view, position) -> {
            boolean select = selects.get(position).isSelect();
            //更改資料
            selects.get(position).setSelect(!select);
            //重新整理擴充卡
            selectAdapter.notifyItemChanged(position);
            if (!select) selectNum++;
            else selectNum--;
            binding.tvSelectNum.setText(String.format(Locale.getDefault(), "選中%d個", selectNum));
        });
        binding.rvText.setLayoutManager(new LinearLayoutManager(this));
        binding.rvText.setAdapter(selectAdapter);

        ...
    }      

  省略号表示之前的tvEdit點選事件,這裡修改的核心内容就是擴充卡item的點選事件,點選時擷取目前位置對應資料的選中狀态,然後更改選中狀态,通過​

​notifyItemChanged()​

​表示重新整理擴充卡資料,不過這裡隻重新整理目前位置的資料,然後記錄選中的個數,最後顯示選中個數,就是這麼簡單,下面我們運作一下看看。

Android RecyclerView使用簡述

  那麼到這裡就完了嗎?其實還沒有,我們還需要注意到這個編輯和取消的處理,例如我現在是編輯狀态下,我選擇了幾個,然後我不取消勾選,而是推出編輯,那麼這時候則需要在推出編輯的時候也清空所有選中的Item,而在編輯的情況下才能選中。

首先在RvDynamicallyChangeActivity中定義一個變量

private boolean show = false;      

然後修改Item點選中事件中加上一個判斷,代碼如下所示:

.setOnItemClickListener((view, position) -> {
            if (!show) {
                boolean select = selects.get(position).isSelect();
                //更改資料
                selects.get(position).setSelect(!select);
                //重新整理擴充卡
                selectAdapter.notifyItemChanged(position);
                if (!select) selectNum++;
                else selectNum--;
                binding.tvSelectNum.setText(String.format(Locale.getDefault(), "選中%d個", selectNum));
            }
        });      

然後修改tvEdit點選事件,代碼如下:

.tvEdit.setOnClickListener(v -> {
            show = selectAdapter.isShow();
            selectAdapter.setShow(!show);
            binding.tvSelectNum.setVisibility(show ? View.GONE : View.VISIBLE);
            binding.tvEdit.setText(show ? "編輯" : "取消");
            boolean cancel = !show;
            if (!cancel) {
                for (SelectBean select : selects) {
                    select.setSelect(false);
                }
                selectAdapter.notifyDataSetChanged();
                selectNum = 0;
                binding.tvSelectNum.setText(String.format(Locale.getDefault(), "選中%d個", selectNum));
            }
        });      

  這裡的代碼就是取消的時候周遊清單的每一項,設定狀态為false,然後通過​

​notifyDataSetChanged()​

​方法重新整理擴充卡所有資料,最後修改一下選中的數字和顯示文字,這樣就結束了,看看效果圖如何。

Android RecyclerView使用簡述

九、RecyclerView左右滑動和上下拖動

  在操作RecyclerView的時候,我們還會有例如Item側滑删除這樣的操作,或者上下拖動更改Item的位置。下面來看看怎麼做,首先同樣是增加入口,說實話這部分代碼我是真的不想加,但是我又擔心有人看不懂,是以還是加上吧。在activity_main.xml中增加一個按鈕,代碼如下:

<Button
        android:id="@+id/btn_rv_swipe_drag"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="RecyclerView 左右滑動和上下拖動"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rv_dynamically_change_data" />      

然後在MainActivity中​

​onCreate()​

​方法中增加如下代碼:

.btnRvSwipeDrag.setOnClickListener(v -> jumpActivity(RvSwipeDragActivity.class));      

下面在​

​com.llw.recyclerviewdemo​

​​包下建立RvSwipeDragActivity,對應布局​

​activity_rv_swipe_drag.xml​

​,布局代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    tools:context=".RvSwipeDragActivity">
    
    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/ic_back"
        app:title="RecyclerView 滑動和拖動"
        app:titleTextColor="@color/white" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_simple"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>      

這裡的擴充卡和資料Bean我就不需要重新建立了,直接用之前寫好的​

​BasicAdapter​

​就可以了。

① 顯示資料

首先我們先顯示資料清單,修改RvSwipeDragActivity中的代碼,如下所示:

public class RvSwipeDragActivity extends BasicActivity {

    private ActivityRvSwipeDragBinding binding;
    private final List<String> lists = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityRvSwipeDragBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        initView();
    }

    private void initView() {
        back(binding.materialToolbar);

        lists.addAll(getStrings());
        //擷取擴充卡執行個體
        BasicAdapter basicAdapter = new BasicAdapter(lists);
        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        //配置布局管理器
        binding.rvSimple.setLayoutManager(linearLayoutManager);
        //配置擴充卡
        binding.rvSimple.setAdapter(basicAdapter);
    }
}      

  這個代碼就很簡單了,相信你已經見到過很多次了,而添加滑動或者拖動的操作其實是很簡單的,主要就是​

​ItemTouchHelper​

​這個類,下面我們先實作左右滑動。

② ItemTouchHelper

在initView()添加如下代碼:

ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {

            @Override
            public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
                return 0;
            }

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

            @Override
            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
               
            }
        });
        //關聯recyclerView
        helper.attachToRecyclerView(binding.rvSimple);      

  在這裡通過實作​

​ItemTouchHelper.Callback()​

​​,重寫裡面的三個方法,​

​getMovementFlags()​

​​方法用于擷取移動标志,例如标志有上下左右,通常滑動時左右,拖動是上下左右。​

​onMove()​

​​方法用于拖動回調,​

​onSwiped()​

​方法用于滑動回調。

最後通過擷取的​

​helper​

​執行個體,然後關聯RecyclerView。

③ Item左右滑動

要實作左右滑動,首先需要設定移動标志位,也就是說需要修改​

​getMovementFlags()​

​方法的傳回值,代碼如下:

@Override
  public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
      //控制快速滑動的方向
        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(0, swipeFlags);
  }      

  這裡設定滑動的方向,使用​

​makeMovementFlags()​

​方法,裡面傳入了兩個參數,第一個參數是拖動辨別,第一個參數是滑動标志,設定為0就是不啟用。

int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;      

  這裡的swipeFlags的值是可以組合的,你是單獨設定​

​ItemTouchHelper.START​

​​或者​

​ItemTouchHelper.END​

​,也可以組合使用,START表示像左滑動,使用LEFT也行,END表示向右滑動,也可以使用RIGHT。

現在可以左右滑動了,那麼滑動回調中我們需要做什麼?需要移除清單資料,更新擴充卡,修改​

​onSwiped()​

​方法,代碼如下:

@Override
  public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
    int adapterPosition = viewHolder.getAdapterPosition();
    lists.remove(adapterPosition);
    basicAdapter.notifyItemRemoved(adapterPosition);
  }      

這裡看這個使用的​

​notifyItemRemoved()​

​方法作為擴充卡移除資料,下面運作看看效果。

Android RecyclerView使用簡述

滑動超過螢幕中間後動作就不能回彈了,才會執行滑動回調,下面我們添加上下拖動。

④ Item上下拖動

  添加拖動需要同樣需要設定移動标志。修改​

​getMovementFlags()​

​方法,代碼如下所示:

@Override
  public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
      //控制拖拽的方向
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END;
    //控制快速滑動的方向
        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
  }      

這裡設定了拖拽的辨別位,下面就是在拖動回調方法​

​onMove()​

​,實作拖動item的位置的更換,修改代碼如下所示:

@Override
  public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
      if (lists.size() > 0) {
          //擷取被拖拽的Item的Position
            int from = viewHolder.getAdapterPosition();
            //擷取目标Item的Position
            int endPosition = target.getAdapterPosition();
            //交換List集合中兩個元素的位置
            Collections.swap(lists, from, endPosition);
            //交換界面上兩個Item的位置
            basicAdapter.notifyItemMoved(from, endPosition);
      }
    return true;
  }      

這裡的​

​notifyItemMoved()​

​方法就是用于拖動,修改單個Item,現在我們同時支援了滑動和拖動,運作一下看看效果。

Android RecyclerView使用簡述

十、源碼

繼續閱讀