【问题标题】:Custom TextView - setText() called before constructor自定义 TextView - setText() 在构造函数之前调用
【发布时间】:2014-08-29 09:58:45
【问题描述】:

我的 CustomTextView 有问题。我试图从我的layout-xml 文件中获取一个自定义值,并在我的setText() 方法中使用它。不幸的是,setText() 方法在 constructor 之前被调用,因此我不能在此方法中使用自定义值。 p>

这是我的代码(分解为相关部分):

CustomTextView.class

public class CustomTextView extends TextView {

    private float mHeight;
    private final String TAG = "CustomTextView";
    private static final Spannable.Factory spannableFactory = Spannable.Factory.getInstance();

    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.d(TAG, "in CustomTextView constructor");
        TypedArray values = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView);
        this.mHeight = values.getDimension(R.styleable.CustomTextView_cHeight, 20);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        Log.d(TAG, "in setText function");
        Spannable s = getCustomSpannableString(getContext(), text);
        super.setText(s, BufferType.SPANNABLE);
    }

    private static Spannable getCustomSpannableString(Context context, CharSequence text) {
        Spannable spannable = spannableFactory.newSpannable(text);
        doSomeFancyStuff(context, spannable);
        return spannable;
    }

    private static void doSomeFancyStuff(Context context, Spannable spannable) {
        /*Here I'm trying to access the mHeight attribute.
        Unfortunately it's 0 though I set it to 24 in my layout 
        and it's correctly set in the constructor*/
    }
}

styles.xml

<declare-styleable name="CustomTextView">
    <attr name="cHeight" format="dimension"/>
</declare-styleable>

layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ctvi="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.mypackage.views.CustomTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/my_fancy_string"
        android:textSize="16sp"
        ctvi:cHeight="24dp" />

</LinearLayout>

作为证明 - 这是 LogCat 输出:

30912-30912/com.mypackage.views D/CustomTextView﹕ in setText function
30912-30912/com.mypackage.views D/CustomTextView﹕ in CustomTextView constructor

如您所见,setText() 方法在构造函数之前被调用。这有点奇怪,我不知道我需要更改什么才能在 setText 方法中使用我的自定义属性 (cHeight)。

提前感谢您的帮助!

【问题讨论】:

  • 感谢您提出这个问题,我只花了一个小时才弄清楚为什么我在构造函数中初始化的自定义属性在 setText 中为空。在第一行的构造函数中调用 super(..) 是愚蠢的 Java 限制,否则您将能够在实际调用 setText 之前初始化自定义属性

标签: android textview android-custom-view


【解决方案1】:

TextViewsuper() 构造函数根据属性值调用您的setText()

如果您在设置文本值时确实需要访问自定义属性,也可以为文本使用自定义属性。

【讨论】:

  • 有点意思。而不是在 setText() 方法中做我的事情,而是直接从构造函数中调用它?!创建 SpannableString 后是否应该再次调用 super.setText(...)
【解决方案2】:

恕我直言,我认为这些解决方案都不好。如果您只使用自定义方法,例如setCustomText(),而不是覆盖自定义TextView.setText(),该怎么办。我认为它在可扩展性方面可能会更好,并且破解/覆盖TextView 的实现可能会导致您遇到未来的问题。

干杯!

【讨论】:

    【解决方案3】:

    首先,请记住在使用后始终回收TypedArray

    TextView 在其构造期间调用#setText(CharSequence text, BufferType type) 因此定义对setText 的延迟调用:

    private Runnable mDelayedSetter;
    private boolean mConstructorCallDone;
    
    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.d(TAG, "in CustomTextView constructor");
        TypedArray values = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView);
        this.mHeight = values.getDimension(R.styleable.CustomTextView_cHeight, 20);
        mConstructorCallDone = true;
    }
    

    然后在你的setText-override 里面:

    public void setText(final CharSequence text, final TextView.BufferType type) {
        if (!mConstructorCallDone) {
            // The original call needs to be made at this point otherwise an exception will be thrown in BoringLayout if text contains \n or some other characters.
            super.setText(text, type);
            // Postponing setting text via XML until the constructor has finished calling
            mDelayedSetter = new Runnable() {
                @Override
                public void run() {
                    CustomTextView.this.setText(text, type);
                }
            };
            post(mDelayedSetter);
        } else {
            removeCallbacks(mDelayedSetter);
            Spannable s = getCustomSpannableString(getContext(), text);
            super.setText(s, BufferType.SPANNABLE);
        }
    }
    

    【讨论】:

      【解决方案4】:

      不幸的是,这是对 Java 的限制,需要在 constructor 中调用 super(..),然后再进行其他操作。因此,您唯一的解决方法是在初始化自定义属性后再次调用 setText(..)

      请记住,在初始化自定义属性之前,setText 也会调用它们,它们可能具有 null 值,您可以获得 NullPointerException

      查看我的customTextView 示例,它将首字母大写并在 and 处添加双点(我在所有活动中都使用它)

      package com.example.myapp_android_box_detector;
      
      import android.content.Context;
      import android.content.res.TypedArray;
      import android.support.v7.widget.AppCompatTextView;
      import android.util.AttributeSet;
      
      public class CapsTextView extends AppCompatTextView {
          public Boolean doubleDot;
          private Boolean inCustomText = false;
      
          public CapsTextView(Context context){
              super(context);
              doubleDot = false;
              setText(getText());
          }
      
          public CapsTextView(Context context, AttributeSet attrs){
              super(context, attrs);
              initAttrs(context, attrs);
              setText(getText());
          }
      
          public CapsTextView(Context context, AttributeSet attrs, int defStyle){
              super(context, attrs, defStyle);
              initAttrs(context, attrs);
              setText(getText());
          }
      
          public void initAttrs(Context context, AttributeSet attrs){
              TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CapsTextView, 0, 0);
              doubleDot = a.getBoolean(R.styleable.CapsTextView_doubleDot, false);
              a.recycle();
          }
      
          @Override
          public void setText(CharSequence text, BufferType type) {
              if (text.length() > 0){
                  text = String.valueOf(text.charAt(0)).toUpperCase() + text.subSequence(1, text.length());
                  // Adds double dot (:) to the end of the string
                  if (doubleDot != null && doubleDot){
                      text = text + ":";
                  }
              }
              super.setText(text, type);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2022-12-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-28
        • 1970-01-01
        • 2011-03-06
        • 1970-01-01
        相关资源
        最近更新 更多