本文共 12064 字,大约阅读时间需要 40 分钟。
数据绑定框架有很多,其实我就看过谷歌官方的数据绑定框架,官方的框架用起来的时候,觉得不是很顺手,侵入性还比较强。而且也一直纠结彷徨,从心底里质疑数据绑定框架的价值,到底给我们开发带来了什么,实用吗,可维护吗?
某一天的早晨突然灵光一现,决定自己去试试开发一个自己喜欢的数据绑定框架,经过没日没夜的艰苦风斗(当然是开玩笑的),中间反复修改设计,最后尘埃落定。虽然这个框架已经出世,也在生产环境试运行了一些页面,但是我对于数据绑定框架的价值,始终抱有质疑的态度,所以今天我将其取名
sword
,希望他像一把双刃剑,能够有一个面给开发者带来一些小小的便利。
数据绑定的价值,我的理解就是一句话:就是通过操作一个数据对象,达到修改视图的目的,或者反过来,通过操作视图对象,达到修改数据对象的目的。
提示:
在大多数的情况下,通常都是数据单向操作视图的,极少的情况下需要数据和视图双向绑定,互相操作。
textView.setText(CharSequence data);data = textView.getText();
第一行代码代表值到视图的映射,第二行代码代表视图到值的映射。
sword的用法分为单向绑定(常用的),双向绑定2种。
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(); } };}
@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种用法,直接上图。
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;
对应
双向绑定的用法:
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.数据更新的时候,是变化的数据才会应用更新,内部采用差量更新的策略。标签(空格分隔): 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 ListswordBindList = 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数据绑定}
转载地址:http://ynbva.baihongyu.com/