版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiatiandefeiyu/article/details/78979266
上一篇中大体的讲解了一下属性绑定的套路LightSun/android-databinding(第一篇属性绑定)源码剖析与思考,这一篇来看一下事件绑定的套路,怎么通过xml的属性配置让DataBinding自己识别是什么事件,在View层不见一句OnClick、OnTouch等等事件,确可以分隔离操纵它。先来看一下xml的OnClick事件的配置
<property name="onClick" referVariable="user,mainHanlder" >mainHanlder.onClickChangeUsername(user)</property>
<variable name="user" classname="com.heaven7.databinding.demo.bean.User" type="bean"/>
<variable name="mainHanlder" classname="com.heaven7.databinding.demo.callback.MainEventHandler" type="callback"/>
如果是你自己写这么一条配置的话,换句话说自己制造规则的话,那么我们需要分别解析出property标签的text内容mainHanlder为com.heaven7.databinding.demo.callback.MainEventHandler的对象,然后调用它的onClickChangeUsername方法,带的参数是user,然后再看下面这条代码
mDataBinder.bind(R.id.bt0, false, mUser, new MainEventHandler(mDataBinder));
绑定的时候将MainEventHandler传入,在解析variable时候type不同,要放在不同的集合中,然后在绑定的时侯根据class的名字判断传入的对象属于哪一个集合,如果在type为callback是标记当前的属性property映射的对象isMethod为true,表示这个对象要做的是回调方法,从而达到最终点击事件的反射传递。ok,开始上代码,代码才能说明一切。
首先是解析分开放,还记得上一篇中说到的映射操作的监听函数吗?这些回调都在DataBindParser类中的内部类InternalElementParserListener中实现
private void doWithVariableElement(VariableElement ve) {
if(sDebug){
System.out.println("doWithVariableElement(): " + ve.toString());
}
final String type = ve.getType();
if(VariableType.CALLBACK.equals(type)){
//事件添加集合
//event
mVariableCallbakMap.put(ve.getClassname(),ve.getName());
//将名字添加到HandlerVariable中
mDataResolver.addEventHandlerVariable(ve.getName());
}else {
mVariableBeanMap.put(ve.getClassname(), ve.getName());
}
}
看到了吗,这里的一个判断如果是事件,那么在mVariableCallbakMap集合中保存以className为key,那么为value的集合中,并在BaseDataResolver留一个名字的备注,如果只是普通的bean对象的话,那么只保存mVariableBeanMap集合中。
还记得上一篇生成的值返回的策略的方法吗,也就是将表达式解析为你所需要的值的辅助类,假设这里已经将你的mainHanlder.onClickChangeUsername(user)生成了你的规则类,这里需要的映射就是将user变为你传入的User对象,onClickChangeUsername为方法名。也就是最终会解析为这个策略类,这里标记为是方法的方式setIsMethod方法将它标记为是callback类型的属性绑定。
Expression expr = new Expression.Builder()
.setVariable(info.variableName)
.setAccessName(info.accessName)
.setStaticAccessClassname(info.staticClassname)
.setIsMethod(info.isMethod())
//填入方法参数
.setParamAccessInfos(info.isIncludeMethodParam() ?
parse(str.substring(info.miniBracketLeftIndex + 1,
info.miniBracketRightIndex), true)
:null)
.setArrayIndexExpression(info.isArray() ? parse(str.substring(
info.compactSquareLeftIndex + 1,info.compactSquareRightIndex),false
).get(0) :null)
.build();
public boolean isMethod(){
return miniBracketLeftIndex != ExpressionParser.INVALID_INDEX;
}
看到这个方法,判断是否是方法的条件是它检测到表达式中有“(”,这就是这个框架制定的规则,只要检测到表达式中有“(“就认为策略模式定义它为方法。解析完数据得到策略之后,接下来就是绑定数据了,再一次进入绑定数据的方法,如下
public void applyData(int id, int type, boolean checkStrictly ,boolean cacheData,Object... datas) {
//检查绑定的数据是否存在
checkDatasExist(datas);
//check , put data and apply
//将当前的View取出来
mDataResolver.setCurrentBindingView(mViewHelper.getView(id));
if(cacheData){
final ViewHelper mViewHelper = this.mViewHelper;
final IDataResolver mDataResolver = this.mDataResolver;
final EventParseCaretaker caretaker = this.mEventCareTaker;
final SparseArray<ListenerImplContext> mListenerMap = this.mListenerMap;
final Array<VariableInfo> propVarInfos = new Array<>(4);
/**
* 获取保存的PropertyBindInfo集合
*/
Array<PropertyBindInfo> array = mBindMap_viewId.get(id);
if(checkStrictly) {
checkReferVariables(id, array, datas);
}
/**
* 根据viewID找到所有需要绑定的属性
*/
PropertyBindInfo info ;
for (int i = 0, size = array.size; i < size; i++) {
info = array.get(i);
getReferVariableInfos(info.referVariables, datas, propVarInfos);
//here checkStrictly must be false, to ignore unnecessary exception
applyDataInternal0(id, propVarInfos, info, mViewHelper, mDataResolver,
false, mListenerMap,caretaker);
//放入缓存
addToVariableInfoCache(id,propVarInfos,info);
propVarInfos.clear();
}
}else {
if(checkStrictly) {
checkReferVariables(id, mBindMap_viewId.get(id), datas);
}
Array<VariableInfo> mTmpVariables = getAllVariables(datas);
//here checkStrictly must be false, to ignore unnecessary exception
applyDataInternal(id, null, mTmpVariables, false);
}
this.mTmpVariables.clear();
//clear datas refer
mDataResolver.clearObjects();
}
还是和昨天同样的内容,先从映射完的集合中,取出和当前view的Id有关的所有绑定的属性,然后为每一个属性进行相应的操作,最终还是调用到下面这个方法
扫描二维码关注公众号,回复:
3195000 查看本文章
private static void applyDataReally(int id, int layoutId,PropertyBindInfo info, ViewHelper vp,
IDataResolver dr,SparseArray<ListenerImplContext> mListenerMap,
EventParseCaretaker caretaker) {
www
/**
* 图片属性
*/
if(info instanceof ImagePropertyBindInfo){
checkAndGetImageApplier().apply((ImageView) vp.getView(id),
dr, (ImagePropertyBindInfo) info);
// applyImageProperty(vp.getView(id), dr, (ImagePropertyBindInfo) info);
}else {
//将一些信息注册给EventParseCaretaker
caretaker.beginParse(id, layoutId, info.propertyName, mListenerMap);
//得到计算值
final Object val = info.realExpr.evaluate(dr);
//将事件添加进去
caretaker.endParse();
apply(vp, id, layoutId, info.propertyName, val, mListenerMap);
}
}
上一篇中没有介绍caretaker.beginParse和caretaker.endParse()方法,绑定事件和这两个方法紧密相关,来看看这两个方法的实现
void beginParse(int viewId,int layoutId, String propertyName, SparseArray<ListenerImplContext> listenerMap){
this.id = viewId;
this.propertyName = propertyName;
this.mListenerMap = listenerMap;
this.layoutId = layoutId;
}
这个方法比较简单就是设置一些属性
/**
* 解析完成的时候注册监听
*/
void endParse(){
if(isEventProperty(propertyName)) {
final int key = getEventKey(id, layoutId, propertyName);
ListenerImplContext l = mListenerMap.get(key);
if (l == null) {
//通过listenter工厂获得
// ListenerFactory
l = createEventListener(propertyName);
mListenerMap.put(key, l);
}
//设置方法和holder和参数
l.set(method,holder,params);
}
reset();
}
这个方法就比较重要了,是根据属性取出注册的class
static ListenerImplContext createEventListener(String propName){
String className = sRegistedListenerMap.get(propName.hashCode());
if(className ==null){
throw new IllegalStateException("the target property is not " +
"event property name,propertyName = " +propName);
}else{
try {
return (ListenerImplContext) Class.forName(className).newInstance();
} catch (ClassCastException e) {
throw new DataBindException("the all listener must extends ListenerImplContext ");
}catch (Exception e) {
throw new DataBindException(e);
}
}
}
tatic ListenerImplContext createEventListener(String propName){
String className = sRegistedListenerMap.get(propName.hashCode());
if(className ==null){
throw new IllegalStateException("the target property is not " +
"event property name,propertyName = " +propName);
}else{
try {
return (ListenerImplContext) Class.forName(className).newInstance();
} catch (ClassCastException e) {
throw new DataBindException("the all listener must extends ListenerImplContext ");
}catch (Exception e) {
throw new DataBindException(e);
}
}
}
这里看到最终是从
sRegistedListenerMap集合中取的数据,来看下它是什么东东
sRegistedListenerMap = new SparseArray<String>();
/**
* 点击事件、长按事件、文本改变事件、onTouch事件、焦点事件
*/
registEventListener(PropertyNames.ON_CLICK , OnClickListenerImpl.class);
registEventListener(PropertyNames.ON_LONG_CLICK , OnLongClickListenerImpl.class);
registEventListener(PropertyNames.TEXT_CHANGE , TextWatcherImpl.OnTextChangeImpl.class);
registEventListener(PropertyNames.TEXT_CHANGE_AFTER , TextWatcherImpl.AfterTextChangeImpl.class);
registEventListener(PropertyNames.TEXT_CHANGE_BEFORE, TextWatcherImpl.BeforeTextChangeImpl.class);
registEventListener(PropertyNames.ON_FOCUS_CHANGE, OnFocusChangeListenerImpl.class);
registEventListener(PropertyNames.ON_TOUCH, OnTouchListenerImpl.class);
}
可以看出key值正好对应property标签的属性值,也可以看出作者的事件规则只有点击、长按、文本改变监听事件、onTouch事件、焦点事件的监听,这里只以onclick为事件为引,看看OnClickListenerImpl
public class OnClickListenerImpl extends ListenerImplContext implements View.OnClickListener{
public OnClickListenerImpl(){}
//真正的实现类点击的类
@Override
public void onClick(View v) {
invokeCallback();
}
}
额,它实现了
View.OnClickListener事件
protected Object invokeCallback(Object[] params,String exceptionNotice){
try {
if(sDebug) {
System.out.print("[ invokeCallback() ]: holder = " + mHolder);
System.out.println(" ,params = " + Arrays.toString(params));
}
return mMethod.invoke(mHolder,params);
} catch (Exception e) {
if (exceptionNotice == null)
throw new DataBindException(e);
else {
throw new DataBindException(exceptionNotice, e);
}
}finally {
afterCallback();
}
}
最后通过反射调用方法,那么反射调用的是哪个方法,这又回到了xml文件中
<property name="onClick" referVariable="user,mainHanlder" >mainHanlder.onClickChangeUsername(user)</property>
方法名为onClickChangeUsername,参数为user对象,当前对象为MainEventHandler,好了只要想法设法将这几个数据传给ListenerImplContext对象,那么事件就有了生命,接下来又到了数据通过策略类映射为数据了,这里只贴和方法有关的代码
if (mIsMethod) {
final List<IExpression> mParamAccessInfos = this.mParamAccessInfos;
final int len = mParamAccessInfos == null ? 0 : mParamAccessInfos.size();
Object[] params = new Object[len];
//mParams[i].getClass().isPrimitive() //if wrapped i don't know is int or Integer
IExpression ie ;
for (int i = 0; i < len; i++) {
ie = mParamAccessInfos.get(i);
params[i] = ie.evaluate(dataResolver);
if(sDebug){
System.out.println("Param value: "+params[i]);
}
}
//reset and clear occasional ObjectExpression
if(len > 0 ) {
for (int i = 0; i < mParamAccessInfos.size(); ) {
ie = mParamAccessInfos.get(i);
if (ie instanceof ObjectExpression && ((ObjectExpression)ie).isOccasional() ) {
ie.reset();
mParamAccessInfos.remove(ie);
}else{
i++;
}
}
}
//invokeCallback mMethod or callback if is event
Object holder = this.mHolder;
Object result = null;
if(params.length > 0 && params[0] instanceof View){
Method method = ReflectUtil.getAppropriateMethod(clazz,mAccessName,ArrayUtil.getTypes(params));
/*try {
method = clazz.getDeclaredMethod(mAccessName, ArrayUtil.getTypes(params));
}catch (NoSuchMethodException e){
List<Method> ms = dataResolver.getMethod(clazz, mAccessName);
final int size = ms.size();
if( size == 0){
throw new DataBindException("event handler must have the method name = " + mAccessName);
}
if(size > 1){
throw new DataBindException("event handler can only have one method with the name = " +
mAccessName + " ,but get " + size +"( this means burden method in event handler" +
" is not support !)");
}
method = ms.get(0);
}*/
/**
* 解析完了将参数方法,当前对象回调
*/
dataResolver.getEventEvaluateCallback().onEvaluateCallback(holder,method,params);
这个方法的意思就是首先获得所有参数的策略类,然后对它进行策略运算返回当前的参数值,比如user参数,那么返回的就是bind方法传入的user对象,然后用class和方法名反射出method,最后通过dataResolver.getEventEvaluateCallback().onEvaluateCallback将参数传回给EventParseCaretaker保持数据,注意这里的参数是通过下面这个方法获取的
public Object resolveVariable(String pName) throws DataBindException {
pName = pName.trim();
//color
if(pName.charAt(0)=='#'){
return Color.parseColor(pName);// throws IllegalArgumentException
}
// 12dp or sp
else if(Character.isDigit(pName.charAt(0)) ){
if( pName.endsWith("dp")) {
return ViewUtil.getDpSize(mAppContext,
Float.parseFloat(pName.substring(0, pName.length() - 2)));
}else if(pName.endsWith("sp")){
return ViewUtil.getSpSize(mAppContext,
Float.parseFloat(pName.substring(0, pName.length() - 2)));
}
}
else if(ResourceResolver.isResourceReference(pName)){
//check android resource reference , eg: '@color/red'
return ResourceResolver.getResValue(mAppContext, pName);
}
Object val = mObjectMap.get(pName);
if(val != null)
return val;
val = mLongStandingObjs.get(pName);
if(val != null)
return val;
throw new DataBindException("can't resolve the variable , name = " + pName +
" , current data map = " + mObjectMap.toString());
}
这里的user是通过mObjectMap获取的,既然参数、方法名、当前对象都先交给EventParseCaretaker了,EventParseCaretaker又通过caretaker.endParse方法将参数传给
ListenerImplContext,也就是传给OnClickListenerImpl了,那么接下来就是注册监听事件了,如下
else if(PropertyNames.ON_CLICK.equals(propertyName)){
impl.setOnClickListener((View.OnClickListener)
mListenerMap.get(getEventKey(id, layoutId, propertyName)));
直接给当前要绑定的view注册了点击监听事件,就是它OnClickListenerImpl
最终回调MainEventHandler的这个方法从而达到点击事件的注册交给框架来注册
public void onClickChangeUsername(View v,User user){
Util.changeUserName(user,"by_MainEventHandler_OnClick");
//change male
user.setMale(!user.isMale());
getDataBinder().notifyDataSetChanged(R.id.bt);
}
这里感觉有点IOC的感觉,控制反转,我只在xml当中标记下,真正需要的时候框架你帮我注册,我不管了。