view.setOnClickListener(onClickListener);
别的一种做法是直接在XML规划内里指定View点击时刻的回调要领,起首须要在Activity中编写用于回调的要领,比方
public void onClickView(View view){ // do something }
然后在XML设置View的android:onClick
属性
<View android:layout_width="match_parent" android:layout_height="match_parent" android:onClick="onClickView" />
有的时刻从XML规划里直接设定点击事宜会比较轻易(迥殊是在写DEMO项目的时刻),这类做法日常平凡用的人并不多,从运用体式格局上大抵能猜出来,View应该是在运转的时刻,运用反射的体式格局从Activity找到“onClickView”要领并挪用,由于这类做法并没有用到任何接口。
接下来,我们可以从源码中分析出View是怎样触发还调要领的。
View有5个组织要领,第一个是内部运用的,日常平凡在Java代码中直接建立View实例用的是第二种要领,而从XML规划衬着出来的View实例末了都是要挪用第五种要领。
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { this(context); final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { …… // 处置惩罚onClick属性 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) { // 给当前View实例设置一个DeclaredOnClickListener监听器 setOnClickListener(new DeclaredOnClickListener(this, handlerName)); } break; } } }
处置惩罚onClick属性的时刻,先推断View的Context是不是isRestricted,假如是就抛出一个IllegalStateException非常。看看isRestricted要领
/** * Indicates whether this Context is restricted. * * @return {@code true} if this Context is restricted, {@code false} otherwise. * * @see #CONTEXT_RESTRICTED */ public boolean isRestricted() { return false; }
isRestricted是用于推断当前的Context实例是不是出于被限定的状况,根据官方的诠释,处限定状况的Context,会疏忽某些特性的功用,比方XML的某些属性,很明显,我们在研讨的android:onClick
属性也会被疏忽。
a restricted context may disable specific features. For instance, a View associated with a restricted context would ignore particular XML attributes.
不过isRestricted要领是Context中为数不多的有细致完成的要领(其他基础是笼统要领),这里直接返回false,而且这个要领只要在ContextWrapper和MockContext中有重写
public class ContextWrapper extends Context { Context mBase; @Override public boolean isRestricted() { return mBase.isRestricted(); } } public class MockContext extends Context { @Override public boolean isRestricted() { throw new UnsupportedOperationException(); } }
ContextWrapper中也只是代办挪用mBase的isRestricted,而MockContext是写单元测试的时刻才会用到,所以这里的isRestricted基础只会返回false,除非运用了自定义的ContextWrapper并重写了isRestricted。
回到View,接着的final String handlerName = a.getString(attr);
实在就是拿到了android:onClick="onClickView"
中的“onClickView”这个字符串,接着运用了当前View的实例和“onClickView”建立了一个DeclaredOnClickListener实例,并设置为当前View的点击监听器。
/** * An implementation of OnClickListener that attempts to lazily load a * named click handling method from a parent or ancestor context. */ private static class DeclaredOnClickListener implements OnClickListener { private final View mHostView; private final String mMethodName; private Method mMethod; public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) { mHostView = hostView; mMethodName = methodName; } @Override public void onClick(@NonNull View v) { if (mMethod == null) { mMethod = resolveMethod(mHostView.getContext(), mMethodName); } try { mMethod.invoke(mHostView.getContext(), v); } catch (IllegalAccessException e) { throw new IllegalStateException( "Could not execute non-public method for android:onClick", e); } catch (InvocationTargetException e) { throw new IllegalStateException( "Could not execute method for android:onClick", e); } } @NonNull private Method resolveMethod(@Nullable Context context, @NonNull String name) { while (context != null) { try { if (!context.isRestricted()) { return context.getClass().getMethod(mMethodName, View.class); } } catch (NoSuchMethodException e) { // Failed to find method, keep searching up the hierarchy. } if (context instanceof ContextWrapper) { context = ((ContextWrapper) context).getBaseContext(); } else { // Can't search up the hierarchy, null out and fail. context = null; } } final int id = mHostView.getId(); final String idText = id == NO_ID ? "" : " with id '" + mHostView.getContext().getResources().getResourceEntryName(id) + "'"; throw new IllegalStateException("Could not find method " + mMethodName + "(View) in a parent or ancestor Context for android:onClick " + "attribute defined on view " + mHostView.getClass() + idText); } }
到这里就清晰了,当点击View的时刻,DeclaredOnClickListener实例的“onClick”要领会被挪用,接着会挪用“resolveMethod”要领,运用反射的体式格局从View的Context中找一个叫“onClickView”要领,这个要领有一个View范例的参数,末了再运用反射挪用该要领。要注意的是,“onClickView”要领必需是public范例的,不然反射挪用时会抛出IllegalAccessException非常。
同时从源码也能看出,运用android:onClick
设置点击事宜的体式格局是从Context内里查找回调要领的,所以假如关于在Fragment的XML里建立的View,是没法经由过程这类体式格局绑定Fragment中的回调要领的,由于Fragment本身并非一个Context,这里的View的Context实际上是FragmentActivity,这也意味着运用这类体式格局可以疾速地从Fragment中回调到FragmentActivity。
另外,从DeclaredOnClickListener类的解释也能看出android:onClick
的功用,主如果起到懒加载的作用,只要到点击View的时刻,才会晓得哪一个要领是用于点击回调的。
末了,迥殊须要补充申明的是,运用android:onClick
给View设置点击事宜,就意味着要在Activity里增加一个非接口的public要领。如今Android的开辟趋向是“不要把营业逻辑写在Activity类内里”,如许做有利于项目的保护,防备Activity爆炸,所以只管不要在Activity里涌现非接口、非生命周期的public要领。因而,冒然运用android:onClick
可能会“污染”Activity。
以上就是在XML规划里给View设置点击事宜的案例分享的细致内容,更多请关注ki4网别的相干文章!