Andrews DataBinding (five) Custom View's way binding

Android DataBinding (a) Basic Usage  
Android DataBinding (two) event handler  
Android DataBinding (three) Observable  
Android DataBinding (d) custom attributes 
Android DataBinding (five) Custom View of two-way binding (paper) 
Android DataBinding (six) EditText tie given TextChangedListener and FocusChangeListener

Foreword

Custom View if used when non-system-defined properties of time, if you want to achieve two-way binding, not with the @ = line, the Custom View also need some settings.

The following example to illustrate through a custom View achieve two-way binding.

Examples claim: 
1. RadioButton selected hobby (hobby options are: eat / sleep / play Peas) 
when loading a display screen 2. The initial value is interested (in the formula set value ViewModel spread to RadioButton) 
. 3. RadioButton selected when the value passed to the ViewModel 
4. RadioButton value can be empty, which means you can not have a passion

First Custom RadioButton and RadioGroup

As the hobby is the need to define the enum type, and select RadioButton RadioGroup time by id come, it must first be converted into enum id be able to achieve binding. But we can customize the RadioButton and RadioGroup to allow them to support enum binding!

First look at the custom code RadioButton

public class DataBindingRadioButton extends AppCompatRadioButton {

    private Integer value;

    public DataBindingRadioButton(Context context) {
        super(context);
    }

    public DataBindingRadioButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DataBindingRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

    @Override
    public void toggle() {
        if (isChecked()) {
            if (getParent() instanceof RadioGroup) {
                // 点击选中的 RadioButton,可以取消选择
                ((RadioGroup) getParent()).clearCheck();
            }
        } else {
            setChecked(true);
        }
    }

    @BindingAdapter(value = {"value"})
    public static void setValue(DataBindingRadioButton radioButton, Integer value) {
        radioButton.setValue(value);
        ViewParent parent = radioButton.getParent();
        if (parent instanceof DataBindingRadioGroup) {
            Integer checkedValue = ((DataBindingRadioGroup) parent).getCheckedValue();
            radioButton.setChecked(IntegerUtil.isSame(checkedValue, value));
        }
    }
}

We give DataBindingRadioButton define a property value, the value is the value corresponding enum value Integer.

Enum value is coming through DataBinding binding, it is necessary to set the corresponding method.

We did not use setValue (Integer value), but by @BindingAdapter 
using the method additionally set with a parameter of DataBindingRadioButton.

The reason is not only the value passed in, let RadioGroup need to know which one is selected RadioButton. RadioGroup If OnCheckedChange listen, then, radioButton.setChecked will notify the RadioGroup.

RadioButton must select a default, toggle () RadioButton what part of the code is to support not chosen. Because our request or may not have hobbies.

IntegerUtil code to compare a two Integer class Util written. The question is, Why is the value of value of Integer type rather than an int? Because it supports do not choose love, so loving values ​​can be null, so it is necessary to define the type Integer.

The following is the custom code RadioGroup

@InverseBindingMethods({
        @InverseBindingMethod(
                type = DataBindingRadioGroup.class,
                attribute = "checkedValue",
                event = "checkedValueAttrChanged",
                method = "getCheckedValue")
})
public class DataBindingRadioGroup extends RadioGroup {

    private Integer checkedValue;
    private OnValueChangedListener listener;

    public DataBindingRadioGroup(Context context) {
        super(context);
        init();
    }

    public DataBindingRadioGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public void init() {
        setOnCheckedChangeListener((group, checkedId) -> {
            if (checkedId > 0) {
                DataBindingRadioButton radioButton = (DataBindingRadioButton) findViewById(checkedId);
                setCheckedValue(radioButton.isChecked() ? radioButton.getValue() : null);
            } else {
                setCheckedValue(null);
            }
        });
    }

    public Integer getCheckedValue() {
        return checkedValue;
    }

    public void setCheckedValue(Integer checkedValue) {

        if (IntegerUtil.isSame(this.checkedValue, checkedValue)) {
            return;
        }

        this.checkedValue = checkedValue;

        if (this.checkedValue == null) {
            clearCheck();
        } else {
            DataBindingRadioButton customRadioButton = (DataBindingRadioButton) findViewById(getCheckedRadioButtonId());
            if (customRadioButton == null || !IntegerUtil.isSame(this.checkedValue, customRadioButton.getValue())) {

                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    if (child instanceof DataBindingRadioButton) {
                        Integer value = ((DataBindingRadioButton) child).getValue();
                        if (IntegerUtil.isSame(this.checkedValue, value)) {
                            ((DataBindingRadioButton) child).setChecked(true);
                        }
                    }
                }
            }
        }

        if (listener != null) {
            listener.onValueChanged();
        }
    }

    public void setListener(OnValueChangedListener listener) {
        this.listener = listener;
    }

    public interface OnValueChangedListener {
        void onValueChanged();
    }

    @BindingAdapter("checkedValueAttrChanged")
    public static void setValueChangedListener(DataBindingRadioGroup view, final InverseBindingListener bindingListener) {
        if (bindingListener == null) {
            view.setListener(null);
        } else {
            // 通知 ViewModel
            view.setListener(bindingListener::onChange);
        }
    }
}

To support the reverse binding, you must first define @InverseBindingMethods on the class name. 
attribute = "checkedValue" attribute is designated to support the reverse binding. 
event = "checkedValueAttrChanged" is designated valueChanged listen for events. 
method = "getCheckedValue" reverse data source binding method specified time.

event and method are not necessary, if not specified, the default is automatically generated to the following rule 
event = "xxxAttrChanged" 
method = "getXxx"

The method can also be defined directly in the process of the above

@InverseBindingAdapter(attribute = "checkedValue", event = "checkedValueAttrChanged")
public Integer getCheckedValue() {
    return checkedValue;
}

@BindingAdapter ( "checkedValueAttrChanged") is used to specify listener methods, with emphasis on InverseBindingListener, it's onChange method is to place a final notice ViewModel value changes (implemented in a class which generated InverseBindingListener to this example, then, is ActivityMainBinding, below paste the realization InverseBindingListener).

    private android.databinding.InverseBindingListener mboundView1checkedValueAttrChanged = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of vm.hobby
            //         is vm.setHobby((java.lang.Integer) callbackArg_0)
            // 这里就是 method = "getCheckedValue" 指定的方法
            java.lang.Integer callbackArg_0 = mboundView1.getCheckedValue();
            // localize variables for thread safety
            // vm != null
            boolean vmJavaLangObjectNull = false;
            // vm
            com.teletian.databindingradiobutton.viewmodel.ViewModel vm = mVm;
            // vm.hobby
            java.lang.Integer vmHobby = null;

            vmJavaLangObjectNull = (vm) != (null);
            if (vmJavaLangObjectNull) {
                // 这里就是修改 ViewModel 的值
                vm.setHobby(((java.lang.Integer) (callbackArg_0)));
            }
        }
    };

Doing things setValueChangedListener onChange method is to do OnValueChangedListener set to go inside.

You might ask, why bother, I directly attribute the definition of a InverseBindingListener it does not directly assigned to OK!

Yes, it is indeed the case, the above code can really simple to do so! But if you really need to set RadioGroup OnValueChangedListener, then it can not be the case! The following code needs to be changed so

    @BindingAdapter(value = {"onCheckedValueChanged", "checkedValueAttrChanged"}, requireAll = false)
    public static void setValueChangedListener(DataBindingRadioGroup view,
                                               final OnValueChangedListener valueChangedListener,
                                               final InverseBindingListener bindingListener) {
        if (bindingListener == null) {
            view.setListener(valueChangedListener);
        } else {
            view.setListener(() -> {
                if (valueChangedListener != null) {
                    valueChangedListener.onValueChanged();
                }
                // 通知 ViewModel
                bindingListener.onChange();
            });
        }
    }

setCheckedValue method inside to do is control Check the state and perform content RadioButton listening. 
Because the calls RadioButton setChecked method, then the init method which has set up a setOnCheckedChangeListener, so setCheckedValue method is called again, in order to prevent the cycle call, the following code is essential

if (IntegerUtil.isSame(this.checkedValue, checkedValue)) {
    return;
}

RadioGroup and RadioButton are custom finished, let's look at Layout file

         <com.teletian.databindingradiobutton.customview.DataBindingRadioGroup
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:checkedValue="@={vm.hobby}">

            <com.teletian.databindingradiobutton.customview.DataBindingRadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="吃饭"
                app:value="@{Hobby.EATING.value}" />

            <com.teletian.databindingradiobutton.customview.DataBindingRadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="睡觉"
                app:value="@{Hobby.SLEEPING.value}" />

            <com.teletian.databindingradiobutton.customview.DataBindingRadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="打豆豆"
                app:value="@{Hobby.ATTACKING_DOUDOU.value}" />

        </com.teletian.databindingradiobutton.customview.DataBindingRadioGroup>

RadioButton first value by app: = "@ {Hobby.EATING.value}" specified, so put enum values ​​and RadioButton linked up value.

App then disposed in the RadioGroup: checkedValue = "@ = {vm.hobby}" to set the two-way binding.

Source

https://github.com/teletian/Android/tree/master/DataBindingRadioButton

Published 24 original articles · won praise 5 · views 20000 +

Guess you like

Origin blog.csdn.net/qq_26923265/article/details/82745515