github地址
背景
数据绑定框架有很多,其实我就看过谷歌官方的数据绑定框架,官方的框架用起来的时候,觉得不是很顺手,侵入性还比较强。而且也一直纠结彷徨,从心底里质疑数据绑定框架的价值,到底给我们开发带来了什么,实用吗,可维护吗?
某一天的早晨突然灵光一现,决定自己去试试开发一个自己喜欢的数据绑定框架,经过没日没夜的艰苦风斗(当然是开玩笑的),中间反复修改设计,最后尘埃落定。虽然这个框架已经出世,也在生产环境试运行了一些页面,但是我对于数据绑定框架的价值,始终抱有质疑的态度,所以今天我将其取名
sword
,希望他像一把双刃剑,能够有一个面给开发者带来一些小小的便利。
价值
数据绑定的价值,我的理解就是一句话:就是通过操作一个数据对象,达到修改视图的目的,或者反过来,通过操作视图对象,达到修改数据对象的目的。
提示:
在大多数的情况下,通常都是数据单向操作视图的,极少的情况下需要数据和视图双向绑定,互相操作。
模型分析
textView.setText(CharSequence data);
data = textView.getText();
第一行代码代表值到视图的映射,第二行代码代表视图到值的映射。
值到视图的绑定
视图到值的绑定
sword的使用方法
sword的用法分为单向绑定(常用的),双向绑定2种。
单向绑定
新建data
class Data implements BindImpl {
@Bind(viewId = R.id.end_title,setMethod = B.setText)
public String end_title = "1234";
public String end_desc = "aaa";
@Bind(viewName = "end_title", setMethod = B.setTextColor)
public int endTitleColor=getResources().getColor(R.color.colorAccent);
@Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
public View.OnClickListener end_titleL = new View.OnClickListener() {
@Override
public void onClick(View v) {
end_title = "88888";
end_desc = "+++++";
endTitleColor = getResources().getColor(R.color.colorPrimary);
url = "~~~~~";
bindH.dataChange();
}
};
}
值的写法有3种
@Bind(viewId = R.id.end_title,setMethod = B.setText)
public String end_title = "1234";
@Bind(viewName = "end_title",setMethod = B.setText)
public String end_title = "1234";
@Bind(setMethod = B.setText)
public String end_title = "1234";
如果要映射的视图是一个textview,setMethod的默认值就是“setText”那么还有第四种写法.
public String end_title = "1234";
我来解释一下,上面3种用法,直接上图。
天然的2个缺陷
值对应的缺陷
textView.setText(CharSequence data);
所谓绑定,就是一一对应,view的方法只能接受一个参数。抽离出关键字就是:view
,一个方法参数
。但是有时候没办法处理特殊情况,比如我们使用volly框架的NetworkImageView,用法是这样的
NetworkImageView.setImageUrl(String url, ImageLoader imageLoader)
解决缺陷办法
class Data implements BindImpl {
...
@Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
private String url;
void setImageUrl(String url) {
networkImageView.setImageUrl(url, imageLoader);
}
....
}
指定到data自身viewName = B.viewNameSelf
,指定到自身的方法setImageUrl
(名字可以任意取),在方法内部构造特殊情况。
伪值绑定的缺陷
textView.setText(int textId);
上面代码是找不到视图到值的对应,即不存在:
textId = textView.getText()
这样的对应,我将其称之为伪值对应,即只存在数据到视图的对应。
所以这样的类型我们要告知没有返回值(单向绑定
的情况下不需要告知,只有双向绑定
的时候才需要这么做,即添加getMethod = B.getMethodNull
),代码如下:
@(Bind viewName="text",getMethod = B.getMethodNull)
int textId
完整的用法:
使用的时候只需要2句代码即可:
data = new Data();
swordBind.bindOneWay(this, data);
data变化时通知view变化:
end_title = "88888";
end_desc = "+++++";
endTitleColor = getResources().getColor(R.color.colorPrimary);
url = "~~~~~";
swordBind.dataChange();
完整代码:
package com.taobao.pandora.hello;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.taobao.sword.BindImpl;
import com.taobao.sword.SwordBind;
import com.taobao.sword.meta.B;
import com.taobao.sword.meta.Bind;
/**
* Created by shanksYao on 1/5/16.
*/
public class BindActivity extends Activity {
Data data;
SwordBind swordBind = new SwordBind();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.live_include_deal_status);
data = new Data();
swordBind.bindOneWay(this, data);
}
/**
* data就是用来操作View 任何跟View无关的属性 请不要放进来,以免造成设计混乱
*/
class Data implements BindImpl {
@Bind(viewName = "end_title")
public String end_title = "1234";
public String end_desc = "aaa";
@Bind(viewName = "end_title", setMethod = B.setTextColor)
public int endTitleColor = getResources().getColor(R.color.colorAccent);
@Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
public View.OnClickListener end_titleL = new View.OnClickListener() {
@Override
public void onClick(View v) {
end_title = "88888";
end_desc = "+++++";
endTitleColor = getResources().getColor(R.color.colorPrimary);
url = "~~~~~";
swordBind.dataChange();
}
};
@Bind(viewName = "end_desc", setMethod = B.setOnClickListener)
public View.OnClickListener end_descL = new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("===", end_desc);
end_title = "+++++";
end_desc = "88888";
endTitleColor = getResources().getColor(R.color.colorAccent);
url = "++++++++";
swordBind.dataChange();
Log.e("===", end_desc);
}
};
@Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
private String url;
void setImageUrl(String url) {
Log.e("self", "--------" + url);
}
}
}
双向绑定
双向绑定和单向绑定的使用类似,只不过双向绑定,需要建立一个view的java类,如下:
class ViewHolder implements BindImpl {
private View price_ll;
private TextView luochui;
private View price_num;
private View other_win_desc;
private View end_rl;
private ImageView end_img;
private TextView end_title;
private TextView end_desc;
private View to_my_order;
}
使用时viewHolder
的属性名就是xml里面id的名字,也就是说 private TextView luochui;
对应
<TextView
android:id="@+id/luochui"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="落槌价:¥" />
双向绑定的用法:
SwordBind.renderById(holder,this);
data = new Data();
swordBind.bind(holder,data);
view变化时,通知data变化:
holder.luochui.setText("123");
holder.end_desc.setText("hahahah");
...
swordBind.viewChange();
优化特性:
1.getMethod不指明的情况下,是根据setMethod来推演的,推演出getMethod或者isMethod.
2.内部有一个对应常量关系,罗列了几乎所有用到的view的setMethod方法和对应的getMethod方法。
3.数据更新的时候,是变化的数据才会应用更新,内部采用差量更新的策略。
法则
- 尽量使用真值绑定(真值指的是具有双向绑定的值,上面提到的伪值绑定有缺陷)
- 单向绑定受外力可能会有影响(外力指的是除了data作用外的其他调用方式)
- 单向绑定很容易转化为双向绑定
- 尽量不要使用外力影响view的展现
请务必记住这张图
最新功能
- 2016.14 ,对 minifyenable =true 支持。## 标题 ##
Sword 框架新增实验特性
标签(空格分隔): Android
背景
这段时间一直在想能不能去掉swordBind.dataChange();
或者swordBind.viewChange();
代码的调用.即用户只需要关心操作数据,或者操作视图,不需要关心操作完数据后,通知sword
框架数据有变化。这段时间有一些想法,虽然不是很完美,但是确实做到了去掉swordBind.dataChange();
和swordBind.viewChange();
代码的目的。
直接代码对比
原来的代码是这样写的
end_title = "+++++";
end_desc = "88888";
url = "++++++++";
swordBind.dataChange();
现在成了这样:
end_title = "+++++";
end_desc = "88888";
url = "++++++++";
少了一行swordBind.dataChange();
,只不过我把这行代码隐藏起来放到了另外的地方。
如何做到
Android的UI是运行在一个loop线程上的,每一次loop运行完,android.os.Looper.loop()
都会调用代码
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
利用logging
的回调,我们可以注入一个android.util.Printer
对象,每次UI的线程执行完,我们都会得到一次回调。在这个回调里面做我们想做的事情,即swordBind.dataChange();
或者swordBind.viewChange();
.
限定模型
我不敢保证我上述的做法在任何情况下都是OK的,但是每一次引起数据变化,肯定是存在交互,交互最后都会回溯到UI线程上面,在UI线程的底部完成交互。即使是网络调用,AsycTask依赖会回调到UI线程上onPostExecute
.
根据这一普适的特性,我可以构造一个属于我的模型,在这个模型里面我可以保证这一个实验特性的正确性。
收窄模型,即只要遵循data的值操作都是在UI线程上进行,(视图操作就不用说了,肯定在UI线程操作)
总结:
只要data的值操作都是在UI线程上进行,那么这个模型一定有效。这个实验特性也是一样有效
核心代码
/////check value change
private boolean useExperiment=false;
private static List<SwordBind> swordBindList = new ArrayList<>();
private static boolean isInitSmart=false;
public void setUseExperiment(boolean useExperiment) {
this.useExperiment = useExperiment;
}
private void initSmartCheck(final Context context) {
if (!isInitSmart) {
context.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
if(x.startsWith("<<<<< Finished to"))
for (SwordBind swordBind : swordBindList) {
swordBind.dataChange();
swordBind.viewChange();
}
}
});
isInitSmart = true;
}
}
public void onPause(){
if(useExperiment)
swordBindList.remove(this);
}
public void onResume(){
if(!useExperiment)
return;
if(parent!=null)
initSmartCheck(parent.getContext());
else
initSmartCheck(activity);
swordBindList.add(this);
}
效率
为了平衡最少代码调用和代码执行效率2个方面,我苦苦挣扎了好几天,最后还是效率为王,即sword所在的activity或者fragment当前是否在前台,需要主动告诉我。如果在adaptor里一定要用这个实验特性,需要构造2个回调方法,让activity或者fragment回调。
用法
开启实验特性
swordBind.setUseExperiment(true);
开启完毕之后,调用2个核心代码即可:
@Override
protected void onResume() {
super.onResume();
swordBind.onResume();
}
@Override
protected void onPause() {
super.onPause();
swordBind.onPause();
}
完整代码:
package com.taobao.pandora.shanks;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.taobao.pandora.sword.BindImpl;
import com.taobao.pandora.sword.SwordBind;
import com.taobao.pandora.sword.meta.B;
import com.taobao.pandora.sword.meta.Bind;
/**
* Created by shanksYao on 1/5/16.
*/
public class BindActivity extends Activity {
Data data;
SwordBind swordBind = new SwordBind();
ViewHolder holder = new ViewHolder();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.live_include_deal_status);
swordBind.renderById(holder,this);
data = new Data();
swordBind.bind(holder,data);
swordBind.setUseExperiment(true);
}
@Override
protected void onResume() {
super.onResume();
swordBind.onResume();
}
@Override
protected void onPause() {
super.onPause();
swordBind.onPause();
}
class ViewHolder implements BindImpl {
private View price_ll;
private TextView luochui;
private View price_num;
private View other_win_desc;
private View end_rl;
private ImageView end_img;
private TextView end_title;
private TextView end_desc;
private View to_my_order;
}
/**
* data就是用来操作View 任何跟View无关的属性 请不要放进来,以免造成设计混乱
*/
class Data implements BindImpl {
@Bind(viewName = "end_title",setMethod = B.setText,getMethod = "getText")
public CharSequence end_title = "1234";
public String end_desc = "aaa";
@Bind(viewName = "end_title", setMethod = B.setTextColor)
public int endTitleColor = getResources().getColor(R.color.colorAccent);
@Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
public View.OnClickListener end_titleL = new View.OnClickListener() {
@Override
public void onClick(View v) {
/* end_title = "88888";
end_desc = "+++++";
endTitleColor = getResources().getColor(R.color.colorPrimary);
url = "~~~~~";*/
holder.end_title.setText("123");
holder.end_desc.setText("hahahah");
// swordBind.dataChange();
}
};
@Bind(viewName = "end_desc", setMethod = B.setOnClickListener)
public View.OnClickListener end_descL = new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("===", end_desc);
end_title = "+++++";
end_desc = "88888";
endTitleColor = getResources().getColor(R.color.colorAccent);
url = "++++++++";
// swordBind.dataChange();
Log.e("===", end_desc);
}
};
//
void setEndTitle(int id) {
end_title = getResources().getString(id);
//swordBind.dataChange();
}
void setUrl(ViewHolder holder, String url) {
/////设置方法处理
holder.end_img.setImageDrawable(null);
}
@Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
private String url;
void setImageUrl(String url) {
Log.e("self", "--------" + url);
}
}
////Ui数据绑定
}