有没有简单的方法让文本具有黑色轮廓? 我有一些文本视图将具有不同的颜色,但其中一些颜色在我的背景上并不是很清晰,所以我想知道是否有一种简单的方法来获取黑色轮廓或其他能够完成任务的东西? 我宁愿不用创建自定义视图并制作画布之类的东西。
有没有简单的方法让文本具有黑色轮廓? 我有一些文本视图将具有不同的颜色,但其中一些颜色在我的背景上并不是很清晰,所以我想知道是否有一种简单的方法来获取黑色轮廓或其他能够完成任务的东西? 我宁愿不用创建自定义视图并制作画布之类的东西。
可以使用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" />
注意:这是我做的,我发布更多内容是为了未来的旅行者而不是为了原始发帖人。它有点像垃圾邮件,但因为与话题相关,可能还算可以接受?
onDraw
递归调用,因为在onDraw
内部调用了setTextColor
。 - Sermilion您可以在文本后面加上阴影,这通常有助于提高可读性。尝试在绿色文本上使用50%半透明的黑色阴影进行实验。如何执行此操作的详细信息在此处:Android-文本阴影?
要真正在文本周围添加描边,您需要进行更复杂的操作,类似于这样:如何在Android的MapView上绘制带有边框的文本?
虽然这是一个老问题,但我仍然没有看到完整的答案。因此,我发布了这个解决方案,希望有人在处理这个问题时会觉得有用。最简单、最有效的解决方法是覆盖TextView类的onDraw方法。我看到的大多数实现都使用drawText方法来绘制描边,但这种方法并未考虑所有格式对齐和文本换行等问题。结果经常导致描边和文本出现在不同的位置。以下方法使用super.onDraw来绘制文本的填充和描边部分,因此您不必担心其他问题。 这里是步骤:
再次调用父类的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"
setTextColor
的内部机制中嵌入了invalidate()
调用,它会导致无限循环调用自身。除非你想复制每一行来自TextView
的代码到你自己的类中,否则我能看到的唯一解决方法是使用反射强制访问TextView
的私有字段mCurTextColor
。参见此答案 大致了解如何做到这一点。只需使用field.set(this, colorInt)
而不是使用field.get()
即可。 - VerumCH这个框架支持text-shadow,但不支持text-outline。但是有一个技巧:阴影是半透明的并且会褪色。多重绘制阴影几次,所有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" />
java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
的错误。 - Bevor我一直在尝试弄清楚如何做到这一点,但在网上找不到好的指南,最后终于想出了方法。正如Steve Pomeroy所建议的那样,你确实需要做更多的事情。为了获得描边文本效果,您需要绘制两次文本:一次使用粗描边绘制,然后第二次我们在描边上面绘制主要文本。但是,任务变得更容易,因为您可以非常容易地对SDK中提供的代码示例之一进行调整,即SDK目录下名为“/samples/android-/ApiDemos/src/com/example/android/apis/view/LabelView.java”的代码。该示例还可以在Android开发者网站此处找到。
根据您的需求,很容易看出您只需要对该代码进行轻微修改,例如将其更改为扩展自TextView等。在发现此示例之前,我忘记了覆盖onMeasure()方法(除了覆盖onDraw()方法外,在Android Developer网站的“构建自定义组件”指南中也提到),这也是我遇到问题的原因之一。
完成此操作后,您可以像我一样:
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" />
onDraw()
中调用super.setTextColor()
将导致视图无效,进而再次调用onDraw()
。这将导致无限循环。 - Sergey StasishintextView.setTextColor(Color.WHITE);
textView.setShadowLayer(1.6f,1.5f,1.3f,Color.BLACK);
该方法的参数包括半径、dx、dy和颜色,您可以根据自己的需求更改它们。
我希望能够帮助那些在程序中创建TextView而不是在xml中创建的人。
为StackOverflow社区欢呼!
onDraw
的无限调用,因为setTextColor
会调用invalidate()
。所以,为了解决这个问题,你还需要重写invalidate()
并添加一个变量isDrawing
,当正在进行带有描边的绘制时,将其设置为true
。如果变量isDrawing
为true
,则invalidate
将返回。override fun invalidate() {
if (isDrawing) return
super.invalidate()
}
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)
}
}
TextView
上的super.onDraw(Canvas canvas)
,但使用不同的样式进行两次绘制。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>