Home  >  Article  >  Backend Development  >  Case sharing on setting click events for View in XML layout

Case sharing on setting click events for View in XML layout

黄舟
黄舟Original
2017-03-17 17:25:491485browse

Setting up a click monitor for a View Event is a very common thing, such as

    view.setOnClickListener(onClickListener);

Another way is to directly specify the View click in the XML layout When using the callback method, you first need to write the method for callback in the Activity, such as

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

and then set the android:onClick attribute of the View in XML

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

Sometimes it is more convenient to directly set the click event from the XML layout (especially when writing DEMO projects). Not many people usually use this method. You can roughly guess it from the usage method. The View should use reflection to find the "onClickView" method from the Activity and call it when it is running, because this approach does not use any interface.

Next, we can analyze how View triggers the callback method from the source code.

Case sharing on setting click events for View in XML layout
View has 5 construction methods. The first one is used internally. The second method is usually used to create View instances directly in Java code. The View instance rendered from the XML layout will eventually call the fifth method.

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;
                }
        }
}

When processing the onClick attribute, first determine whether the View's Context isRestricted, and if so, throw an IllegalStateException. Take a look at the isRestricted method

    /**
     * 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 is used to determine whether the current Context instance is in a restricted state. According to the official explanation, a Context in a restricted state will ignore certain features, such as XML. Certain properties, obviously the android:onClick property we're looking at are also ignored.

a restricted context may disable specific features. For instance, a View associated with a restricted context would ignore particular XML attributes.

However, the isRestricted method is one of the few in Context Most of them have concrete implementation methods (the rest are basically abstract methods), here they return false directly, and this method is only overridden in ContextWrapper and MockContext
Case sharing on setting click events for View in XML layout

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 is also only a proxy Call mBase's isRestricted, and MockContext is only used when writing unit tests, so isRestricted here will basically only return false, unless a custom ContextWrapper is used and isRestricted is rewritten.
Go back to View, then final String handlerName = a.getString(attr);In fact, we get the "onClickView" in android:onClick="onClickView" ##String, then use the instance of the current View and "onClickView" to create a DeclaredOnClickListener instance and set it as the click listener of the current 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);
        }
}

It is clear here that when the View is clicked, the "onClick" method of the DeclaredOnClickListener instance will be called, and then the "resolveMethod" method will be called, using reflection to find a call from the View's Context. "onClickView" method, this method has a parameter of View type, and finally calls this method using reflection. It should be noted that the "onClickView" method must be of public type, otherwise an IllegalAccessException will be thrown when the reflection call is made.

At the same time, it can also be seen from the source code that the way to set the click event using

android:onClick is to find the callback method from the Context, so if for the View created in the Fragment's XML, It is impossible to bind the callback method in Fragment in this way, because Fragment itself is not a Context. The Context of the View here is actually FragmentActivity, which also means that this method can be used to quickly call back from Fragment to FragmentActivity.

In addition, from the comments of the DeclaredOnClickListener class, we can also see the function of

android:onClick, mainly plays the role of lazy loading, only when the View is clicked , you will know which method is used for click callback.

Finally, what needs to be added is that using

android:onClick to set a click event for View means adding a non-interface public method to the Activity. The current Android development trend is "Don't write business logic in the Activity class." This is beneficial to project maintenance and prevents Activity explosion, so try not to have non-interface and non-lifecycle public methods in Activity. Therefore, rash use of android:onClick may "pollute" the Activity.


The above is the detailed content of Case sharing on setting click events for View in XML layout. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn