先看View中是如何定义clickable和onClick的:
case com.android.internal.R.styleable.View_clickable: if (a.getBoolean(attr, false)) { viewFlagValues |= CLICKABLE; viewFlagMasks |= CLICKABLE; } break;
上面这段代码是View源码中对clickable属性的定义,缺省值为false。
case R.styleable.View_onClick: if (context.isRestricted()) { throw new IllegalStateException("The android:onClick attribute cannot " + "be used within a restricted context"); } final String handlerName = a.getString(attr); if (handlerName != null) { setOnClickListener(new OnClickListener() { private Method mHandler; public void onClick(View v) { if (mHandler == null) { try { mHandler = getContext().getClass().getMethod(handlerName, View.class); } catch (NoSuchMethodException e) { int id = getId(); String idText = id == NO_ID ? "" : " with id ‘" + getContext().getResources().getResourceEntryName( id) + "‘"; throw new IllegalStateException("Could not find a method " + handlerName + "(View) in the activity " + getContext().getClass() + " for onClick handler" + " on view " + View.this.getClass() + idText, e); } } try { mHandler.invoke(getContext(), View.this); } catch (IllegalAccessException e) { throw new IllegalStateException("Could not execute non " + "public method of the activity", e); } catch (InvocationTargetException e) { throw new IllegalStateException("Could not execute " + "method of the activity", e); } } }); } break;
上面是onClick属性在View源码中的定义。可以看到它调用了setOnClickListener()方法对点击事件进行注册,下面看下setOnClickListener()方法的源码:
/** * Register a callback to be invoked when this view is clicked. If this view is not * clickable, it becomes clickable. * * @param l The callback that will run * * @see #setClickable(boolean) */ public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
如果clickable属性是false那么在调用此方法后就会变为true。
继承自View的Button、ImageView控件在xml里使用onClick属性时,通过打印日志可以看到clickable由默认的false变为了true,可是同样继承自View的TextView依旧是false。为什么会出现这种情况呢?
那就要了解Android加载xml文件的过程。
以View为例进行说明。我们使用setOnContentView()方法加载布局文件时。布局文件中的View元素会调用下面的这个构造器来新建一个View对象
/** * Constructor that is called when inflating a view from XML. This is called * when a view is being constructed from an XML file, supplying attributes * that were specified in the XML file. This version uses a default style of * 0, so the only attribute values applied are those in the Context‘s Theme * and the given AttributeSet. * * <p> * The method onFinishInflate() will be called after all children have been * added. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. * @see #View(Context, AttributeSet, int) */ public View(Context context, AttributeSet attrs) { this(context, attrs, 0); }
this(context, attrs, 0);又会调用 public View(Context context, AttributeSet attrs, int defStyleAttr){}构造器。可以看到onClick属性在定义时调用的setOnClickListener()方法是在对象被创建时调用的。
下面再来看TextView控件,他在构造器中对clickable又进行了一些处理
boolean focusable = mMovement != null || getKeyListener() != null; boolean clickable = focusable; boolean longClickable = focusable; n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case com.android.internal.R.styleable.View_focusable: focusable = a.getBoolean(attr, focusable); break; case com.android.internal.R.styleable.View_clickable: clickable = a.getBoolean(attr, clickable); break; case com.android.internal.R.styleable.View_longClickable: longClickable = a.getBoolean(attr, longClickable); break; } }
从代码中可以看到clickable的缺省值变成了focuseable的值,TextView在调用构造器创造新的对象的时候会默认调用父类的构造器,也就是说经过setOnClickListener()处理过的clickable值是变成了true,可是在TextView的构造器中又被赋予了新的值。这也可以解释说为什么TextView控件在xml文件中用了onClick属性后clickable的值依旧是false,而在代码中调用setOnClickListener()方法后,clickable值就变成了true,那是因为在代码中调用的setOnClickListener()方法是在TextView对象被建立之后。