LightSun/android-databinding(第二篇事件绑定)源码剖析与思考

版权声明:本文为博主原创文章,未经博主允许不得转载。 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有关的所有绑定的属性,然后为每一个属性进行相应的操作,最终还是调用到下面这个方法

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当中标记下,真正需要的时候框架你帮我注册,我不管了。






猜你喜欢

转载自blog.csdn.net/xiatiandefeiyu/article/details/78979266
今日推荐