Cómo dibujar correctamente texto en una clase extendida para TextView?

Actualmente estoy trabajando en extender un TextView, agregando un esquema alrededor del texto. Hasta ahora, el único problema que he estado teniendo es mi incapacidad para colocar el "contorno" correctamente detrás de un texto. Si codifico la clase extendida como la que se muestra a continuación, obtengo una etiqueta que se ve así:

Nota: en la captura de pantalla anterior, establezco el color de relleno en blanco y el color de trazo en negro.

¿Qué estoy haciendo mal?

public class OutlinedTextView extends TextView { /* =========================================================== * Constants * =========================================================== */ private static final float OUTLINE_PROPORTION = 0.1f; /* =========================================================== * Members * =========================================================== */ private final Paint mStrokePaint = new Paint(); private int mOutlineColor = Color.TRANSPARENT; /* =========================================================== * Constructors * =========================================================== */ public OutlinedTextView(Context context) { super(context); this.setupPaint(); } public OutlinedTextView(Context context, AttributeSet attrs) { super(context, attrs); this.setupPaint(); this.setupAttributes(context, attrs); } public OutlinedTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.setupPaint(); this.setupAttributes(context, attrs); } /* =========================================================== * Overrides * =========================================================== */ @Override protected void onDraw(Canvas canvas) { // Get the text to print final float textSize = super.getTextSize(); final String text = super.getText().toString(); // setup stroke mStrokePaint.setColor(mOutlineColor); mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION); mStrokePaint.setTextSize(textSize); mStrokePaint.setFlags(super.getPaintFlags()); mStrokePaint.setTypeface(super.getTypeface()); // Figure out the drawing coordinates //mStrokePaint.getTextBounds(text, 0, text.length(), mTextBounds); // draw everything canvas.drawText(text, super.getWidth() * 0.5f, super.getBottom() * 0.5f, mStrokePaint); super.onDraw(canvas); } /* =========================================================== * Private/Protected Methods * =========================================================== */ private final void setupPaint() { mStrokePaint.setAntiAlias(true); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setTextAlign(Paint.Align.CENTER); } private final void setupAttributes(Context context, AttributeSet attrs) { final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OutlinedTextView); mOutlineColor = array.getColor( R.styleable.OutlinedTextView_outlineColor, 0x00000000); array.recycle(); // Force this text label to be centered super.setGravity(Gravity.CENTER_HORIZONTAL); } } 

Bah, eso fue estúpido de mí. Sólo necesitaba cambiar esa línea comentada:

 super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds); 

Además, para realmente renderizar el texto, necesito hacer un promedio de la altura de esta vista y la altura del texto:

 // draw everything canvas.drawText(text, super.getWidth() * 0.5f, (super.getHeight() + mTextBounds.height()) * 0.5f, mStrokePaint); 

El código completo ahora se lee como sigue:

 public class OutlinedTextView extends TextView { /* =========================================================== * Constants * =========================================================== */ private static final float OUTLINE_PROPORTION = 0.1f; /* =========================================================== * Members * =========================================================== */ private final Paint mStrokePaint = new Paint(); private final Rect mTextBounds = new Rect(); private int mOutlineColor = Color.TRANSPARENT; /* =========================================================== * Constructors * =========================================================== */ public OutlinedTextView(Context context) { super(context); this.setupPaint(); } public OutlinedTextView(Context context, AttributeSet attrs) { super(context, attrs); this.setupPaint(); this.setupAttributes(context, attrs); } public OutlinedTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.setupPaint(); this.setupAttributes(context, attrs); } /* =========================================================== * Overrides * =========================================================== */ @Override protected void onDraw(Canvas canvas) { // Get the text to print final float textSize = super.getTextSize(); final String text = super.getText().toString(); // setup stroke mStrokePaint.setColor(mOutlineColor); mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION); mStrokePaint.setTextSize(textSize); mStrokePaint.setFlags(super.getPaintFlags()); mStrokePaint.setTypeface(super.getTypeface()); // Figure out the drawing coordinates super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds); // draw everything canvas.drawText(text, super.getWidth() * 0.5f, (super.getHeight() + mTextBounds.height()) * 0.5f, mStrokePaint); super.onDraw(canvas); } /* =========================================================== * Private/Protected Methods * =========================================================== */ private final void setupPaint() { mStrokePaint.setAntiAlias(true); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setTextAlign(Paint.Align.CENTER); } private final void setupAttributes(Context context, AttributeSet attrs) { final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OutlinedTextView); mOutlineColor = array.getColor( R.styleable.OutlinedTextView_outlineColor, 0x00000000); array.recycle(); // Force this text label to be centered super.setGravity(Gravity.CENTER_HORIZONTAL); } } 

He estado conectando con algunos de estos ejemplos por un tiempo, ya que ninguno parecía alinearse muy bien, y una vez que finalmente conseguí un mango en lo que está sucediendo con el texto y poner mi sombrero de matemáticas en Cambié mi onDraw para esto Ser el siguiente y se sienta perfectamente no importa el tamaño del texto o lo que el tamaño y la forma que está conteniendo la vista es …

 @Override protected void onDraw(Canvas canvas) { if (!isInEditMode()){ // Get the text to print final float textSize = super.getTextSize(); final String text = super.getText().toString(); // setup stroke mStrokePaint.setColor(mOutlineColor); mStrokePaint.setStrokeWidth(textSize * mOutlineSize); mStrokePaint.setTextSize(textSize); mStrokePaint.setFlags(super.getPaintFlags()); mStrokePaint.setTypeface(super.getTypeface()); // draw everything canvas.drawText(text, (this.getWidth()-mStrokePaint.measureText(text))/2, this.getBaseline(), mStrokePaint); } super.onDraw(canvas); } 

Resultó ser mucho menos matemáticas y calcular Rect que un montón de soluciones utilizadas también.

Edit: Olvidé mencionar que copio el textalign del super en la inicialización y NO lo fuerce al centro. La posición drawText calculada aquí siempre va a ser la posición correctamente centrada para el texto acariciado.

He estado tratando de hacer que funcione durante algún tiempo y tengo una solución, pero es sólo para un caso especial! Es posible obtener el objeto de Layout que se utiliza dentro de TextView para el texto de dibujo. Puede crear una copia de este objeto y utilizarlo dentro del onDraw(Canvas) .

  final Layout originalLayout = super.getLayout(); final Layout layout = new StaticLayout(text, mStrokePaint, originalLayout.getWidth(), originalLayout.getAlignment(), originalLayout.getSpacingMultiplier(), originalLayout.getSpacingAdd(), true); canvas.save(); canvas.translate( layout.getLineWidth(0) * 0.5f, 0.0f ); layout.draw(canvas); canvas.restore(); 

Pero estoy seguro de que no es una buena manera de dibujar contornos. No sé cómo realizar el seguimiento de los cambios en un objeto TextView.getLayout() . También no funciona para TextView multilínea y diferentes gravedades. Y, finalmente, este código tiene un rendimiento muy pobre porque asigna un objeto de Layout en cada dibujo. No entiendo exactamente cómo funciona, así que prefiero no usarlo.

Hay algunos atributos en la clase TextView , como android:shadowColor , android:shadowDx , android:shadowDy y android:shadowRadius . Me parece que hacen lo mismo que quieren implementar. Así que quizás deberías probar un TextView simple al principio.

  • La vista de texto de Android no se enrolla correctamente
  • Android: selector de texto personalizado
  • Cómo centrar un texto de dos líneas en un TextView en Android?
  • Espaciado de párrafos con SpannableStringBuilder en TextView
  • Cómo obtener las coordenadas XY y el tamaño de píxeles de un TextView?
  • ¿Cómo establecer un hipervínculo en textview clicable? Android Java
  • ¿Cuál es el color predeterminado para el texto en textview?
  • Desplazamiento por programación de una palabra en vista en una vista de texto
  • EditText vs TextView
  • ¿Cómo crear sugerencias en el cuadro de edición textview que desaparecen cuando textview tiene foco?
  • Establecer texto en TextView utilizando Html.fromHtml
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.