Android 4.4TextView 在xml里面设置onClick点击事件没有响应

在Android4.4也就是sdk19的手机上。TextView的OnClick点击事件没有响应

xml:
<TextView
        android:id="@+id/tv_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="testClick"
        android:text="@string/hello_blank_fragment" />
java:
    public void testClick(View view) {
        Log.e("zmm", "testClick--->");
    }

百思不得其解,查了大量资料,发现,TextView的clickable默认为false,但是我们会发现之前,我们也没有手动将clickable设置为true,但是确可以点击,那是因为,我们通过setOnClickListener,会被设置成可点击:

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

有人又说了,我也是通过 android:onClick="testClick"来设置点击事件的,一样可以响应。是的,在4.4之后android:onClick也会做一个操作,就是将不可点击的修改为可点击的。但是4.4的时候,需要你手动写上:android:clickable="true",写在xml里面的点击事件才可以响应。又双有人说了。我的设备也是4.4,点击事件设置也是在xml里面,也没有手动写上android:clickable="true",点击事件一样响应了,那是因为你的Activity继承的是AppCompatActivity,看源码,可以发现AppCompatActivity里面会将TextView转换成AppCompatTextView。

从AppCompatActivity.java的setContentView得到AppCompatDelegateImpl,然后调用setContentView
    public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
    }

 @NonNull
    public AppCompatDelegate getDelegate() {
        if (this.mDelegate == null) {
            this.mDelegate = AppCompatDelegate.create(this, this);
        }

        return this.mDelegate;
    }

AppCompatDelegate.java的create方法
 public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }

    public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(dialog.getContext(), dialog.getWindow(), callback);
    }

    public static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(context, window, callback);
    }

AppCompatDelegateImpl.java的setContentView()

public void setContentView(View v) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        contentParent.addView(v);
        this.mOriginalWindowCallback.onContentChanged();
    }

    public void setContentView(int resId) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }

这边通过先添加DecorView,在找到名为content的framelayout的id,通过在这个id上添加我们的布局:

具体加载流程请参考:  Android activity如何加载布局

主要是LayoutInflater.createViewFromTag();

 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (Exception e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }

createViewFromTag是用来创建View的,先去判断factory2、factory这两个东西,而我们的AppCompatDelegateImpl实现了factory2接口,所以这里的mFactory2.onCreateView其实回调到了AppCompatDelegateImpl的onCreateView里面。我们跟进去再看下:代码太多,挑一些重点:

final View createView(){
  switch(var12) {
        case 0:
            view = this.createTextView(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 1:
            view = this.createImageView(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 2:
            view = this.createButton(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 3:
            view = this.createEditText(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 4:
            view = this.createSpinner(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 5:
            view = this.createImageButton(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 6:
            view = this.createCheckBox(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 7:
            view = this.createRadioButton(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 8:
            view = this.createCheckedTextView(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 9:
            view = this.createAutoCompleteTextView(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 10:
            view = this.createMultiAutoCompleteTextView(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 11:
            view = this.createRatingBar(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        case 12:
            view = this.createSeekBar(context, attrs);
            this.verifyNotNull((View)view, name);
            break;
        default:
            view = this.createView(context, name, attrs);
        }

        if (view == null && originalContext != context) {
            view = this.createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            this.checkOnClickListener((View)view, attrs);
        }

        return (View)view;

}
    @NonNull
    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }

可以发现,如果是TextView,就直接返回V7包中的AppCompatTextView类,所以这就是为什么当我们使用AppCompatActivity的时候,TextView变成AppCompatTextView的原因.

那么问题来了,难道说AppCompatTextView源码是默认可点击的。我们可以来看下。

public class AppCompatTextView extends TextView implements TintableBackgroundView, AutoSizeableTextView {
    private final AppCompatBackgroundHelper mBackgroundTintHelper;
    private final AppCompatTextHelper mTextHelper;
    @Nullable
    private Future<PrecomputedTextCompat> mPrecomputedTextFuture;

    public AppCompatTextView(Context context) {
        this(context, (AttributeSet)null);
    }

    public AppCompatTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 16842884);
    }

    public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
        this.mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
        this.mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
        this.mTextHelper = new AppCompatTextHelper(this);
        this.mTextHelper.loadFromAttributes(attrs, defStyleAttr);
        this.mTextHelper.applyCompoundDrawablesTints();
    }

    public void setBackgroundResource(@DrawableRes int resId) {
        super.setBackgroundResource(resId);
        if (this.mBackgroundTintHelper != null) {
            this.mBackgroundTintHelper.onSetBackgroundResource(resId);
        }

    }

    public void setBackgroundDrawable(Drawable background) {
        super.setBackgroundDrawable(background);
        if (this.mBackgroundTintHelper != null) {
            this.mBackgroundTintHelper.onSetBackgroundDrawable(background);
        }

    }
.....
}

 其实可以看到AppCompatTextView继承自TextView,代码里面并没有关于Clickable的设置。那肯定就是在默认的样式文件里了。

首先看我们的样式:

values/styles.xml  
   <style name="AppTheme" parent="Theme.AppCompat.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
这里面没有定义AutoCompleteTextView的样式。然后跟到父样式:
<style name="Theme.AppCompat.Light" parent="Base.Theme.AppCompat.Light"/>
也没有定义。再跟进去:
<style name="Base.Theme.AppCompat.Light" parent="Base.V7.Theme.AppCompat.Light">
 同上
<style name="Base.V7.Theme.AppCompat.Light" parent="Platform.AppCompat.Light">
同上
<style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light">
同上:
<style name="Theme.Holo.Light" parent="Theme.Light">
终于找到祖先了。。。。
<style name="Theme.Light">
这个里面定义了很多基本样式。我只挑了部分样式。
<style name="Theme.Light">
  <!-- Button styles -->
        <item name="buttonStyle">@style/Widget.Button</item>
         <item name="textViewStyle">@style/Widget.TextView</item>
        <item name="autoCompleteTextViewStyle">@style/Widget.AutoCompleteTextView</item>
</style>

我们可以看到autoCompleteTextViewStyle,这个其实就是AppCompatTextView的基本样式,点进去:

style路径:(android-sdk/platforms/android-28/data/res/values/styles.xml)

 <style name="Widget.Button">
       ...
        <item name="focusable">true</item>
        <item name="clickable">true</item>
        .....
    </style>

<style name="Widget.TextView">
        <item name="textAppearance">?attr/textAppearanceSmall</item>
        <item name="textSelectHandleLeft">?attr/textSelectHandleLeft</item>
        <item name="textSelectHandleRight">?attr/textSelectHandleRight</item>
        <item name="textSelectHandle">?attr/textSelectHandle</item>
        <item name="textEditPasteWindowLayout">?attr/textEditPasteWindowLayout</item>
        <item name="textEditNoPasteWindowLayout">?attr/textEditNoPasteWindowLayout</item>
。。。。。
    </style>

<style name="Widget.EditText">
      ....
        <item name="clickable">true</item>
      .....
    </style>
    
<style name="Widget.AutoCompleteTextView" parent="Widget.EditText">
        <item name="completionHintView">@layout/simple_dropdown_hint</item>
        <item name="completionThreshold">2</item>
        <item name="dropDownSelector">@drawable/list_selector_background</item>
        <item name="popupBackground">@drawable/spinner_dropdown_background</item>
        <item name="dropDownVerticalOffset">-6dip</item>
        <item name="dropDownHorizontalOffset">0dip</item>
        <item name="dropDownWidth">wrap_content</item>
    </style>

 省略了部分style,我们可以看到AutoCompleteTextView的基本样式,是继承自EditText.而EditText的clickable 默认为true。故

AutoCompleteTextView默认也是可以点击的。。。

所以在4.4上你的Activity继承AppCompatActivity,那你仍可以在xml定义点事件,不需要手动写上:android:clickable="true"

但是,你如果继承Activity或者FragmentActivity,那你需要再xml里面写上:android:clickable="true"

总结:4.4以下的,TextView最好写上android:clickable="true",或者你的Activity继承AppCompatActivity。

另外,在Fragment的xml里面如果是:android:onClick="testClick",则。testClick需要定义在Fragment所在的Activity里面。不能定义在Fragment,原因请自行百度(手动滑稽)....

每日语录:

我其实并不孤僻,甚至可以说开朗活泼,但大多时候我很懒,懒得经营一个关系。还有一些时候就是爱自由,觉得任何一种关系都会束缚自己,当然最主要的还是知音难寻,我老觉得自己跟大多数人交往总是只能拿出自己的一个维度,很难找到一个像我一样的人。
—— 刘瑜《送你一颗子弹》

单曲循环《笑中有泪》

猜你喜欢

转载自blog.csdn.net/androidzmm/article/details/89213961