十、inflate
不知道大家注意没有,上面的代码我们都是在activity中通过DataBindingUtil.setContentView
来加载的布局的,现在有个问题了,如果我们是在Fragment
中使用呢?Fragment
没有setContentView
怎么办?不要着急,Data Binding
也提供了inflate
的支持!
使用方法如下,大家肯定会觉得非常眼熟。
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
接下来,我们就尝试着在Fragmen
t中使用一下Data Binding
吧。
首先还是那个学生类,Student
public class Student extends BaseObservable {
private String name;
private int age;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
@Bindable
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
notifyPropertyChanged(org.loader.app5.BR.age);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(org.loader.app5.BR.name);
}
}
这里面代码如果看不懂了,请翻看前一篇博客。
继续,activity
的布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
activity
的代码,
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new MyFragment()).commit();
}
}
重点来了,我们这里data binding的操作都放在了fragment里,那么我们先来看看fragment的布局。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<import type="org.loader.app5.Student" />
<variable
name="stu"
type="Student" />
<variable
name="frag"
type="org.loader.app5.MyFragment" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{frag.click}"
android:text="@{stu.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(stu.age)}"/>
</LinearLayout>
</layout>
如果你看过上篇博客,那么这里也很简单,简单说一下吧。两个TextView
分别绑定了Student
的name
和age
字段,而且给name
添加了一个点击事件,点击后会调用Fragment
的click
方法。我们来迫不及待的看一下Fragment
怎么写:
public class MyFragment extends Fragment {
private Student mStu;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
org.loader.app5.Custom binding = DataBindingUtil.inflate(inflater,
R.layout.frag_layout, container, false);
mStu = new Student(20, "loader");
binding.setStu(mStu);
binding.setFrag(this);
return binding.getRoot();
}
public void click(View view) {
mStu.setName("qibin");
mStu.setAge(18);
}
}
在onCreateView
中,不同于在Activity
中,这里我们使用了DataBindingUtil.inflate
方法,接受4个参数,第一个参数是一个LayoutInflater
对象,正好,我们这里可以使用onCreateView
的第一个参数,第二个参数是我们的布局文件,第三个参数是一个ViewGroup
,第四个参数是一个boolean
类型的,和在LayoutInflater.inflate
一样,后两个参数决定了是否想container
中添加我们加载进来的布局。
下面的代码和我们之前写的并无差别,但是有一点,onCreateView
方法需要返回一个View
对象,我们从哪获取呢?ViewDataBinding
有一个方法getRoot
可以获取我们加载的布局,是不是很简单?
十一、 Data Binding VS RecyclerView
有了上面的思路,大家是不是也会在ListView和RecyclerView中使用了?我们仅以一个RecyclerView来学习一下。
首先来看看item的布局,
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="stu"
type="org.loader.app6.Student" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}"
android:layout_alignParentLeft="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(stu.age)}"
android:layout_alignParentRight="true"/>
</RelativeLayout>
</layout>
可以看到,还是用了那个Student
实体,这样得代码,相信你也已经看烦了吧。
那我们来看看activity
的。
private RecyclerView mRecyclerView;
private ArrayList<Student> mData = new ArrayList<Student>() {
{
for (int i=0;i<10;i++) add(new Student("loader" + i, 18 + i));
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false));
mRecyclerView.setAdapter(new MyAdapter(mData));
}
这里给RecyclerView
设置了一个Adapter
,相信最主要的代码就在这个Adapter
里。
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<Student> mData = new ArrayList<>();
private MyAdapter(ArrayList<Student> data) {
mData.addAll(data);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater
.from(viewGroup.getContext()), R.layout.item, viewGroup, false);
ViewHolder holder = new ViewHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));
viewHolder.getBinding().executePendingBindings();
}
@Override
public int getItemCount() {
return mData.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public ViewHolder(View itemView) {
super(itemView);
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
public ViewDataBinding getBinding() {
return this.binding;
}
}
}
果然,这个adapter
的写法和我们之前的写法不太一样,首先看看ViewHolder
,在这个holder
里,我们保存了一个ViewDataBinding
对象,并给它提供了Getter
和Setter
方法, 这个ViewDataBinding
是干嘛的?我们稍后去讲。继续看看onCreateViewHolder
,在这里面,我们首先调用DataBindingUtil.inflate
方法返回了一个ViewDataBinding
的对象,这个ViewDataBinding
是个啥?我们以前没见过啊,这里告诉大家我们之前返回的那些都是ViewDataBinding
的子类!继续看代码,我们new
了一个holder
,参数是肯定是我们的item
布局了,继续看,接着我们又把binding
设置给了holder
,最后返回holder
。这时候,我们的holder
里就保存了刚刚返回的ViewDataBinding
对象,干嘛用呢?继续看onBindViewHolder
就知道了。
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));
viewHolder.getBinding().executePendingBindings();
}
只有两行代码,但是都是我们没有见过的,首先第一行,我们以前都是使用类似binding.setStu
这样方法去设置变量,那这个setVariable
呢? 为什么没有setStu
,这里要记住,ViewDataBinding
是我们之前用的那些binding
的父类,只有自动生成的那些子类才会有setXXX
方法,那现在我们需要在ViewDataBinding
中设置变量咋办?这个类为我们提供了setVariable
去设置变量,第一个参数是我们的变量名的引用,第二个是我们要设置的值。第二行代码,executePendingBindings
的作用是干嘛的?官方的回答是:
当数据改变时,binding
会在下一帧去改变数据,如果我们需要立即改变,就去调用executePendingBindings
方法。
所以这里的作用就是去让数据的改变立即执行。
ok,现在看起来,我们的代码更加简洁了,而且不需要保存控件的实例,是不是很爽? 来看看效果:
十二、 View with ID
在使用Data Binding的过程中,我们发现并没有保存View的实例,但是现在我们有需求需要这个View的实例咋办?难道走老路findViewById
?当然不是啦,当我们需要某个view
的实例时,我们只要给该view
一个id
,然后Data Binding框架就会给我们自动生成该view
的实例,放哪了?当然是ViewDataBinding
里面。
上代码:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="str"
type="android.databinding.ObservableField<String>" />
<variable
name="handler"
type="org.loader.app7.MainActivity" />
</data>
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{str.get}"
android:onClick="@{handler.click}"/>
</layout>
xml中代码没有什么好说的,都是之前的代码,如果在这有点迷糊,建议你还是回头看看上篇博客。需要注意的是,
我们给TextView
了一个id-textView
。
activity,
public class MainActivity extends AppCompatActivity {
private org.loader.app7.Custom mBinding;
private ObservableField<String> mString;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
mString = new ObservableField<String>();
mString.set("loader");
mBinding.setStr(mString);
mBinding.setHandler(this);
}
public void click(View view) {
mString.set("qibin");
mBinding.textView.setTextColor(Color.GREEN);
}
}
主要还是来看click
方法中,这里我们需要获取TextView
的实例,用来改变他的颜色,我们是通过ViewDataBinding
类的实例直接去获取的。
只要我们给了view
一个id,那么框架就会在ViewDataBinding
中自动帮我们保存这个view
的实例,变量名就是我们设置的id
。
十三、 自定义setter
想想这样的一种情景,一个ImageView
需要通过网络去加载图片,那我们怎么办?看似好像使用DataBinding
不行,恩,我们上面所学到东西确实不能够解决这个问题,但是DataBinding
框架给我们提供了很好的扩展,允许我们自定义setter
,那该怎么做呢?这里就要引出另一个知识点——BindingAdapter
,这是一个注解,参数是一个数组,数组中存放的是我们自定义的’属性’。接下来就以一个例子学习一下BindingAdapter
的使用。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data class=".Custom">
<variable
name="imageUrl"
type="String" />
</data>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:image="@{imageUrl}"/>
</layout>
这里我们增加了一个命名空间app,并且注意ImageView
的app:image
属性,这里和我们自定义view
时自定义的属性一样,但是这里并不需要我们去重写ImageView
,这条属性的值是我们上面定义的String
类型的imageUrl
,从名称中看到这里我们可能会塞给他一个url
。
activity,
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
org.loader.app8.Custom binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
binding.setImageUrl("");
}
}
果然在这里我们set
了一个url
,那图片怎么加载呢?这里就要使用到我们刚才说的BindingAdapter
注解了。
public class Utils {
@BindingAdapter({"bind:image"})
public static void imageLoader(ImageView imageView, String url) {
ImageLoaderUtils.getInstance().displayImage(url, imageView);
}
}
我们定义了一个Utils
类,这个类你可以随便起名,该类中只有一个静态的方法imageLoader
,该方法有两个参数,一个是需要设置数据的view
,
一个是我们需要的url
。值得注意的是那个BindingAdapter
注解,看看他的参数,是一个数组,内容只有一个bind:image
,仅仅几行代码,我们不需要
手工调用Utils.imageLoader
,也不需要知道imageLoader
方法定义到哪了,一个网络图片加载就搞定了,是不是很神奇,这里面起关键作用的就是BindingAdapter
注解了,来看看它的参数怎么定义的吧,难道是乱写?当然不是,这里要遵循一定的规则,
以bind:
开头,接着书写你在控件中使用的自定义属性名称。
这里就是image
了,不信来看。
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:image="@{imageUrl}"/>
看看运行结果:
十四、 Converters Converter
是什么呢?举个例子吧:假如你的控件需要一个格式化好的时间,但是你只有一个Date
类型额变量咋办?肯定有人会说这个简单,转化完成后在设置,恩,这也是一种办法,但是DataBinding
还给我们提供了另外一种方式,虽然原理一样,但是这种方式使用的场景更多,那就是——Converter
。和上面的BindingAdapter
使用方法一样,这也是一个注解。下面还是以一段代码的形式进行学习。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="time"
type="java.util.Date" />
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{time}"/>
</layout>
看TextView
的text
属性,我们需要一个String
类型的值,但是这里确给了一个Date
类型的,这就需要我们去定义Converter
去转换它,
activity,
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
org.loader.app9.Custom binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
binding.setTime(new Date());
}
}
去给这个Date类型的变量设置值。怎么去定义Converter呢? 看代码:
public class Utils {
@BindingConversion
public static String convertDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
}
和上面一样,我们不需要关心这个convertDate
在哪个类中,重要的是他的@BindingConversion
注解,这个方法接受一个Date
类型的变量,正好我们的android:text
设置的就是一个Date
类型的值,在方法内部我们将这个Date
类型的变量转换成String
类型的日期并且返回。这样UI上就显示出我们转化好的字符串。
看看效果:
好了,到这里DataBinding的知识我们就算学习完了,在学完之后发现这东西也没什么难度,学会使用就ok了,而且android官网也有非常详细的文档,
这两篇博客只是系统的去讲解了DataBinding的使用,大家在以后使用的过程中发现忘记怎么用了,可以再来翻看博客或者直接去官方查看。
ok, 那就到这里吧,下次见。