Custom TextView - setText() called before constructor

First of all, remember to always recycle the TypedArray after using it.

TextView calls #setText(CharSequence text, BufferType type) during its construction therefore define a delayed call to setText as so:

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

Then inside your 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);
    }
}

It's the TextView super() constructor that calls your setText() based on the attribute values.

If you really need to access your custom attribute when setting a text value, use a custom attribute for the text as well.


Unfortunately it is a limitation on Java, that requires to call super(..) in constructor before anything else. So, your only workaround is to call setText(..) again after you initialize the custom attributes.

Just remember, as setText called also before you initialize your custom attributes, they may have null value and you can get NullPointerException

Check my example of customTextView which capitalize first letter and adds double dots at the and (I use it in all my activities)

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

I don't think any of this solutions are good, IMHO. What if you just use a custom method, like setCustomText() instead of overriding the custom TextView.setText(). I think it could be much better in terms of scalability, and hacking / overriding the implementation of the TextView could lead you into future problems.

Cheers!