DataBinding

1.基本使用:

开启DataBinding:

android {
	...
    dataBinding {
        enabled = true
    }
}

databinding布局的一般写法:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
 		<import type="android.view.View" />
 		
        <variable
            name="viewmodel"
            type="com.example.mtest.MainViewModel" />

        <variable
            name="callback"
            type="android.view.View.OnClickListener" />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
         	<TextView
        		android:layout_width="wrap_content"
        	 	android:layout_height="wrap_content"
        	 	android:onClick="@{callback}"
         		android:text="@{viewmodel.name}"/>
    </FrameLayout>
</layout>

转换时可以在xml文件选中根布局,使用Alt + 回车快捷键将普通布局转换为databinding布局

Activity页面初始化布局:

public class MainActivity extends Activity {
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 		//databinding为Activity设置布局文件通过DataBindingUtil
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setViewmodel(new MainViewModel());
    }
}

fragment页面初始化布局:

public class MainFragment extends Fragment {
	private FragmentMainBinding binding;
	
	@Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
        binding.setViewmodel(new MainViewModel());
        return binding.getRoot();
    }
}
数据绑定时方法的优先级高于成员变量,例如:

ViewModel:

public class MainViewModwl {
	public ObservableField<String> name = new ObservableField("Hello");
	
	public String getName() {
		return "World";
	}
}

布局文件:

	<TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@{viewmodel.name}"/>				//TextView显示World,而不是Hello
2.Observable类型:

有如下布局:

	 <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@{viewmodel.name}"/>

viewmodel:

	//普通的String类型,name更新后不会使TextView的内容更新
	public String name = "Tom";				
	//Observable类型,name更新后相关联的TextView内容也会自动更新
	public ObservableField<String> name = new ObservableField<>("Tom");
	//LiveData不能用于绑定UI
	public MutableLiveData<String> name = new MutableLiveData<>();

如何实现自己的Observable类型?
查看源码发现各种Observable类型的对象,如ObservableInt,ObservableFiled等,他们都继承了BaseObservable类:

public class BaseObservable implements Observable {
    private transient PropertyChangeRegistry mCallbacks;

    @Override
    public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
        synchronized (this) {
            if (mCallbacks == null) {
                mCallbacks = new PropertyChangeRegistry();
            }
        }
        mCallbacks.add(callback);
    }

    public void notifyChange() {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
        mCallbacks.notifyCallbacks(this, 0, null);
    }
}

要想实现普通数据类型绑定到UI,可以这样:

public class User extends BaseObservable {				//1.继承BaseObservable 
	public String name;

	public void setName(String name) {
		this.name = name;
		notifyChange();									//2.通知更新
	}	
}
3.ObservableField<T>和继承BaseObservable的区别:

DataModel:

public class User {
    private String name;
    
    public User(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

设有如下布局:

 	<TextView
         ...
         android:text="@{viewmodel.user.name}"/>
         //这里的user.name虽然是private的,但还是可以通过getName方法引用到
         //除了user.name,也可以通过user.getName或user.getName()获取到name

ViewModel:

	public ObservableField<User> user = new ObservableField<>(new User("Google"));

这样Google字样可以显示在UI上,但是当我们想要通过修改User的属性触发UI更新:

	public ObservableField<User> user = new ObservableField<>(new User("Google"));
	//这种方式并不能触发UI更新
	public void setUserName(String name) {
		user.get().setName("Android");
	}
	//这种方式才可以更新UI,但如果User是一个复杂对象,new一个User就不现实了
	public void setUserName(String name) {
		user.set(new User("Android"))
	}

因为第一种方式修改的只是user的内容,而setName前后user对象始终是同一个,所以第一种方式不能触发UI更新
要想使得第一种方式可以更新UI,就要把User修改成这样:

public class User extends BaseObservable {				//1.继承BaseObservable 
	public String name;

	public void setName(String name) {
		this.name = name;
		notifyChange();									//2.通知更新
	}	
}

这样我们就可以不用这样写ViewModel了:

	public ObservableField<User> user = new ObservableField();
	public ObservableField<String> userName = new ObservableField();
	public ObservableInt userAge = new ObservableInt();
4.事件绑定:
	<data>
		 <variable
            name="viewmodel"
            type="com.example.mtest.MainViewModel" />
        <variable
            name="callback"
            type="android.view.View.OnClickListener" />
    </data>
    
	<TextView
		...这里不需要把textView设置为clickable,就可以点击
		android:onClick="@{callback}"/>

	<TextView
		...这里的onClick需要是OnClickListener类型
		android:onClick="@{callback::onClick}"/>
		
	<TextView
		...这里viewmodel的类型没有限制
		android:onClick="@{v->viewmodel.setName(&quot;Android&quot;)}"/>
5.在布局文件中调用静态方法:
public class Utils {
	public static String toUpperCase(String s){
		 return s.toUpperCase();
	}			
}

布局文件:

	<data>
        <import type="com.example.mtest.User" />

        <variable
            name="viewmodel"
            type="com.example.mtest.MainViewModel" />
    </data>
	<TextView
         android:layout_width="200dp"
         android:layout_height="100dp"
         android:layout_marginTop="100dp"
         android:text="@{Utils.toUpperCase(viewmodel.name)}" />
6.运算符:

布局文件中的某些特殊字符沿用了Html中的转义字符,如:

&&   	&amp;&amp;
""   	&quot;&quot;
<    	&lt;
>    	&gt;
*    	&times;
/    	&divide;
空格 	&nbsp;

??符号:

 <TextView
     ...
     android:text="@{viewmodel.firstName ?? viewmodel.lastName }" />
     等价于
  <TextView
     ...
     android:text="@{viewmodel.firstName != null ? viewmodel.firstName : viewmodel.lastName }" />
7.用BindingAdapter为控件增加自定义属性:

新建一个类,类名随意,里面的方法名也随意

import android.databinding.BindingAdapter;

public class MyBindingAdapter {				
	@BindingAdapter("url")
    public static void setUrl(TextView view, String url) {
        view.setText("https://" + url);
    }
    
    //这样写可以覆盖TextView原本的属性
	@BindingAdapter("android:text")
    public static void setText(TextView view, String text) {
        view.setText(text);
    }
}
 <TextView
     ...
     url="@{viewmodel.firstName}" />
8.BindingConversion
import android.databinding.BindingConversion;

public class MyBindingAdapter {				
	@BindingConversion
    public static String conversionString(String text) {
    	if (text.startsWith("www.")) {
            return "https://" + text;
        }
        return text;
    }
}

这样可以检查所有控件的String类型的输入,并且把"www."开头的字符串前面都加上”https://“

9.双向绑定:
1.系统控件双向绑定:
 	<EditText
         android:layout_width="match_parent"
         android:layout_height="100dp"
         android:text="@={viewmodel.name}"/>
         
 	<Switch
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:checked="@={viewmodel.on}" />

ViewModel:

	public ObservableField<String> name = new ObservableField<>("Google");
	public ObservableBoolean on = new ObservableBoolean();
2.自定义控件双向绑定:

如果我们对自定义的属性使用双向绑定数据,就会产生错误,例如:

	@BindingAdapter("content")
    public static void setText(final Editor, String text) {
         editor.setText(text);
    }
	<EditText
         ...
         content="@={viewmodel.user.name}"/>

以下将实现一个自定义控件,并且实现它的自定义属性双向绑定:

public class SimpleEditor extends FrameLayout {
    private EditText editor;
    private Context context;
    private EditorTextChangeCallback callback;

    public SimpleEditor(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    private void init() {
        inflate(context, R.layout.layout_simple_editor, this);
        editor = (EditText) findViewById(R.id.root_view);
        editor.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (s != null && callback != null) {
                    callback.onTextChange(s.toString());
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });
    }

    public void setTextChangeCallback(EditorTextChangeCallback callback) {
        this.callback = callback;
    }

    public void setText(String text) {
        this.editor.setText(text);
    }

    public String getText() {
        return editor.getText().toString();
    }

    public interface EditorTextChangeCallback {
        void onTextChange(String s);
    }

	//这个接口方法提供了SimpleEditor的自定义属性
    @BindingAdapter("content")
    public static void setText(final SimpleEditor editor, String text) {
    	//反向绑定会触发数据在View和ViewModel层之间来回传递造成死循环,所以一定要增加不相等判定
        if (!text.equals(editor.getText())) {
            editor.setText(text);
        }
    }
	//这里定义了一个属性textChangeCallback(名字随意),这个字段并不是作为自定义属性供布局xml文件使用的;
	//它的作用仅仅是将textChangeCallback和editor.setTextChangeCallback关联起来
	//否则@InverseBindingAdapter方法不知道textChangeCallback到底对应那个接口
    @BindingAdapter(value = {"onContentChange"}, requireAll = false)
    public static void setTextChangeCallback(SimpleEditor editor, final InverseBindingListener textChangeListener) {
        editor.setTextChangeCallback(s -> textChangeListener.onChange());
    }

	//需要实现双向绑定的是content属性,并且在事件textChangeCallback发生时,触发数据由控件回传给viewmodel
	//这里的onContentChange事件只要保持和上一个方法中定义的事件名称相同就可以了
    @InverseBindingAdapter(attribute = "content", event = "onContentChange")
    public static String setInverseTest(final SimpleEditor editor) {
        return editor.getText();
    }
}

然后在布局文件中使用这个属性:

  	<com.example.mtest.SimpleEditor
  		...
        content="@={viewmodel.name}" />
10.RecycleView中的DataBinding

参考 RecycleView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值