¿Existe una manera sencilla de que el texto pueda tener un contorno negro? Tengo vistas de texto que serán de diferentes colores, pero algunos de los colores no se ven tan bien en mi fondo, así que me preguntaba si hay una manera fácil de obtener un contorno negro o algo más que funcione. Preferiría no tener que crear una vista personalizada y hacer un lienzo y demás.
82
Respuestas:
Puede poner una sombra detrás del texto, lo que a menudo puede mejorar la legibilidad. Intente experimentar con sombras negras translúcidas al 50% en su texto verde. Los detalles sobre cómo hacer esto están aquí: Android - ¿sombra en el texto?
Para agregar realmente un trazo alrededor del texto, debe hacer algo un poco más complicado, como esto: ¿Cómo se dibuja texto con un borde en un MapView en Android?
fuente
El efecto de contorno se puede lograr usando la sombra en TextView:
android:shadowColor="#000000" android:shadowDx="1.5" android:shadowDy="1.3" android:shadowRadius="1.6" android:text="CCC" android:textAllCaps="true" android:textColor="@android:color/white"
fuente
Entonces, un poco tarde, pero MagicTextView hará esquemas de texto, entre otras cosas.
<com.qwerjk.better_text.MagicTextView xmlns:qwerjk="http://schemas.android.com/apk/res/com.qwerjk.better_text" android:textSize="78dp" android:textColor="#ff333333" android:layout_width="fill_parent" android:layout_height="wrap_content" qwerjk:strokeColor="#FFff0000" qwerjk:strokeJoinStyle="miter" qwerjk:strokeWidth="5" android:text="Magic" />
Nota: Hice esto y estoy publicando más por el bien de los futuros viajeros que por el OP. Está en el límite del spam, pero si es sobre el tema, ¿quizás sea aceptable?
fuente
onDraw
que se llame de forma recursiva debido a la llamadasetTextColor
dentro deonDraw
.El marco admite text-shadow pero no admite text-outline. Pero hay un truco: la sombra es algo que es traslúcido y se desvanece. Vuelve a dibujar la sombra un par de veces y todo el alfa se resume y el resultado es un contorno.
Una implementación muy simple extiende
TextView
y anula eldraw(..)
método. Cada vez que se solicita un sorteo, nuestra subclase realiza entre 5 y 10 dibujos.public class OutlineTextView extends TextView { // Constructors @Override public void draw(Canvas canvas) { for (int i = 0; i < 5; i++) { super.draw(canvas); } } } <OutlineTextView android:shadowColor="#000" android:shadowRadius="3.0" />
fuente
java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet
Es una pregunta bastante antigua, pero todavía no veo ninguna respuesta completa. Así que estoy publicando esta solución, con la esperanza de que alguien que esté luchando con este problema la encuentre útil. La solución más simple y efectiva es anular el método onDraw de la clase TextView. La mayoría de las implementaciones que he visto usan el método drawText para dibujar el trazo, pero ese enfoque no tiene en cuenta toda la alineación del formato y el ajuste del texto que entra. Y como resultado, a menudo el trazo y el texto terminan en lugares diferentes. El siguiente enfoque usa super.onDraw para dibujar el trazo y rellenar partes del texto para que no tenga que preocuparse por el resto del material. Aquí están los pasos
Y vuelva a llamar a la clase principal onDraw para dibujar el trazo sobre el texto renderizado anteriormente.
package com.example.widgets; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Typeface; import android.util.AttributeSet; import android.widget.Button; public class StrokedTextView extends Button { private static final int DEFAULT_STROKE_WIDTH = 0; // fields private int _strokeColor; private float _strokeWidth; // constructors public StrokedTextView(Context context) { this(context, null, 0); } public StrokedTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StrokedTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if(attrs != null) { TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.StrokedTextAttrs); _strokeColor = a.getColor(R.styleable.StrokedTextAttrs_textStrokeColor, getCurrentTextColor()); _strokeWidth = a.getFloat(R.styleable.StrokedTextAttrs_textStrokeWidth, DEFAULT_STROKE_WIDTH); a.recycle(); } else { _strokeColor = getCurrentTextColor(); _strokeWidth = DEFAULT_STROKE_WIDTH; } //convert values specified in dp in XML layout to //px, otherwise stroke width would appear different //on different screens _strokeWidth = dpToPx(context, _strokeWidth); } // getters + setters public void setStrokeColor(int color) { _strokeColor = color; } public void setStrokeWidth(int width) { _strokeWidth = width; } // overridden methods @Override protected void onDraw(Canvas canvas) { if(_strokeWidth > 0) { //set paint to fill mode Paint p = getPaint(); p.setStyle(Paint.Style.FILL); //draw the fill part of text super.onDraw(canvas); //save the text color int currentTextColor = getCurrentTextColor(); //set paint to stroke mode and specify //stroke color and width p.setStyle(Paint.Style.STROKE); p.setStrokeWidth(_strokeWidth); setTextColor(_strokeColor); //draw text stroke super.onDraw(canvas); //revert the color back to the one //initially specified setTextColor(currentTextColor); } else { super.onDraw(canvas); } } /** * Convenience method to convert density independent pixel(dp) value * into device display specific pixel value. * @param context Context to access device specific display metrics * @param dp density independent pixel value * @return device specific pixel value. */ public static int dpToPx(Context context, float dp) { final float scale= context.getResources().getDisplayMetrics().density; return (int) (dp * scale + 0.5f); } }
Eso es todo. Esta clase utiliza atributos XML personalizados para permitir especificar el color y el ancho del trazo de los archivos de diseño XML. Por lo tanto, debe agregar estos atributos en su archivo attr.xml en la subcarpeta 'valores' en la carpeta 'res'. Copie y pegue lo siguiente en su archivo attr.xml.
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="StrokedTextAttrs"> <attr name="textStrokeColor" format="color"/> <attr name="textStrokeWidth" format="float"/> </declare-styleable> </resources>
Una vez que haya terminado con eso, puede usar la clase StrokedTextView personalizada en sus archivos de diseño XML y especificar el color y el ancho del trazo también. Aquí hay un ejemplo
<com.example.widgets.StrokedTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Stroked text sample" android:textColor="@android:color/white" android:textSize="25sp" strokeAttrs:textStrokeColor="@android:color/black" strokeAttrs:textStrokeWidth="1.7" />
Recuerde reemplazar el nombre del paquete con el nombre del paquete de su proyecto. También agregue el espacio de nombres xmlns en el archivo de diseño para usar atributos XML personalizados. Puede agregar la siguiente línea en el nodo raíz de su archivo de diseño.
xmlns:strokeAttrs="http://schemas.android.com/apk/res-auto"
fuente
invalidate()
llamada enterrada en el funcionamiento interno desetTextColor
. A menos que desee copiar hasta la última línea de código deTextView
su propia clase, la única forma de evitar esto que puedo ver es acceder a la fuerza bruta al campo privadomCurTextColor
delTextView
uso de Reflection. Vea esta respuesta para ver aproximadamente cómo hacerlo. Solo use enfield.set(this, colorInt)
lugar de usarfield.get()
.He estado tratando de averiguar cómo hacer esto y no pude encontrar una buena guía en línea, pero finalmente lo descubrí. Como sugirió Steve Pomeroy, tienes que hacer algo más complicado. Para obtener el efecto de texto delineado, dibuja el texto dos veces: una vez con un contorno grueso y luego la segunda vez dibujamos el texto principal sobre el contorno. Pero, la tarea se hace más fácil porque puede adaptar muy fácilmente una de las muestras de código proporcionadas con el SDK, a saber, la que tiene este nombre en su directorio SDK: "/ samples / android- / ApiDemos / src / com / example / android /apis/view/LabelView.java ". Que también se puede encontrar en el sitio web para desarrolladores de Android aquí .
Dependiendo de lo que esté haciendo, es muy fácil ver que solo necesitará hacer modificaciones menores a ese código, como cambiarlo para extenderlo desde TextView, etc. Antes de descubrir esta muestra, olvidé anular onMeasure () (que debe hacer además de anular onDraw () como se menciona en la guía "Creación de componentes personalizados" en el sitio web para desarrolladores de Android), que es parte de la razón por la que estaba teniendo problemas.
Una vez que hayas hecho eso, puedes hacer lo que hice yo:
public class TextViewOutline extends TextView { private Paint mTextPaint; private Paint mTextPaintOutline; //add another paint attribute for your outline ... //modify initTextViewOutline to setup the outline style private void initTextViewOutline() { mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); mTextPaint.setTextSize(16); mTextPaint.setColor(0xFF000000); mTextPaint.setStyle(Paint.Style.FILL); mTextPaintOutline = new Paint(); mTextPaintOutline.setAntiAlias(true); mTextPaintOutline.setTextSize(16); mTextPaintOutline.setColor(0xFF000000); mTextPaintOutline.setStyle(Paint.Style.STROKE); mTextPaintOutline.setStrokeWidth(4); setPadding(3, 3, 3, 3); } ... //make sure to update other methods you've overridden to handle your new paint object ... //and finally draw the text, mAscent refers to a member attribute which had //a value assigned to it in the measureHeight and Width methods @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent, mTextPaintOutline); canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent, mTextPaint); }
Entonces, para obtener el efecto de texto delineado, dibuja el texto dos veces: una vez con un contorno grueso y luego la segunda vez dibujamos el texto principal sobre el contorno.
fuente
Aquí está el truco que encontré que funciona mejor que el trazo de MagicTextView en mi opinión
@Override protected void onDraw(Canvas pCanvas) { int textColor = getTextColors().getDefaultColor(); setTextColor(mOutlineColor); // your stroke's color getPaint().setStrokeWidth(10); getPaint().setStyle(Paint.Style.STROKE); super.onDraw(pCanvas); setTextColor(textColor); getPaint().setStrokeWidth(0); getPaint().setStyle(Paint.Style.FILL); super.onDraw(pCanvas); }
fuente
TextView
en su propia clase es usar Reflection para acceder directamente al campo privadomCurTextColor
enTextView
. Esta respuesta proporciona una guía general sobre cómo hacer esto. Si desea que la sugerencia y el texto del enlace también tengan un trazo, también tendrá que cambiarmHintTextColor
ymLinkTextColor
. Desafortunadamente, cambiarmTextColor
no hace nada, ya que solo se hace referencia a él.Escribí una clase para realizar texto con contorno y aún admitir todos los demás atributos y dibujos de una vista de texto normal.
Básicamente usa el
super.onDraw(Canves canvas)
on theTextView
pero dibuja dos veces con diferentes estilos.espero que esto ayude.
public class TextViewOutline extends TextView { // constants private static final int DEFAULT_OUTLINE_SIZE = 0; private static final int DEFAULT_OUTLINE_COLOR = Color.TRANSPARENT; // data private int mOutlineSize; private int mOutlineColor; private int mTextColor; private float mShadowRadius; private float mShadowDx; private float mShadowDy; private int mShadowColor; public TextViewOutline(Context context) { this(context, null); } public TextViewOutline(Context context, AttributeSet attrs) { super(context, attrs); setAttributes(attrs); } private void setAttributes(AttributeSet attrs){ // set defaults mOutlineSize = DEFAULT_OUTLINE_SIZE; mOutlineColor = DEFAULT_OUTLINE_COLOR; // text color mTextColor = getCurrentTextColor(); if(attrs != null) { TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.TextViewOutline); // outline size if (a.hasValue(R.styleable.TextViewOutline_outlineSize)) { mOutlineSize = (int) a.getDimension(R.styleable.TextViewOutline_outlineSize, DEFAULT_OUTLINE_SIZE); } // outline color if (a.hasValue(R.styleable.TextViewOutline_outlineColor)) { mOutlineColor = a.getColor(R.styleable.TextViewOutline_outlineColor, DEFAULT_OUTLINE_COLOR); } // shadow (the reason we take shadow from attributes is because we use API level 15 and only from 16 we have the get methods for the shadow attributes) if (a.hasValue(R.styleable.TextViewOutline_android_shadowRadius) || a.hasValue(R.styleable.TextViewOutline_android_shadowDx) || a.hasValue(R.styleable.TextViewOutline_android_shadowDy) || a.hasValue(R.styleable.TextViewOutline_android_shadowColor)) { mShadowRadius = a.getFloat(R.styleable.TextViewOutline_android_shadowRadius, 0); mShadowDx = a.getFloat(R.styleable.TextViewOutline_android_shadowDx, 0); mShadowDy = a.getFloat(R.styleable.TextViewOutline_android_shadowDy, 0); mShadowColor = a.getColor(R.styleable.TextViewOutline_android_shadowColor, Color.TRANSPARENT); } a.recycle(); } PFLog.d("mOutlineSize = " + mOutlineSize); PFLog.d("mOutlineColor = " + mOutlineColor); } private void setPaintToOutline(){ Paint paint = getPaint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(mOutlineSize); super.setTextColor(mOutlineColor); super.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor); } private void setPaintToRegular() { Paint paint = getPaint(); paint.setStyle(Paint.Style.FILL); paint.setStrokeWidth(0); super.setTextColor(mTextColor); super.setShadowLayer(0, 0, 0, Color.TRANSPARENT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setPaintToOutline(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override public void setTextColor(int color) { super.setTextColor(color); mTextColor = color; } @Override public void setShadowLayer(float radius, float dx, float dy, int color) { super.setShadowLayer(radius, dx, dy, color); mShadowRadius = radius; mShadowDx = dx; mShadowDy = dy; mShadowColor = color; } public void setOutlineSize(int size){ mOutlineSize = size; } public void setOutlineColor(int color){ mOutlineColor = color; } @Override protected void onDraw(Canvas canvas) { setPaintToOutline(); super.onDraw(canvas); setPaintToRegular(); super.onDraw(canvas); } }
attr.xml
<declare-styleable name="TextViewOutline"> <attr name="outlineSize" format="dimension"/> <attr name="outlineColor" format="color|reference"/> <attr name="android:shadowRadius"/> <attr name="android:shadowDx"/> <attr name="android:shadowDy"/> <attr name="android:shadowColor"/> </declare-styleable>
fuente
crédito a @YGHM agregar soporte en la sombra
package com.megvii.demo; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; public class TextViewOutline extends android.support.v7.widget.AppCompatTextView { // constants private static final int DEFAULT_OUTLINE_SIZE = 0; private static final int DEFAULT_OUTLINE_COLOR = Color.TRANSPARENT; // data private int mOutlineSize; private int mOutlineColor; private int mTextColor; private float mShadowRadius; private float mShadowDx; private float mShadowDy; private int mShadowColor; public TextViewOutline(Context context) { this(context, null); } public TextViewOutline(Context context, AttributeSet attrs) { super(context, attrs); setAttributes(attrs); } private void setAttributes(AttributeSet attrs) { // set defaults mOutlineSize = DEFAULT_OUTLINE_SIZE; mOutlineColor = DEFAULT_OUTLINE_COLOR; // text color mTextColor = getCurrentTextColor(); if (attrs != null) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TextViewOutline); // outline size if (a.hasValue(R.styleable.TextViewOutline_outlineSize)) { mOutlineSize = (int) a.getDimension(R.styleable.TextViewOutline_outlineSize, DEFAULT_OUTLINE_SIZE); } // outline color if (a.hasValue(R.styleable.TextViewOutline_outlineColor)) { mOutlineColor = a.getColor(R.styleable.TextViewOutline_outlineColor, DEFAULT_OUTLINE_COLOR); } // shadow (the reason we take shadow from attributes is because we use API level 15 and only from 16 we have the get methods for the shadow attributes) if (a.hasValue(R.styleable.TextViewOutline_android_shadowRadius) || a.hasValue(R.styleable.TextViewOutline_android_shadowDx) || a.hasValue(R.styleable.TextViewOutline_android_shadowDy) || a.hasValue(R.styleable.TextViewOutline_android_shadowColor)) { mShadowRadius = a.getFloat(R.styleable.TextViewOutline_android_shadowRadius, 0); mShadowDx = a.getFloat(R.styleable.TextViewOutline_android_shadowDx, 0); mShadowDy = a.getFloat(R.styleable.TextViewOutline_android_shadowDy, 0); mShadowColor = a.getColor(R.styleable.TextViewOutline_android_shadowColor, Color.TRANSPARENT); } a.recycle(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setPaintToOutline(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private void setPaintToOutline() { Paint paint = getPaint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(mOutlineSize); super.setTextColor(mOutlineColor); super.setShadowLayer(0, 0, 0, Color.TRANSPARENT); } private void setPaintToRegular() { Paint paint = getPaint(); paint.setStyle(Paint.Style.FILL); paint.setStrokeWidth(0); super.setTextColor(mTextColor); super.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor); } @Override public void setTextColor(int color) { super.setTextColor(color); mTextColor = color; } public void setOutlineSize(int size) { mOutlineSize = size; } public void setOutlineColor(int color) { mOutlineColor = color; } @Override protected void onDraw(Canvas canvas) { setPaintToOutline(); super.onDraw(canvas); setPaintToRegular(); super.onDraw(canvas); } }
attr definir
<declare-styleable name="TextViewOutline"> <attr name="outlineSize" format="dimension"/> <attr name="outlineColor" format="color|reference"/> <attr name="android:shadowRadius"/> <attr name="android:shadowDx"/> <attr name="android:shadowDy"/> <attr name="android:shadowColor"/> </declare-styleable>
xml código a continuación
<com.megvii.demo.TextViewOutline android:id="@+id/product_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="110dp" android:background="#f4b222" android:fontFamily="@font/kidsmagazine" android:padding="10dp" android:shadowColor="#d7713200" android:shadowDx="0" android:shadowDy="8" android:shadowRadius="1" android:text="LIPSTICK SET" android:textColor="@android:color/white" android:textSize="30sp" app:outlineColor="#cb7800" app:outlineSize="3dp" />
fuente
Puede hacer esto mediante programación con el siguiente fragmento. Eso proporciona letras blancas con fondo negro:
textView.setTextColor(Color.WHITE); textView.setShadowLayer(1.6f,1.5f,1.3f,Color.BLACK);
Los parámetros del método son radio, dx, dy, color. Puede cambiarlos según sus necesidades específicas.
Espero ayudar a alguien que crea TextView mediante programación y no lo tenga dentro de xml.
¡Salud a la comunidad de stackOverflow!
fuente
He creado una biblioteca basada en la respuesta de Nouman Hanif con algunas adiciones. Por ejemplo, corregir un error que provocó un bucle infinito indirecto en las llamadas a View.invalidate ().
OTOH, la biblioteca también admite texto delineado en los widgets de EditText, ya que era mi objetivo real y necesitaba un poco más de trabajo que TextView.
Aquí está el enlace a mi biblioteca: https://github.com/biomorgoth/android-outline-textview
¡Gracias a Nouman Hanif por la idea inicial de la solución!
fuente
Quiero agregar una solución para resolver el problema de rendimiento. Por ejemplo, la respuesta de @YGHM y algunos otros hace el trabajo, pero causa infinita de llamada
onDraw
, porquesetTextColor
las llamadasinvalidate()
. Entonces, para resolverlo, también debe anularinvalidate()
y agregar una variableisDrawing
que establecerátrue
, cuandoonDraw()
esté en progreso y dibujando con un trazo. invalidar devolverá si la variable estrue
.override fun invalidate() { if (isDrawing) return super.invalidate() }
Tu onDraw se verá así:
override fun onDraw(canvas: Canvas) { if (strokeWidth > 0) { isDrawing = true val textColor = textColors.defaultColor setTextColor(strokeColor) paint.strokeWidth = strokeWidth paint.style = Paint.Style.STROKE super.onDraw(canvas) setTextColor(textColor) paint.strokeWidth = 0f paint.style = Paint.Style.FILL isDrawing = false super.onDraw(canvas) } else { super.onDraw(canvas) } }
fuente
MagicTextView es muy útil para hacer una fuente de trazo, pero en mi caso, causa un error como este, este error causado por la duplicación de atributos de fondo que establece MagicTextView
por lo que necesita editar attrs.xml y MagicTextView.java
attrs.xml
<attr name="background" format="reference|color" /> ↓ <attr name="mBackground" format="reference|color" />
MagicTextView.java 88:95
if (a.hasValue(R.styleable.MagicTextView_mBackground)) { Drawable background = a.getDrawable(R.styleable.MagicTextView_mBackground); if (background != null) { this.setBackgroundDrawable(background); } else { this.setBackgroundColor(a.getColor(R.styleable.MagicTextView_mBackground, 0xff000000)); } }
fuente
Encontré una forma sencilla de esquematizar la vista sin la herencia de TextView . Escribí una biblioteca simple que usa Spannable de Android para delinear texto. Esta solución brinda la posibilidad de delinear solo una parte del texto.
Ya había respondido a la misma pregunta ( respuesta )
Clase:
class OutlineSpan( @ColorInt private val strokeColor: Int, @Dimension private val strokeWidth: Float ): ReplacementSpan() { override fun getSize( paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt? ): Int { return paint.measureText(text.toString().substring(start until end)).toInt() } override fun draw( canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint ) { val originTextColor = paint.color paint.apply { color = strokeColor style = Paint.Style.STROKE this.strokeWidth = this@OutlineSpan.strokeWidth } canvas.drawText(text, start, end, x, y.toFloat(), paint) paint.apply { color = originTextColor style = Paint.Style.FILL } canvas.drawText(text, start, end, x, y.toFloat(), paint) } }
Biblioteca: OutlineSpan
fuente
¿Quieres un trazo alrededor de la vista de texto? Desafortunadamente, no hay una forma sencilla de hacerlo con el estilo. Tendrá que crear otra vista y colocar su vista de texto en la parte superior, haciendo que la vista principal (la que está encima) sea solo unos pocos píxeles más grande; esto debería crear un contorno.
fuente