Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android 练手之旅——RecyclerView(二)improvement #13

Open
soapgu opened this issue Mar 14, 2021 · 0 comments
Open

Android 练手之旅——RecyclerView(二)improvement #13

soapgu opened this issue Mar 14, 2021 · 0 comments
Labels
Demo Demo 安卓 安卓

Comments

@soapgu
Copy link
Owner

soapgu commented Mar 14, 2021

前言

在学习RecyclerView的过程中也看到了一些对RecyclerView改造MVVM的第三方控件。但是上来直接拿来用而不做深入学习我这里是坚决反对的。自己动手一步步改造,也能加速对这个控件的理解。

Step1:MVVM的整合

首先回到我们的ViewModel

public class SearchViewModel extends ObservableViewModel {
    public SearchViewModel(@NonNull Application application) {
        super(application);
        this.setStringItems(new ArrayList<>(Arrays.asList("AAA", "BBB", "CCC", "DDD", "EEE", "FFF")));
    }

    private List<String> stringItems;

    @Bindable
    public List<String> getStringItems() {
        return this.stringItems;
    }

    public void setStringItems(List<String> stringItems) {
        this.stringItems = stringItems;
        this.notifyPropertyChanged(BR.stringItems);
    }
}

我们为我们的SearchActivity配上他的灵魂伴侣

数据集对象有了,但是怎么把ViewModel的StringItems和View层的RecyclerView产生关联那?
答案是我们的自定义属性,RecyclerView不给我们提供itemsSource我们就自己造一个,这个起名让学习过WPF的同学产生强烈的代入感。

@BindingAdapter("itemsSource")
    public static void setItems(RecyclerView recyclerView , List<String> items ){
        CustomAdapter adapter = new CustomAdapter( items.toArray( new String[0]) );
        recyclerView.setAdapter( adapter );
    }

这里有个巨坑,BindingAdapter的属性命名必须小写开头,如果完全用情怀“ItemsSource”是行不通的。作为知识点需要牢记。
回到activity_search.xml

<data>
        <variable
            name="datacontext"
            type="com.soapdemo.photohunter.viewmodels.SearchViewModel" />
    </data>

增加绑定变量datacontext,一样一样,情怀

<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/data_list"
            app:itemsSource="@{datacontext.stringItems}"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="100dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView" />

recyclerview上加上itemsSource的绑定
最后是SearchActivity部分完成View层和ViewModel层的装配

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SearchViewModel viewModel = new ViewModelProvider(this,
                ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()))
                .get(SearchViewModel.class);
        ActivitySearchBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_search);
        binding.dataList.setLayoutManager( new LinearLayoutManager( this ) );
        binding.setDatacontext( viewModel );
    }

第一步改造完成

Step 2 数据泛型化改造

BindingAdapter还有很大的缺陷,目前我只能支持String的列表,难道我一个数据类型配一个BindingAdapter和一个RecyclerView的
Adapter。需要继续改

public class CustomAdapter<T> extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
    private List<T> mDataSet;

    public CustomAdapter(List<T> dataSet) {
        mDataSet = dataSet;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        TextRowItemBinding binding = DataBindingUtil.getBinding(holder.itemView);
        assert binding != null;
        binding.setContent(  (String)this.mDataSet.get(position));
        binding.executePendingBindings();
    }

CustomAdapter改成泛型类,如上。只是
binding.setContent( (String)this.mDataSet.get(position));
这行代码非常的扎眼,先过。

 @BindingAdapter("itemsSource")
    public static <T> void setItems(RecyclerView recyclerView , List<T> items  ){
        CustomAdapter<T> adapter = new CustomAdapter<>( items );
        recyclerView.setAdapter( adapter );
    }

BindingAdapter也改成可以传泛型的List。
代码抽象度一下上了一层,泛型化改造完成
未完待续~~~~

Step 3 ItemTemplate的实现

数据泛型完成了,CustomAdapter还有两处扎眼的地方
TextRowItemBinding binding = DataBindingUtil.inflate(inflater,R.layout.text_row_item,parent, false );
binding.setContent( (String)this.mDataSet.get(position));
也就是说我Item呈现的View现在还是写死的,我往View传递的数据也是死的。
RecyclerView还看不到ItemTemplate的实现,我试试看能不能加上来实现Item的View的可配置化
首先定义ItemTemplate

public class ItemTemplate {
    private int variableId;
    @LayoutRes
    private int templateId;

    @NonNull
    public static ItemTemplate of(int variableId, @LayoutRes int templateId) {
        return new ItemTemplate(variableId, templateId);
    }

    public ItemTemplate( int variableId , @LayoutRes int templateId){
        this.variableId = variableId;
        this.templateId = templateId;
    }

    public int getTemplateId() {
        return templateId;
    }

    public int getVariableId() {
        return variableId;
    }
}

VariableId是代表数据项模板的绑定变量Id,就是在比如xml定义如下

<data>
       <variable
           name="datacontext"
           type="String" />
   </data>

最终在Java 的generated里面生成BR绑定常量

public class BR {
  public static final int _all = 0;

  public static final int bitmap = 1;

  public static final int countSec = 2;

  public static final int datacontext = 3;

  public static final int isCount = 4;

  public static final int photoInfo = 5;

  public static final int stringItems = 6;

  public static final int viewmodel = 7;
}

这里所有定义的绑定变量和绑定字段都会在BR类中
TemplateId就是数据项布局资源

public class CustomAdapter<T> extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
    private List<T> mDataSet;
    private ItemTemplate itemTemplate;

    public CustomAdapter(List<T> dataSet , ItemTemplate itemTemplate) {
        mDataSet = dataSet;
        this.itemTemplate = itemTemplate;
    }

在CustomAdapter类中增加ItemTemplate 变量,并在构造中传入

@NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // Create a new view, which defines the UI of the list item
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
                //.inflate(R.layout.text_row_item, parent, false);
        ViewDataBinding binding  = DataBindingUtil.inflate(inflater,itemTemplate.getTemplateId(),parent, false );
        return new ViewHolder(binding.getRoot());
    }

onCreateViewHolder的时候可以动态获取View及View对应的Binding了

@Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ViewDataBinding binding = DataBindingUtil.getBinding(holder.itemView);
        assert binding != null;
        //binding.setContent(  (String)this.mDataSet.get(position));
        binding.setVariable( this.itemTemplate.getVariableId(), this.mDataSet.get(position) );
        binding.executePendingBindings();
    }

onBindViewHolder的时候动态传入绑定数据

@BindingAdapter({"itemsSource","itemTemplate"})
    public static <T> void setItems(RecyclerView recyclerView , List<T> itemsSource , ItemTemplate itemTemplate ){
        //ItemTemplate itemTemplate = ItemTemplate.of( BR.content, @layout/text_row_item)
        CustomAdapter<T> adapter = new CustomAdapter<>( itemsSource,itemTemplate);
        recyclerView.setAdapter( adapter );
    }

BindingAdapter改造,增加itemTemplate参数。

<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/data_list"
            app:itemsSource="@{datacontext.stringItems}"
            app:itemTemplate="@{ItemTemplate.of(3,@layout/text_row_item)}"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="100dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView" />

RecyclerView的绑定修改如下
这里留一个大尾巴以后解决,我在绑定表达式里面直接用BR.datacontext的话编译无法通过
猜测里面有循环使用的可能,我不能在绑定表达式中使用绑定常量,可能这是一种非法使用,具体等明天再想办法解决了

Step 4 ItemTemplate的收尾

改造的思路是走反射和默认参数的路子

@NonNull
    public static ItemTemplate of( @LayoutRes int templateId , String variableName){
        int variableId = BR.datacontext;
        try {
            variableId = BR.class.getField(variableName).getInt(BR.class);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            Logger.e( e, "Error variableName parse" );
        }
        return new ItemTemplate(variableId, templateId);
    }

    @NonNull
    public static ItemTemplate of( @LayoutRes int templateId ){
        int variableId = BR.datacontext;
        return new ItemTemplate(variableId, templateId);
    }

如果不传入变量名字符串,则默认为datacontext
如果变量自定义,则需要传入variableName,通过反射的方式获取,绑定模块默认生成代码定义的变量id

<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/data_list"
            app:itemsSource="@{datacontext.stringItems}"
            app:itemTemplate='@{ItemTemplate.of(@layout/text_row_item)}'
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="100dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView" />

View层改成这样,基本比较满意了

Step 5 名副其实的Adpter

重构到这一步,其实CustomAdapter已经不明准备表达他的功能了
改名为ShadowAdapter。起名意境来自于黑泽明导演的《影武者》,顾名思义ShadowAdapter,并不需要让应用层很明显的感受到他的存在。他只要默默干活就行,他把耦合自己吃掉做好一个合格的中间商,留下更干净View和ViewModel
public class ShadowAdapter<T> extends RecyclerView.Adapter<ShadowAdapter.ViewHolder>

埋坑

还剩下一部分问题未得到妥善解决
ViewModel的集合对象整个替换怎么解决
集合发生变化了怎么解决
这一篇里都没解决,目前的功能就是一次性的列表绑定

所有这些问题留在下篇博客解决
Android 练手之旅——RecyclerView(三)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Demo Demo 安卓 安卓
Projects
None yet
Development

No branches or pull requests

1 participant