ホームページ  >  記事  >  バックエンド開発  >  XML レイアウトでの表示のクリック イベントの設定に関するケースシェアリング

XML レイアウトでの表示のクリック イベントの設定に関するケースシェアリング

黄舟
黄舟オリジナル
2017-03-17 17:25:491485ブラウズ

    view.setOnClickListener(onClickListener);
など、ビューのクリック監視 イベント

を設定するのが一般的です。もう 1 つの方法は、

XML レイアウトでビューがクリックされたときのコールバック メソッドを直接指定することです。 Activity メソッドにコールバック (

    public void onClickView(View view){
        // do something
    }

など) を記述し、View の android:onClick

android:onClick属性

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:onClick="onClickView" />

有的时候从XML布局里直接设定点击事件会比较方便(特别是在写DEMO项目的时候),这种做法平时用的人并不多,从使用方式上大致能猜出来,View应该是在运行的时候,使用反射的方式从Activity找到“onClickView”方法并调用,因为这种做法并没有用到任何接口。

接下来,我们可以从源码中分析出View是怎么触发回调方法的。

XML レイアウトでの表示のクリック イベントの設定に関するケースシェアリング
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中有重写
XML レイアウトでの表示のクリック イベントの設定に関するケースシェアリング

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&#39;t search up the hierarchy, null out and fail.
                    context = null;
                }
            }

            final int id = mHostView.getId();
            final String idText = id == NO_ID ? "" : " with id &#39;"
                    + mHostView.getContext().getResources().getResourceEntryName(id) + "&#39;";
            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:onClickrrreee

場合によっては、XML レイアウトから直接クリック イベントを設定する方が便利な場合があります (特にデモ プロジェクトを作成する場合)。通常、この方法を使用する人は多くありません。このアプローチではインターフェイスが使用されないため、ビューはリフレクションを使用してアクティビティから「onClickView」メソッドを見つけ、実行時にそれを呼び出す必要があります。


次に、View がソース コードからコールバック メソッドをトリガーする方法を分析できます。

🎜XML レイアウトでの表示のクリック イベントの設定に関するケースシェアリング🎜ビューには 5 つあります構築メソッド 🎜、最初のメソッドは内部で使用されます。通常、2 番目のメソッドは Java コード内で View インスタンスを直接作成するために使用され、XML レイアウトからレンダリングされた View インスタンスが作成されます。最終的には 5 番目のメソッドを呼び出します。 🎜rrreee🎜 onClick 属性を処理するときは、まずビューのコンテキストが制限されているかどうかを判断し、制限されている場合は IllegalStateException をスローします。 isRestricted メソッドを見てみましょう🎜rrreee🎜 isRestricted は、現在の Context インスタンスが制限された状態にあるかどうかを判断するために使用されます。公式の説明によると、制限された状態にある Context は XML の特定の属性などの特定の機能を無視します。明らかに、私たちが研究している android:onClick 属性も無視されます。 🎜
🎜制限されたコンテキストでは、特定の機能が無効になる場合があります。たとえば、制限されたコンテキストに関連付けられたビューは、特定の XML 属性を無視します。🎜
🎜ただし、isRestricted メソッドは、Context の数少ないメソッドの 1 つです。特定の実装 (残りは基本的に抽象メソッドです)、ここでは false を直接返します。このメソッドは ContextWrapper と MockContext でのみオーバーライドされます🎜XML レイアウトでの表示のクリック イベントの設定に関するケースシェアリング🎜rrreee🎜ContextWrapper はプロキシのみ mBase の isRestricted を呼び出しますが、MockContext は書き込まれます
これは単体テスト中にのみ使用されるため、カスタム ContextWrapper が使用され、isRestricted が書き換えられない限り、ここでの isRestricted は基本的に false のみを返します。 🎜ビューに戻り、final String handlerName = a.getString(attr); は実際に android:onClick="onClickView"文字列 🎜 を取得し、現在のビューのインスタンスと「onClickView」を使用して DeclaredOnClickListener インスタンスを作成し、クリック リスナーに設定します。現在のビューの場合。 🎜rrreee🎜ここで明らかです。ビューがクリックされると、DeclaredOnClickListener インスタンスの「onClick」メソッドが呼び出され、次にリフレクションを使用してビューの「onClickView」というメソッドが検索されます。 Context. 、このメソッドには View 型のパラメータがあり、最後にメソッドはリフレクションを使用して呼び出されます。 「onClickView」メソッドはパブリック型でなければならないことに注意してください。そうでない場合、リフレクション呼び出しが行われたときに IllegalAccessException がスローされます。 🎜🎜同時に、android:onClick を使用してクリック イベントを設定する方法は、Context からコールバック メソッドを見つけることであることもソース コードからわかります。 Fragment 自体は Context ではないため、このメソッドは Fragment のコールバック メソッドをバインドします。これは、このメソッドが Fragment から FragmentActivity にすばやくコールバックできることも意味します。 。 🎜🎜さらに、DeclaredOnClickListener クラスのコメントから、 android:onClick の機能も確認できます。これは主に View の場合のみ遅延読み込みとして機能するものです。クリックされると、クリック コールバックにどのメソッドが使用されるかがわかります。 🎜🎜最後に、追加する必要があるのは、android:onClick を使用して View のクリック イベントを設定するということは、非インターフェイスのパブリック メソッドをアクティビティに追加することを意味するということです。現在の Android 開発トレンドは、「Activity クラスにビジネス ロジックを記述しない」です。これはプロジェクトの保守に有益であり、Activity の爆発を防ぐため、Activity には非インターフェイスおよび非ライフサイクルのパブリック メソッドを含めないようにしてください。したがって、 android:onClick をむやみに使用すると、アクティビティが「汚染」される可能性があります。 🎜🎜🎜🎜

以上がXML レイアウトでの表示のクリック イベントの設定に関するケースシェアリングの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。