android 基于MVVM架构的超简便易用RecyclerView适配器Adapter封装(databingding方式)

赵英资
2023-12-01

先上git地址https://github.com/mazwu110/libhttpapiMvvmDemo

1. 适配器基类

/**
 * Created by mzw on 2019/7/25.
 */

public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<BindingViewHolder>
        implements BaseViewHolder.OnNotifyChangeListener {
    public static Context mContext;
    public LayoutInflater mLayoutInflater;
    public List<T> mRecordList = new ArrayList();
    public static final int TYPE_CONTENT = 1;

    public BaseRecyclerAdapter(Context context) {
        this.mContext = context;
        this.mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public BindingViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
        return setViewHolder(viewGroup);
    }

    @Override
    public void onBindViewHolder(BindingViewHolder holder, int position) {
        bindData(holder, position);
    }

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

    //空布局类型返回1,正常布局类型返回0
    @Override
    public int getItemViewType(int position) {
        return TYPE_CONTENT;
    }

    @Override
    public void onNotify() {
        //提供给BaseViewHolder方便刷新视图
        notifyDataSetChanged();
    }

    //添加第一页数据
    public void addRecordList(List<T> datas) {
        mRecordList.clear();
        if (null != datas) {
            mRecordList.addAll(datas);
        }

        notifyDataSetChanged();
    }

    // 添加更多数据
    public void addMoreRecordList(List<T> datas) {
        if (null != datas) {
            mRecordList.addAll(datas);
        }
        notifyDataSetChanged();
    }

    public void clearDatas() {
        mRecordList.clear();
        notifyDataSetChanged();
    }


    public T getItem(int position) {
        if (mRecordList != null && position < mRecordList.size()) {
            return mRecordList.get(position);
        } else {
            return null;
        }
    }

    //删除单条数据
    public void deletItem(T data) {
        mRecordList.remove(data);
        notifyDataSetChanged();
    }

    /**
     * 子类重写实现自定义ViewHolder
     */
    public abstract BindingViewHolder setViewHolder(ViewGroup parent);

    //用给定的 data 对 holder 的 view 进行赋值,交给子类自己实现
    public abstract void bindData(BindingViewHolder bindingViewHolder, int positon);


    // 图片加载
    @BindingAdapter({"app:imageUrl"})
    public static void loadImageFromUrl(ImageView imageView, String url) {
        if (url.startsWith("http") || url.startsWith("HTTP")) { // 网络图片
            QApp.mImageLoader.displayNetImage(imageView, url);
        } else { // base64图片
            //QApp.mImageLoader.addBase64Image(url, imageView);
        }
    }

    // 解决为图片赋值本地资源时 运行效果不见图片的问题
    @BindingAdapter("android:src")
    public static void setSrc(ImageView view, int resId) {
        view.setImageResource(resId);
    }

    @BindingAdapter("android:textColor")
    public static void setTextColor(TextView view, int resId) {
        view.setTextColor(mContext.getResources().getColor(resId));
    }
}

 

BindingViewHolder类
public class BindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
    private T mBinding;
    public BindingViewHolder(@NonNull T binding) {
        super(binding.getRoot());
        mBinding = binding;
    }
    public T getBinding() {
        return mBinding;
    }
}

 

超级基类,所谓超级基类即使用的时候其他适配器直接继承他即可

//SuperBaseAdapter 所有适配器基类
// add by mzw 2019-12-22
public abstract class SuperBaseAdapter<T> extends BaseRecyclerAdapter<T> {
    private int layoutId;

    public SuperBaseAdapter(Context context, int layoutId) {
        super (context);
        this.layoutId = layoutId;
    }

    @Override
    public BindingViewHolder setViewHolder(ViewGroup viewGroup) {
        ViewDataBinding binding = DataBindingUtil.inflate (mLayoutInflater, layoutId, viewGroup, false);
        return new BindingViewHolder (binding);
    }

    @Override
    public void bindData(BindingViewHolder bindingViewHolder, int positon) {
        T item = mRecordList.get (positon);
        ViewDataBinding binding = bindingViewHolder.getBinding ();
        binding.setVariable (BR.item, item); // item布局中名称要和这个设置的一致,否则这个基类不能用,或者自己改item为布局中的名称,以下类似
        binding.setVariable (BR.click, getClickListener (item, positon, binding)); // 设置点击事件,item布局中名称要和这个设置的一致,否则这个基类不能用
        binding.executePendingBindings (); // 防止闪烁
    }

    //由子类实现点击事件,然后子类自己在布局中绑定即可
    public abstract AdapterClickListener getClickListener(T item, int postion, ViewDataBinding binding);
}

 

点击超类:

public abstract class AdapterClickListener {
}

欺骗编译器布局item_my_base.xml,要不SuperBaseAdapter编译不会通过

布局内容

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

    <data>

        <!-- 此布局用来欺骗SuperBaseAdapter,让他能编译,骗过编译器的功能,不能少-->
        <variable
            name="item"
            type="com.httpapi.BaseResultEntity" />

        <variable
            name="click"
            type="com.qlib.libadapter.AdapterClickListener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@null"
        android:orientation="vertical">

    </LinearLayout>
</layout>
MainActivity
// 所谓的MVVM模式,M就是获取数据的一个独立类,比如做实际的网络请求等,
// V就是界面显示的一个类,说白了就是MainActivity类似的界面,VM其实就是
// 从MainActivity分离出来,然后用来处理数据罢了,注意如果在VM中引用到了Activity中的视图控件等,记得释放
// 这里使用谷歌的AAC框架,其实就是典型的MVVM框架来解析下如何使用
// 遇到任何问题,可以加Q一起探讨交流  QQ:315145320 或者发邮箱
public class MainActivity extends BaseActivity {
    // 注意: 必须要在布局的根节点上增加<layout></layout>,要不下面这个家伙出不来
    // private 多余的,默认就是,没必要写了,除了private外,其他访问权限需要写哦
    ActivityMainBinding mBinding;
    MainActivityVM VM;
    WeatherAdapter mAdapter;
    UserInfoAdapter tAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 加载界面
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        init();
    }

    // 其他初始化
    void init() {
        // 注意 mAdapter 顺序,有列表的时候才需要
        mAdapter = new WeatherAdapter(this, R.layout.item_weather);
        VM = new MainActivityVM(this, mAdapter); // 除了这一句外,其他可以移到MainActivityVM中,以减少Activity的臃肿,这里就不移了,测试代码而已
        // 注意 lambda表达式不支持Java8以下的,所以JDK必须是8及以上的哦,要不会报错
        VM.getTestData().observe(this, data -> mBinding.setUserName(data));
        // 设置监听
        mBinding.setQClickListener(VM.getQClickListener());

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        mBinding.recyclerView.setLayoutManager(layoutManager); // 设置recyclerview布局方式
        mBinding.recyclerView.setAdapter(mAdapter); //给recyclerview 添加适配器

        // 这个是超级封装的适配器
        tAdapter = new UserInfoAdapter (this, R.layout.item_my_weather);
        LinearLayoutManager layoutManagerT = new LinearLayoutManager(this);
        mBinding.recyclerViewTest.setLayoutManager(layoutManagerT); // 设置recyclerview布局方式
        mBinding.recyclerViewTest.setAdapter(tAdapter); //给recyclerview 添加适配器

        List<QTestBean> list = new ArrayList<> ();
        for (int i = 0; i < 10; i++) {
            QTestBean bean = new QTestBean();
            bean.setId ("" + i);
            bean.setName ("张三" + i);
            bean.setAge ("" + (20 + i));
            list.add (bean);
        }

        tAdapter.addRecordList (list);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        VM.freeObject();
    }
}
MainActivity 辅助类 MainActivityVM
// 所有的数据获取我统一用了泛型,大家可以看下我对MVVM中MEDEL的封装,及里面对
//retrofit2 rxjava2, ApiService 的封装。可以根据自身情况进行修改,但是这里只需要一个泛型
//model就行了,避免创建太多的model 代码文件太多不好维护,但是VM是需要多个的,
// 就像每一个界面需要一个Activity一样,有问题发我邮箱或者加Q吧 QQ:315145320
public class MainActivityVM extends BaseViewModel implements OnHttpApiListener {
    QClickListener qClickListener;
    WeatherAdapter mAdapter;
    int index = 0;

    // 动态数据绑定
    MutableLiveData<TestDataBean> testData;

    public MutableLiveData<TestDataBean> getTestData() {
        return testData;
    }

    // 还可以在构造函数中增加你乐意的参数或者控件传过来
    public MainActivityVM(Context context, WeatherAdapter adapter) {
        this.mContext = context;
        this.mAdapter = adapter;
        qClickListener = new QClickListener();
        testData = new MutableLiveData<>(); // 记得初始化,要不会报错哦
    }

    // 返回给Activity绑定点击事件
    public QClickListener getQClickListener() {
        return qClickListener;
    }

    public class QClickListener {
        // key-value get 获取后台数据
        public void doGet(View view) {
            Map<String, Object> params = new HashMap<>();
            params.put("city", "北京");
            params.put("key", "0132423b3e085efed24b7b8f00d83a91");
            // 第三个参数,需要用到哪个类解析数据结果就传哪个类进去就行,这里采用了泛型解析
            QHttpApi.doGet(Constants.getWeather, params, WeatherBean.class, HttpWhatConfig.CODE_10, MainActivityVM.this);
           // QHttpApi.doStrGet(Constants.getWeather, params, HttpWhatConfig.CODE_10, MainActivityVM.this);
        }

        // key-value post获取后台数据
        public void doPost(View view) {
            // 另外有QHttpApi.doStrGet方法可用,此返回是后台返回什么,解析出就直接返回给您,需要您自己接受了解析,包括CODE等都返回来了
            Map<String, Object> params = new HashMap<>();
            params.put("city", "上海");
            params.put("key", "0132423b3e085efed24b7b8f00d83a91");
            // 第三个参数,需要用到哪个类解析数据结果就传哪个类进去就行,这里采用了泛型解析
            //
            QHttpApi.doPost(Constants.getWeather, params, WeatherBean.class, HttpWhatConfig.CODE_11, MainActivityVM.this);
        }

        //post json格式的参数获取后台数据
        public void doJsonPost(View view) {
        }

        // 设置数据,可以通过网络获取
        public void getUserName(View view) {
            TestDataBean bean = new TestDataBean();
            index += 1;
            bean.setUsreName("张三" + index);

            // 更新数据,界面上也会同步更新
            testData.setValue(bean);
        }
    }

    @Override
    public void onSuccess(int what, Object data) {
        switch (what) {
            case HttpWhatConfig.CODE_10: {
                // 使用请求数据的时候的class反解析就行
                WeatherBean bean = (WeatherBean) data;
                ArrayList<FutureBean> list = bean.getFuture();
                // 清除数据,然后刷新, 如果您做的是分页,可以使用 mAdapter.addMoreRecordList(list);
                mAdapter.addRecordList(list);
                break;
            }
            // 数据格式一样 就拷贝上面的解析了
            case HttpWhatConfig.CODE_11:
                // 使用请求数据的时候的class反解析就行
                WeatherBean bean = (WeatherBean) data;
                ArrayList<FutureBean> list = bean.getFuture();
                // 清除数据,然后刷新, 如果您做的是分页,可以使用 mAdapter.addMoreRecordList(list);
                mAdapter.addRecordList(list);
                break;
        }
    }

    @Override
    public void onFailure(int what, String msg, int code) {
        showToast(msg);
    }

    public void freeObject() {
        if (qClickListener != null)
            qClickListener = null;

        if (mAdapter != null)
            mAdapter = null;
    }
}
TestDataBean
// 测试用的单个数据绑定
public class TestDataBean extends BaseObservable implements Serializable {
    String usreName;

    public String getUsreName() {
        return usreName;
    }

    public void setUsreName(String usreName) {
        this.usreName = usreName;
    }
}

 

WeatherBean
// 所有的字段必须要和后台返回的一致,包括后台返回的字段是对象的,名字也必须要统一,要不GSON解析不了
public class WeatherBean extends BaseResultEntity<WeatherBean> {
    String city;// 字段必须要和后台返回的一致,要不GSON解析不了
    RealtimeBean realtime; // 字段必须要和后台返回的一致,要不GSON解析不了
    ArrayList<FutureBean> future;// 字段必须要和后台返回的一致,要不GSON解析不了

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public RealtimeBean getRealtime() {
        return realtime;
    }

    public void setRealtime(RealtimeBean realtime) {
        this.realtime = realtime;
    }

    public ArrayList<FutureBean> getFuture() {
        return future;
    }

    public void setFuture(ArrayList<FutureBean> future) {
        this.future = future;
    }
}
// 基础数据解析类
public class RealtimeBean extends BaseObservable implements Serializable {
    String temperature;
    String humidity;
    String info;
    String wid;
    String direct;
    String power;
    String aqi;

    public String getTemperature() {
        return temperature;
    }

    public void setTemperature(String temperature) {
        this.temperature = temperature;
    }

    public String getHumidity() {
        return humidity;
    }

    public void setHumidity(String humidity) {
        this.humidity = humidity;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    public String getWid() {
        return wid;
    }

    public void setWid(String wid) {
        this.wid = wid;
    }

    public String getDirect() {
        return direct;
    }

    public void setDirect(String direct) {
        this.direct = direct;
    }

    public String getPower() {
        return power;
    }

    public void setPower(String power) {
        this.power = power;
    }

    public String getAqi() {
        return aqi;
    }

    public void setAqi(String aqi) {
        this.aqi = aqi;
    }
}
FutureBean
public class FutureBean extends BaseObservable implements Serializable {
    String date;
    String temperature;
    String weather;
    WidBean wid;
    String direct;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getTemperature() {
        return temperature;
    }

    public void setTemperature(String temperature) {
        this.temperature = temperature;
    }

    public String getWeather() {
        return weather;
    }

    public void setWeather(String weather) {
        this.weather = weather;
    }

    public WidBean getWid() {
        return wid;
    }

    public void setWid(WidBean wid) {
        this.wid = wid;
    }

    public String getDirect() {
        return direct;
    }

    public void setDirect(String direct) {
        this.direct = direct;
    }
}

 

主布局文件

<?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:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <!-- 可以导入任意包,然后调用 -->
        <import type="com.libhttpapimvvmdemo.R" />

        <variable
            name="userName"
            type="com.libhttpapimvvmdemo.javaBean.TestDataBean" />

        <variable
            name="qClickListener"
            type="com.libhttpapimvvmdemo.viewModel.MainActivityVM.QClickListener" />
    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="none">

        <!--习惯用约束布局,减少嵌套,提高界面绘制性能 -->
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".view.MainActivity">

            <EditText
                android:id="@+id/edt_top"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="动态绑定内容展示"
                android:text="@{userName.usreName}"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <Button
                android:id="@+id/btn_left"
                android:layout_width="0dp"
                android:layout_height="44dp"
                android:layout_marginLeft="10dp"
                android:layout_marginTop="10dp"
                android:onClick="@{qClickListener.doGet}"
                android:text="get请求"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toLeftOf="@+id/btn_middle"
                app:layout_constraintTop_toBottomOf="@id/edt_top" />

            <Button
                android:id="@+id/btn_middle"
                android:layout_width="0dp"
                android:layout_height="44dp"
                android:layout_marginLeft="10dp"
                android:layout_marginTop="10dp"
                android:onClick="@{qClickListener.doPost}"
                android:text="post请求"
                app:layout_constraintLeft_toRightOf="@+id/btn_left"
                app:layout_constraintRight_toLeftOf="@+id/btn_right"
                app:layout_constraintTop_toBottomOf="@id/edt_top" />

            <Button
                android:id="@+id/btn_right"
                android:layout_width="0dp"
                android:layout_height="44dp"
                android:layout_marginLeft="10dp"
                android:layout_marginTop="10dp"
                android:layout_marginRight="10dp"
                android:onClick="@{qClickListener.getUserName}"
                android:text="获取用户明"
                app:layout_constraintLeft_toRightOf="@+id/btn_middle"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edt_top" />

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_marginTop="224dp"
                android:background="@color/white"
                app:layout_constraintHorizontal_bias="1.0"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/btn_right" />

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerViewTest"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:layout_marginTop="224dp"
                android:background="@color/white"
                app:layout_constraintHorizontal_bias="1.0"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/recyclerView" />

        </android.support.constraint.ConstraintLayout>
    </ScrollView>
</layout>
UserInfoAdapter
public class UserInfoAdapter extends SuperBaseAdapter<QTestBean> {
    public UserInfoAdapter(Context context, int layoutId) {
        super (context, layoutId);
    }

    // 不需要点击事件的直接重载,然后放null即可
    @Override
    public AdapterClickListener getClickListener(QTestBean item, int postion, ViewDataBinding binding) {
        // 每个item都有一个点击对象
        return new QClickListener(item, postion, binding);
    }

    // 点击事件逻辑处理
    public  class QClickListener extends AdapterClickListener {
        private QTestBean item;
        private int postion;
        private ItemMyWeatherBinding binding; // 可以直接使用binding.获取要控制的组件,方便灵活,不用亦不影响
        public QClickListener(QTestBean item, int postion, ViewDataBinding binding) {
            this.item = item;
            this.postion = postion;
            this.binding = (ItemMyWeatherBinding) binding;
        }
        public void showToast(View v){
            Toast.makeText (mContext, item.getName () + ";" + binding.btnTest.getText().toString (), Toast.LENGTH_LONG).show ();
        }
    }
}
WeatherAdapter
// SuperBaseAdapter 是我自己封装的,大家可以任意改,SuperBaseAdapter
// 使用处,只需要new WeatherAdapter,传入相应的参数即可使用,
// 如果还要实现点击事件,是要重写getClickListener,然后在其内部定义相应的方法,然后绑定到item布局即可,
// 点击事件具体可参考UserInfoAdapter中的,已实现
public class WeatherAdapter extends SuperBaseAdapter<FutureBean>  {

    public WeatherAdapter(Context context, int layoutId) {
        super (context, layoutId);
    }

    // 没有点击事件,不用实现下面此方法
    @Override
    public AdapterClickListener getClickListener(FutureBean item, int postion, ViewDataBinding binding) {
        return null;
    }

}

使用方式:自定义一个

适配器,名字随意,然后继承SuperBaseAdapter 即可,如 

public class WeatherAdapter extends SuperBaseAdapter<FutureBean>  {
}

然后在使用中:

// 这个是超级封装的适配器 R.layout.item_my_weather 是适配器的布局文件
UserInfoAdapter  tAdapter = new UserInfoAdapter (this, R.layout.item_my_weather);
LinearLayoutManager layoutManagerT = new LinearLayoutManager(this);
mBinding.recyclerViewTest.setLayoutManager(layoutManagerT); // 设置recyclerview布局方式
mBinding.recyclerViewTest.setAdapter(tAdapter); //给recyclerview 添加适配器

即可绑定了相关数据,都是算半个泛型绑定,这里所有的网络通信部分我git中也有封装,全是泛型解析数据。

特别注意,凡是所有继承SuperBaseAdapter的适配器,布局中绑定的数据选项名称必须是这两个,否则数据会绑定不上,第一个是数据对应的JavaBean名称item,另一个是点击事件对应的名称click,切记名字不能错,错了就永远绑定不了数据了。

 类似资料: