自定义TextView - 在构造函数之前调用setText()

时间:2014-08-29 09:58:46

标签: android textview android-custom-view

我的 CustomTextView 出了问题。我正在尝试从layout-xml文件中获取自定义值,并在我的setText()方法中使用此值。不幸的是 setText()方法在构造函数之前调用,因此我不能在此方法中使用自定义值。

这是我的代码(细分到相关部分):

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)。

提前感谢您的帮助!

4 个答案:

答案 0 :(得分:6)

根据属性值调用TextView的{​​{1}} super()构造函数。

如果您在设置文本值时确实需要访问自定义属性,请同时使用文本的自定义属性。

答案 1 :(得分:2)

我不认为这些解决方案有任何好处,恕我直言。如果您只使用setCustomText()等自定义方法而不是覆盖自定义TextView.setText(),该怎么办?我认为在可扩展性方面可能会好得多,而且TextView的黑客/覆盖实施可能会引导您解决未来的问题。

干杯!

答案 2 :(得分:1)

首先,请记住在使用后始终回收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内 - 覆盖:

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

答案 3 :(得分:0)

很遗憾,这是对Java的限制,它要求先在构造函数中调用super(..)。因此,唯一的解决方法是在初始化自定义属性后再次调用setText(..)

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

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

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