熊猫儿
发表于 2016-9-14 10:34
Android Data Binding作者:ashqal1 引入如何高效地实现以下界面?http://img.blog.csdn.net/20160504094028845有好几年findViewById实战经验的我,感觉并不难啊。一般会
* 1.先定义一个User的Model类,数据来自JSON解析;
* 2.创建一个xml,随后在xml中布局完所有View,对头像、标题、积分、登录按钮一个id;
* 3.在Activity中通过findViewById获取到头像ImageView、标题TextView、积分TextView、登录Button,然后给Button设置监听器,再根据登陆状态展示对应数据;实现如下:
* User.javapublic class User { private String name; private int score; private int level; private int avatar; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } public int getLevel() { return level; } public int getAvatar() { return avatar; } public void setAvatar(int avatar) { this.avatar = avatar; } public void setLevel(int level) { this.level = level; } public static User newInstance() { User user = new User(); user.setName("王大锤:" + (int)(Math.random() * 10)); user.setScore((int) (Math.random() * 999)); user.setLevel((int) (Math.random() * 77)); user.setAvatar(R.drawable.avatar); return user; }}
熊猫儿
发表于 2016-9-14 10:34
[*]activity_detail.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:background="@color/detail_background" android:layout_width="match_parent" android:layout_height="66dp"> </View> <ImageView android:id="@+id/detail_avatar" android:layout_gravity="center" android:src="@drawable/avatar" android:layout_marginTop="-33dp" android:layout_width="66dp" android:layout_height="66dp" /> <TextView android:id="@+id/detail_name" android:textSize="17sp" android:textColor="@color/textColorPrimary" android:layout_marginTop="15dp" android:layout_gravity="center" android:text="王大锤" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/detail_desc" android:layout_marginTop="15dp" android:textSize="13sp" android:layout_gravity="center" android:text="积分:102 金币:0" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/detail_action_button" android:layout_marginTop="15dp" android:layout_gravity="center" android:text="退出登陆" android:textColor="@color/white" android:background="@drawable/selector_g_button" android:layout_width="220dp" android:layout_height="wrap_content" /></LinearLayout>
熊猫儿
发表于 2016-9-14 10:36
[*]DetailActivity
public class DetailActivity extends AppCompatActivity { ImageView avatarIV; TextView nameTV; TextView descTV; Button actionBtn; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); initView(); login(); } private void login(){ fill(User.newInstance()); } private void logout(){ fill(null); } private void initView() { avatarIV = (ImageView) findViewById(R.id.detail_avatar); nameTV = (TextView) findViewById(R.id.detail_name); descTV = (TextView) findViewById(R.id.detail_desc); actionBtn = (Button) findViewById(R.id.detail_action_button); } private void fill(final User user){ final int visibility = user != null ? View.VISIBLE : View.GONE; if (avatarIV != null){ avatarIV.setVisibility(visibility); if (user != null) avatarIV.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar())); } if (nameTV != null){ nameTV.setVisibility(visibility); if (user != null) nameTV.setText(user.getName()); } if (descTV != null){ descTV.setVisibility(visibility); if (user != null) descTV.setText(String.format("积分:%d 等级:%d",user.getScore(),user.getLevel())); } if (actionBtn != null){ actionBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (user == null) login(); else logout(); } }); actionBtn.setText(user == null ? "登录":"退出登录"); } }}
熊猫儿
发表于 2016-9-14 10:37
2 去掉烦人的findViewById(View注入)可以看到,在Activity中View的定义、find、判空占据了大量篇幅,我们需要更优雅的实现。2.1 ButterKnife你可能听说过Jake Wharton的ButterKnife,这个库只需要在定义View变量的时候通过注解传入对应id,随后在onCreate时调用ButterKnife.bind(this)即可完成view的注入,示例如下:class ExampleActivity extends Activity {@BindView(R.id.user) EditText username;@BindView(R.id.pass) EditText password;@Override public void onCreate(Bundle savedInstanceState{ super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this);}}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
2.2 Android Data Binding如果使用了Android Data Binding,那么View的定义、find、判空这些都不用写了,如何做呢?2.2.1 准备工作首先,你需要满足一个条件:你的Android Plugin for Gradle版本必须等于或高于1.5.0-alpha1版本,这个版本位于根目录build.gradle中,示例如下:buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.1.0-rc1' }}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
接着,你必须告诉编译器开启Data Binding,一般位于app:build.gradle的android标签中,示例如下:android { compileSdkVersion 23 buildToolsVersion "23.0.2" dataBinding { enabled true } ...}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
2.2.2 修改layout.xml以activity_detail.xml为例,原来的根节点为LinearLayout,如下所示:http://lib.csdn.net/article/android/34439
熊猫儿
发表于 2016-9-14 10:37
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
我们拷一份activity_detail.xml,改为activity_detail2.xml,并且需要在外面wrap一层layout标签,修改后的activity_detail2.xml为:<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <!-- LinearLayout为原布局根节点 --> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:background="@color/detail_background" android:layout_width="match_parent" android:layout_height="66dp"> </View> ... </LinearLayout></layout>
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
2.2.3 开始享受乐趣吧!在上述操作完成后,编译器会自动为我们生成
com.asha.demo.databinding.ActivityDetail2Binding.java类,这个类的命令方式为:包名 + databinding + activity_detail2驼峰命名方式 + Binding.java。随后,使用这个activity_detail2的DetailActivity2.java的代码可以简化为:public class DetailActivity2 extends AppCompatActivity { ActivityDetail2Binding binding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2); login(); } private void login(){ fill(User.newInstance()); } private void logout(){ fill(null); } private void fill(final User user){ final int visibility = user != null ? View.VISIBLE : View.GONE; if (user != null){ binding.detailAvatar.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar())); binding.detailName.setText(user.getName()); binding.detailDesc.setText(String.format("积分:%d 等级:%d",user.getScore(),user.getLevel())); } binding.detailAvatar.setVisibility(visibility); binding.detailName.setVisibility(visibility); binding.detailDesc.setVisibility(visibility); binding.detailActionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (user == null) login(); else logout(); } }); binding.detailActionButton.setText(user == null ? "登录":"退出登录"); }}
熊猫儿
发表于 2016-9-14 10:38
http://lib.csdn.net/article/android/34439
熊猫儿
发表于 2016-9-15 23:09
是的,所有View的定义、find、判空都不见了,所有的这些操作都在编译器为我们生成的ActivityDetail2Binding.java中完成,只需要在onCreate时调用如下代码进行setContentView即可实现,
binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2);
1
我的天哪
2.2.4 ActivityDetail2Binding中注入View相关的代码分析
可以在as中方便的查看编译器自动生成的类,这个类位于/app/build/intermediates/classes/debug/com/asha/demo/databinding/ActivityDetail2Binding.class中,缩减掉Binding逻辑后的代码为:
public class ActivityDetail2Binding extends ViewDataBinding {
private static final IncludedLayouts sIncludes = null;
private static final SparseIntArray sViewsWithIds = new SparseIntArray();
public final Button detailActionButton;
public final ImageView detailAvatar;
public final TextView detailDesc;
public final TextView detailName;
private final LinearLayout mboundView0;
private long mDirtyFlags = -1L;
public ActivityDetail2Binding(DataBindingComponent bindingComponent, View root) {
super(bindingComponent, root, 0);
Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);
this.detailActionButton = (Button)bindings;
this.detailAvatar = (ImageView)bindings;
this.detailDesc = (TextView)bindings;
this.detailName = (TextView)bindings;
this.mboundView0 = (LinearLayout)bindings;
this.mboundView0.setTag((Object)null);
this.setRootTag(root);
this.invalidateAll();
}
...
static {
sViewsWithIds.put(2131492948, 1);
sViewsWithIds.put(2131492949, 2);
sViewsWithIds.put(2131492950, 3);
sViewsWithIds.put(2131492951, 4);
}
}
其中全局静态SparseIntArray数组中存放了4个数字,这个四个数字为R.java中生成的对应View的id,
public final class R {
...
public static final class id {
...
public static final int detail_action_button = 2131492951;
public static final int detail_avatar = 2131492948;
public static final int detail_desc = 2131492950;
public static final int detail_name = 2131492949;
...
}
...
}
熊猫儿
发表于 2016-9-15 23:10
在ActvityDetail2Binding实例构造的时候调用了mapBindings,一次解决了所有View的查找,mapBindings函数在ActvityDetail2Binding父类ViewDataBinding中实现。3 使用表达式在layout.xml中填充model数据在ActivityDetail2.java中还存在大量的View控制、数据填充代码,如何把这些代码在交给layout.xml完成呢?3.1 ModelAdapter类第2节中已经定义了User.java类作为Model类,但是我们经常会遇到Model类和真正View展示不一致的情况,本例子中定义一个来ModelAdapter类来完整Model数据到展示数据的适配。示例代码为ActivityDetail3.java的内部类,可以调用ActivityDetail3.java中的函数,代码定义如下:public class DetailActivity3 extends AppCompatActivity { public class ModelAdapter { private User user; public ModelAdapter(User user) { this.user = user;} public String getName(){ return user != null ? user.getName() : null;} public Drawable getAvatar(){ return user != null ? ContextCompat.getDrawable(DetailActivity3.this,user.getAvatar()) : null; } public String getDesc(){ return user != null ? String.format("积分:%d 等级:%d",user.getScore(),user.getLevel()) : null; } public String actionText(){ return user != null ? "退出登录" : "登陆"; } public void clickHandler(View view){ if (user != null) logout(); else login(); } }}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
[*]20
[*]21
[*]22
[*]23
[*]24
3.2 activity_detail3.xml中使用model同样复制一份activity_detail2.xml为activity_detail3.xml,在<layout>节点加入<data>节点,并且在里面定义需要用的model类(比如ModelAdapter adapter),当然也可以是基础类型变量(比如int visibility);随后,就可以在下面的view中使用表达式了,全部布局文件如下:<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="adapter" type="com.asha.demo.DetailActivity3.ModelAdapter"/> <variable name="visibility" type="int"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:background="@color/detail_background" android:layout_width="match_parent" android:layout_height="66dp"> </View> <ImageView android:src="@{adapter.avatar}" android:visibility="@{visibility}" android:id="@+id/detail_avatar" android:layout_gravity="center" android:layout_marginTop="-33dp" android:layout_width="66dp" android:layout_height="66dp" /> <TextView android:visibility="@{visibility}" android:text="@{adapter.name}" android:id="@+id/detail_name" android:textSize="17sp" android:textColor="@color/textColorPrimary" android:layout_marginTop="15dp" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:visibility="@{visibility}" android:text="@{adapter.desc}" android:id="@+id/detail_desc" android:layout_marginTop="15dp" android:textSize="13sp" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:text="@{adapter.actionText}" android:onClick="@{adapter.clickHandler}" android:id="@+id/detail_action_button" android:layout_marginTop="15dp" android:layout_gravity="center" android:textColor="@color/white" android:background="@drawable/selector_g_button" android:layout_width="220dp" android:layout_height="wrap_content" /> </LinearLayout></layout>
熊猫儿
发表于 2016-9-15 23:11
3.3 DetailActivity3.java中调用填充如下代码所示,只需要在登录状态改变的时候,给viewDataBinding设置所需要的adatper、visibility值,即可完成数据的填充public class DetailActivity3 extends AppCompatActivity { ActivityDetail3Binding binding; public class ModelAdapter { ... } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this,R.layout.activity_detail3); login(); } private void login(){ fill(User.newInstance()); } private void logout(){ fill(null); } private void fill(final User user){ binding.setAdapter(new ModelAdapter(user)); binding.setVisibility( user != null ? View.VISIBLE : View.GONE); }}
[*]1
[*]2
[*]3
[*]4
[*]5
[*]6
[*]7
[*]8
[*]9
[*]10
[*]11
[*]12
[*]13
[*]14
[*]15
[*]16
[*]17
[*]18
[*]19
[*]20
[*]21
[*]22
[*]23
[*]24
[*]25
[*]26
[*]27
3.4 ActivityDetail3Binding中填充相关的代码分析同样,ActivityDetail3Binding中,编译器根据activity_detail3.xml中的<data>标签,自动生成了诸如setAdapter、setVisibility的代码,setAdapter相关代码如下:public class ActivityDetail3Binding extends ViewDataBinding{ private ModelAdapter mAdapter; ... public void setAdapter(ModelAdapter adapter) { this.mAdapter = adapter; synchronized(this) { this.mDirtyFlags |= 1L; } this.notifyPropertyChanged(1); super.requestRebind(); } public ModelAdapter getAdapter() { return this.mAdapter; } ...}
熊猫儿
发表于 2016-9-15 23:12
非常简单,自动生成了getter和setter,在完成set操作后,调用执行notifyPropertyChanged和super.requestRebind()
* notifyPropertyChanged
ViewDataBinding本身就是一个BaseObservable, 在往ViewDataBinding注册观察某个属性的变化,如果注册了mAdapter的变化,对应的观察器就会接收到回调。相关逻辑与反向Binding相关,谷歌官方还没给出相关使用文档,不再深入分析;
[*]super.requestRebind()
1.此函数为ViewDataBinding中的函数,具体实现为判断现在是否有Rebind请求,如果有则return;如果没有则根据运行时sdk版本交给handler或者choreographer插入到下一帧中执行mRebindRunnable。
2.在mRebindRunnable中会根据当前sdk版本,如果大于等于KITKAT,则需要在onAttachToWindow后执行executePendingBindings;否则直接执行executePendingBindings。
public abstract class ViewDataBinding extends BaseObservable { protected void requestRebind() { synchronized (this) { if (mPendingRebind) { return; } mPendingRebind = true; } if (USE_CHOREOGRAPHER) { mChoreographer.postFrameCallback(mFrameCallback); } else { mUIThreadHandler.post(mRebindRunnable); } } /** * Runnable executed on animation heartbeat to rebind the dirty Views. */ private final Runnable mRebindRunnable = new Runnable() { @Override public void run() { synchronized (this) { mPendingRebind = false; } if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { // Nested so that we don't get a lint warning in IntelliJ if (!mRoot.isAttachedToWindow()) { // Don't execute the pending bindings until the View // is attached again. mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER); mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER); return; } } executePendingBindings(); } };}