有没有一种简单的方法可以让文本具有黑色轮廓?我有不同颜色的文本视图,但有些颜色并没有很好地显示在我的背景上,所以我想知道是否有一种简单的方法可以获得黑色轮廓或其他可以完成这项工作的方法?我宁愿不必创建自定义视图并制作画布等。
16 回答
在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"
所以,有点晚了,但MagicTextView会做文本大纲,除其他外。
<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" />
注意:我做了这个,为了未来的旅行者,我发布的内容比 OP 更多。这是边缘垃圾邮件,但在主题上,也许可以接受?
您可以在文本后面加上阴影,这通常有助于提高可读性。尝试在绿色文本上使用 50% 的半透明黑色阴影。有关如何执行此操作的详细信息,请参见此处:Android - 文本上的阴影?
要真正在文本周围添加笔触,您需要做一些更复杂的事情,例如: 如何在 Android 中的 MapView 上绘制带边框的文本?
这是一个相当古老的问题,但我仍然没有看到任何完整的答案。因此,我发布了此解决方案,希望遇到此问题的人可能会发现它很有用。最简单有效的解决方案是重写 TextView 类的 onDraw 方法。我见过的大多数实现都使用 drawText 方法来绘制笔画,但这种方法并没有考虑到所有格式对齐和文本换行。因此,笔画和文本通常会出现在不同的位置。以下方法使用 super.onDraw 来绘制文本的笔触和填充部分,因此您不必担心其余的东西。以下是步骤
- 扩展 TextView 类
- 覆盖 onDraw 方法
- 将绘制样式设置为 FILL
- 在 Draw 上调用父类以填充模式呈现文本。
- 保存当前文本颜色。
- 将当前文本颜色设置为您的笔触颜色
- 将绘画样式设置为描边
- 设置描边宽度
并再次调用父类 onDraw 以在先前渲染的文本上绘制笔画。
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); } }
就这些。此类使用自定义 XML 属性来启用从 XML 布局文件中指定笔触颜色和宽度。因此,您需要在文件夹“res”下的子文件夹“values”中的 attr.xml 文件中添加这些属性。将以下内容复制并粘贴到 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>
完成后,您可以在 XML 布局文件中使用自定义的 StrokedTextView 类,并指定笔触颜色和宽度。这是一个例子
<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" />
请记住将包名称替换为您项目的包名称。还要在布局文件中添加 xmlns 命名空间,以便使用自定义 XML 属性。您可以在布局文件的根节点中添加以下行。
xmlns:strokeAttrs="http://schemas.android.com/apk/res-auto"
该框架支持文本阴影,但不支持文本轮廓。但是有一个技巧:阴影是半透明的并且会褪色的东西。重绘阴影几次,所有的 alpha 都被总结出来,结果就是一个轮廓。
一个非常简单的实现扩展TextView
并覆盖了该draw(..)
方法。每次请求抽签时,我们的子类都会绘制 5-10 张抽签。
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" />
我一直在试图弄清楚如何做到这一点,在网上找不到好的指南,但最终想通了。正如 Steve Pomeroy 所建议的那样,您确实必须做一些更多的事情。为了获得轮廓文本效果,您绘制了两次文本:一次使用粗轮廓,然后第二次我们在轮廓上绘制主要文本。但是,任务变得更容易了,因为您可以非常轻松地调整随 SDK 提供的代码示例之一,即您的 SDK 目录中此名称下的代码示例:“/samples/android-/ApiDemos/src/com/example/android /apis/view/LabelView.java”。也可以在此处的 Android 开发者网站上找到。
根据您在做什么,很容易看出您只需要对该代码进行少量修改,例如将其更改为从 TextView 扩展等。在我发现这个示例之前,我忘记覆盖 onMeasure() (除了覆盖 Android 开发者网站上的“构建自定义组件”指南中提到的 onDraw() 之外,您还必须这样做),这也是我遇到问题的部分原因。
一旦你这样做了,你就可以做我所做的:
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);
}
因此,为了获得轮廓文本效果,您绘制了两次文本:一次使用粗轮廓,然后第二次我们在轮廓上绘制主要文本。
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);
}
}
属性定义
<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代码
<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" />
这是我发现比 MagicTextView 的 stroke IMO 效果更好的技巧
@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);
}
我编写了一个类来执行带有轮廓的文本,并且仍然支持所有其他属性和普通文本视图的绘图。
它基本上使用super.onDraw(Canves canvas)
on the ,TextView
但以不同的样式绘制了两次。
希望这可以帮助。
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);
}
}
属性.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>
您可以使用以下代码段以编程方式执行此操作。这提供了黑色背景的白色字母:
textView.setTextColor(Color.WHITE);
textView.setShadowLayer(1.6f,1.5f,1.3f,Color.BLACK);
该方法的参数是radius,dx,dy,color。您可以根据您的特定需求更改它们。
我希望我能帮助那些以编程方式创建 TextView 而不是在 xml 中的人。
为 stackOverflow 社区干杯!
我想添加一个解决方案以解决性能问题。例如,@YGHM 和其他一些人的答案可以完成这项工作,但它会导致无限调用onDraw
因为setTextColor
调用invalidate()
。因此,为了解决它,您还需要覆盖并添加一个您将设置为invalidate()
的变量,当正在进行时并用笔画进行绘制。如果变量是 .invalidate 将返回。isDrawing
true
onDraw()
true
override fun invalidate() {
if (isDrawing) return
super.invalidate()
}
您的 onDraw 将如下所示:
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)
}
}
我根据Nouman Hanif 的回答创建了一个库,并添加了一些内容。例如,修复导致 View.invalidate() 调用出现间接无限循环的错误。
OTOH,该库还支持 EditText 小部件中的轮廓文本,因为这是我的真正目标,它需要比 TextView 更多的工作。
这是我的图书馆的链接:https ://github.com/biomorgoth/android-outline-textview
感谢 Nouman Hanif 对解决方案的初步构想!
我找到了简单的方法来概述视图,而无需从TextView继承。我写了一个简单的库,它使用 Android 的Spannable来概述文本。该解决方案提供了仅勾勒部分文本的可能性。
我已经回答了同一个问题(答案)
班级:
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)
}
}
图书馆:大纲跨度
MagicTextView 对于制作笔触字体非常有用,但在我的情况下,它会导致 这样的错误,这个 错误是由 MagicTextView 设置的重复背景属性引起的
所以你需要编辑 attrs.xml 和 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));
}
}
所以你想在textview周围划一下吗?不幸的是,没有简单的方法来使用样式。您必须创建另一个视图并将您的文本视图放在顶部,使父视图(它位于顶部的视图)仅大几个像素 - 这应该创建一个轮廓。
这是我可以通过扩展 TextView 找到的最简单的方法
public class CustomTextView extends androidx.appcompat.widget.AppCompatTextView {
float mStroke;
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CustomTextView);
mStroke=a.getFloat(R.styleable.CustomTextView_stroke,1.0f);
a.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
TextPaint paint = this.getPaint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(mStroke);
super.onDraw(canvas);
}
}
那么你只需要在 attrs.xml 文件中添加以下内容
<declare-styleable name="CustomTextView">
<attr name="stroke" format="float"/>
</declare-styleable>
现在您将能够设置笔画宽度,app:stroke
同时保留 TextView 的所有其他所需属性。我的解决方案只绘制没有填充的笔划。这使它比其他的更简单。在为我的 customtextview 设置自定义字体时,在屏幕截图中显示结果。