Android DataBinding 自定义 View 的双向绑定

本文介绍如何在Android中实现DataBinding自定义View的双向绑定,通过MultiSelectionSpinner示例,详细解释从定义双向绑定方法到布局文件绑定及Activity实现效果的全过程。

本篇文章着重于写怎么实现DataBinding自定义 View 的双向绑定,理论和定义不再赘述。理论和定义可以参考这两篇博文:

Android DataBinding (五) 自定义 View 的双向绑定

Databinding的使用(自定义控件的全局注入、布局绑定)

当然,这两篇博文也有具体实现的代码,十分感谢这两篇博主的博文参考!

首先说明一下这个自定义view的功能:MultiSelectionSpinner(自定义单多选Spinner),点击控件时弹出供单选或多选的Dialog,见下图。当点击确定时会调用回调接口与viewmodel进行属性绑定。

实现步骤如下:

1、自定义MultiSelectionSpinner类

要支持双向绑定,首先要在类名上定义 @InverseBindingMethods

@InverseBindingMethods({
    @InverseBindingMethod(
        type = MultiSelectionSpinner.class,
        attribute = "selectConfirm",
        event = "selectConfirmAttrChanged",
        method = "getSelectConfirm")
})
public class MultiSelectionSpinner extends AppCompatSpinner implements DialogInterface.OnClickListener,OnMultiChoiceClickListener {

    ………………………… // 自定义view具体逻辑实现代码
    
    public interface OnSelectConfirmListener{
        void onSelectConfirm();
    }

    private OnSelectConfirmListener mListener;

    public void setOnSelectConfirmListener(OnSelectConfirmListener listener) {
        mListener = listener;
    }

    @InverseBindingAdapter(attribute = "selectConfirm", event="selectConfirmAttrChanged")
    public static String getSelectConfirm(MultiSelectionSpinner view) {
        return view.getSelectedItemsAsString();
    }

    @BindingAdapter({"selectConfirm"})
    public static void setSelectConfirm(MultiSelectionSpinner spinner, String item){
        if(item!=null){
           spinner.setSelection(Arrays.asList(item.split("\\,")));
        }
    }

    @BindingAdapter(value = {"onSelectConfirm", "selectConfirmAttrChanged"}, requireAll = false)
    public static void setOnValueSelectListener(MultiSelectionSpinner view,
           final OnSelectConfirmListener onSelectConfirmListener,final InverseBindingListener bindingListener) {

        if (bindingListener == null) {
            view.setOnSelectConfirmListener(onSelectConfirmListener);
        } else {

            view.setOnSelectConfirmListener(new OnSelectConfirmListener() {
                @Override
                public void onSelectConfirm() {
                    if(onSelectConfirmListener!=null){
                        onSelectConfirmListener.onSelectConfirm();
                    }else{
                        // 通知 ViewModel
                        bindingListener.onChange();
                    }
                }
            });
        }
    }
    
    @SuppressLint("ClickableViewAccessibility")
	@Override 
    public boolean performClick() {
    	
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        //builder.setMultiChoiceItems(_items, mSelection, this);

        if(!setSingleChoice){                                                       // 多选
            builder.setMultiChoiceItems(_items, mSelection, this);
        }else{
            builder.setSingleChoiceItems(_items, mChoice,this);             // 单选
        }

        _itemsAtStart = getSelectedItemsAsString();
        builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
            // Dialog 确定按钮点击事件,调用onSelectConfirm接口实现与viewmodel属性进行绑定
            @Override
            public void onClick(DialogInterface dialog, int which) {
                System.arraycopy(mSelection, 0, mSelectionAtStart, 0, mSelection.length);
                if(mListener!=null){
                    mListener.onSelectConfirm();
                }
            }
        });
        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                simple_adapter.clear();
                simple_adapter.add(_itemsAtStart);
                System.arraycopy(mSelectionAtStart, 0, mSelection, 0, mSelectionAtStart.length);
            }
        });
        builder.show();
        return true;
    }

}

2、布局文件xml中用app:selectConfirm="@={}"与viewmodel进行双向绑定。注:下面代码只是截取了布局xml中婚否那一部分

         <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="50dp">

            <TextView
                android:layout_weight="1.5"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_gravity="center"
                android:textSize="20sp"
                android:gravity="center"
                android:text="(单选) 婚否"/>

            <com.mvvmdemo.MultiSelectionSpinner
                android:layout_weight="2"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:entries="@array/ifnot"
                android:textSize="18sp"
                android:background="@drawable/spinner_bg"
                custom:custom_gravity="center"
                custom:singleChoice="true"
                app:selectConfirm="@={simple.marriage}"/>

        </LinearLayout>

2、Activity实现效果

public class BlogActivity extends AppCompatActivity implements View.OnClickListener{

    private ActivityBlogBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding=DataBindingUtil.setContentView(this,R.layout.activity_blog);
        binding.setSimple(new SimpleEntity("是","篮球"));
        binding.setClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_blog_save:
                SimpleEntity simple=binding.getSimple();
                Toast.makeText(BlogActivity.this,simple.toString(),Toast.LENGTH_SHORT).show();
                break;
                default:
        }
    }
}

 

运行过程分析:

1、Activity在onCreate()设置布局并且绑定viewmodel后即会调用MultiSelectionSpinner类中设置的 @BindingAdapter({"selectConfirm"}),此时界面上自定义MultiSelectionSpinner即会分别显示我们设置的"是"和"篮球",同时会调用MultiSelectionSpinner类中的监听事件 setOnValueSelectListener()设置确定按钮点击回调接口。

2、当点击自定义MultiSelectionSpinner弹出Dialog选择完毕点击确定后即会进入1设置好的回调事件中调用 bindingListener.onChange(); 通知 ViewModel要改变viewl,接着会调用之前在 MultiSelectionSpinner类中的绑定好的 @InverseBindingAdapter  getSelectConfirm(),在该方法中获取到选择的值后就会把值返回给@BindingAdapter({"selectConfirm"}),由该Adapter把选择值显示在Activity的View上。

至此自定义 View 的双向绑定完成。

水平有限,欢迎批评指正!

<think>好的,我现在需要回答用户关于Android DataBinding双向绑定机制和实现原理的问题。首先,我得回忆一下DataBinding的基本概念,然后深入探讨双向绑定的部分。 首先,用户提到了DataBinding双向绑定,所以需要明确什么是双向绑定。单向绑定是数据从模型流向视图,而双向绑定允许视图的变化也能自动更新模型中的数据。这在像EditText这样的输入控件中非常有用,用户输入后数据能自动更新到对应的数据模型。 接下来,我需要解释双向绑定的实现原理。可能涉及以下几个部分: 1. **数据观察机制**:DataBinding库使用观察者模式,当数据变化时通知视图更新,反之亦然。可能使用BaseObservable和Bindable注解,或者使用ObservableField等类。 2. **双向绑定的语法**:在布局文件中使用@={}符号,例如android:text="@={viewModel.userName}",这里的等号表示双向绑定。 3. **事件监听**:视图的变化如何触发数据更新。例如,EditText的TextWatcher监听文本变化,然后更新数据模型。 4. **底层实现**:可能涉及到生成的绑定类,这些类如何处理双向的数据流。比如,如何自动为控件添加监听器,并在数据变化时更新双方。 然后,需要确保回答结构清晰,分点说明。可能还需要引用一些官方文档或可靠资料来支持解释,但用户提供的引用似乎与Kotlin和Java生态相关,可能不太直接相关,但如果有合适的部分可以引用的话。 还需要注意用户提到的系统级指令,比如数学公式要用$...$,独立公式用$$...$$,但这个问题可能不太涉及数学公式,但如果有涉及的地方需要注意格式。此外,用中文回答,生成相关问题。 现在,需要组织这些思路成一个连贯的回答,分步骤说明双向绑定的机制,可能包括数据观察、绑定适配器、生成的代码等部分。同时,可能需要提到InverseBindingAdapter,因为这在双向绑定中用于处理反向的数据流,即从视图到模型。 还需要检查是否有遗漏的部分,例如数据变更时的通知机制,比如ObservableField的使用,或者LiveData在DataBinding中的整合,但用户的问题可能集中在双向绑定的基础原理上,所以可能不需要涉及LiveData,除非它相关。 最后,生成相关问题,例如如何自定义双向绑定,常见问题有哪些,或者与单向绑定的区别等。</think>### Android DataBinding双向绑定机制及实现原理 Android DataBinding双向绑定允许**视图与数据模型之间的自动同步**:当数据变化时更新视图,当视图内容变化时自动回写数据。其核心实现分为以下几个步骤: --- #### 1. **数据观察机制** - **数据层**:使用`Observable`接口(如`BaseObservable`类)或`ObservableField`包装数据。 示例: ```kotlin class User : BaseObservable() { @Bindable var name: String = "" set(value) { field = value notifyPropertyChanged(BR.name) } } ``` 通过`@Bindable`注解和`notifyPropertyChanged()`方法通知绑定类数据变化[^1]。 - **视图层**:DataBinding自动生成的绑定类(如`ActivityMainBinding`)监听数据变化,并更新对应视图。 --- #### 2. **双向绑定语法** 在布局文件中使用`@={}`符号建立双向绑定: ```xml <EditText android:text="@={viewModel.user.name}" /> ``` 这里的`@=`表示双向绑定,而`@`仅表示单向绑定。 --- #### 3. **反向绑定适配器(Inverse Binding Adapter)** 双向绑定的关键在于**处理视图到数据的反向更新**: - **正向绑定**:数据 → 视图,通过`@BindingAdapter`实现。 - **反向绑定**:视图 → 数据,通过`@InverseBindingAdapter`实现。 以`EditText`的文本绑定为例: ```java // 正向:将数据设置到视图 @BindingAdapter("android:text") fun setText(view: EditText, value: String?) { if (view.text.toString() != value) { view.setText(value) } } // 反向:从视图读取数据 @InverseBindingAdapter(attribute = "android:text") fun getText(view: EditText): String { return view.text.toString() } ``` --- #### 4. **事件监听与更新触发** DataBinding自动为视图添加事件监听器。例如,`EditText`会注册`TextWatcher`: ```java // 自动生成的代码片段 editText.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { inverseBindingListener.onChange(); // 触发反向更新 } }); ``` 当用户输入文本时,`inverseBindingListener`会调用反向绑定适配器,将新值写回数据模型。 --- #### 5. **底层实现流程** 1. **编译时**:APT生成绑定类,解析`@={}`并生成正向/反向绑定逻辑。 2. **运行时**: - 数据变化 → 通过`notifyPropertyChanged()`通知绑定类 → 更新视图。 - 视图变化 → 通过事件监听触发反向绑定 → 更新数据模型。 --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值