Android textview大纲文本

时间:2010-07-05 22:03:38

标签: android colors textview

是否有一种简单的方法让文字能够有黑色轮廓?我的文字视图会有不同的颜色,但是有些颜色不能很好地显示在我的背景上,所以我想知道是否有一种简单的方法来获得黑色轮廓或其他可以完成工作的东西?我不想创建自定义视图并制作画布等。

16 个答案:

答案 0 :(得分:52)

您可以在文本后面添加阴影,这通常有助于提高可读性。尝试在绿色文本上尝试50%半透明黑色阴影。有关如何执行此操作的详细信息,请访问:Android - shadow on text?

要真正在文本周围添加笔划,您需要做一些更多涉及的事情,如下所示: How do you draw text with a border on a MapView in Android?

答案 1 :(得分:51)

使用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"

答案 2 :(得分:50)

所以,很晚,但MagicTextView会做文字大纲,等等。

enter image description here

<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更多。  它是临界垃圾邮件,但是在主题上,也许可以接受?

答案 3 :(得分:21)

框架支持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" />

答案 4 :(得分:17)

这是一个相当古老的问题,但我仍然没有看到任何完整的答案。所以我发布这个解决方案,希望有人在努力解决这个问题可能会觉得它很有用。最简单和最有效的解决方案是覆盖TextView类的onDraw方法。我见过的大多数实现都使用drawText方法来绘制笔划,但是这种方法并没有考虑到所有格式对齐和文本换行。结果通常笔划和文本最终会出现在不同的位置。以下方法使用super.onDraw来绘制文本的笔划和填充部分,这样您就不必费心了。以下是步骤

  1. 扩展TextView类
  2. 覆盖onDraw方法
  3. 将绘画样式设置为FILL
  4. 在Draw上调用父类以在填充中呈现文本 模式。
  5. 保存当前文字颜色。
  6. 将当前文字颜色设置为笔触颜色
  7. 将绘画样式设置为“笔触”
  8. 设置笔触宽度
  9. 再次调用父类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);
       }            
    }
    
  10. 就是这样。此类使用自定义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"
    

答案 5 :(得分:15)

我一直在试图弄清楚如何做到这一点,并且无法在网上找到一个好的指南,但最终想出来了。正如Steve Pomeroy建议的那样,你必须做更多的事情。为了获得轮廓文本效果,您可以将文本绘制两次:一次使用粗轮廓,然后第二次在轮廓上绘制主文本。但是,任务变得更容易,因为您可以非常轻松地调整SDK提供的代码示例之一,即SDK目录中此名称下的代码示例:“/ samples / android- / ApiDemos / src / com / example / android /apis/view/LabelView.java”。这也可以在Android开发者网站here上找到。

根据您正在做的事情,很容易看到您只需对该代码进行少量修改,例如将其更改为从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);
   }

因此,为了获得轮廓文本效果,您可以绘制两次文本:一次使用粗轮廓,然后第二次在轮廓上绘制主文本。

答案 6 :(得分:7)

我发现这个技巧比MagicTextView的中风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);
}

答案 7 :(得分:7)

我写了一个类来执行带有大纲的文本,并且仍然支持所有其他属性和普通文本视图的绘制。

它基本上使用了super.onDraw(Canves canvas)上的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);
    }

}

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>

答案 8 :(得分:3)

您可以使用以下代码段以编程方式执行此操作。 这提供了黑色背景的白色字母:

textView.setTextColor(Color.WHITE);            
textView.setShadowLayer(1.6f,1.5f,1.3f,Color.BLACK);

方法的参数是radius,dx,dy,color。您可以根据具体需要更改它们。

我希望我会帮助那些以编程方式创建TextView但没有在xml中创建TextView的人。

欢迎来到stackOverflow社区!

答案 9 :(得分:1)

所以你想在textview周围进行一下吗?不幸的是,没有简单的方法来使用样式。你必须创建另一个视图并将你的textview置于顶部,使得父视图(它位于其上方的那个)只有几个像素 - 这应该创建一个轮廓。

答案 10 :(得分:1)

MagicTextView is very useful to make stroke font, but in my case, it cause error like this this error caused by duplication background attributes which set by MagicTextView

so you need to edit attrs.xml and 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));
}
}

答案 11 :(得分:1)

我创建了一个基于Nouman Hanif's answer的库,但增加了一些内容。例如,修复导致View.invalidate()调用的间接无限循环的错误。

OTOH,该库还支持EditText小部件中的轮廓文本,因为这是我的真正目标,它需要比TextView更多的工作。

以下是我图书馆的链接:https://github.com/biomorgoth/android-outline-textview

感谢Nouman Hanif对解决方案的初步想法!

答案 12 :(得分:1)

我发现了一种简单的方法来概述视图而无需继承 TextView 。 我编写了一个简单的库,该库使用Android的 Spannable 概述文本。 这种解决方案使只轮廓部分文本成为可能。

我已经回答了相同的问题(answer

班级:

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

}

库:OutlineSpan

答案 13 :(得分:1)

我想添加一个解决方案以解决性能问题。例如,@ YGHM的答案和其他几个答案的确起作用,但是由于onDraw会调用setTextColor,因此会导致无限invalidate()的调用。因此,为了解决该问题,您还需要覆盖invalidate()并添加一个变量isDrawing,当true进行中并使用行程。如果变量为onDraw(),则invalidate将返回。

true

您的onDraw如下所示:

override fun invalidate() {
    if (isDrawing) return
    super.invalidate()
  }

答案 14 :(得分:1)

@YGHM的信用添加阴影支持 enter image description here

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" />

答案 15 :(得分:0)

这是我可以通过扩展 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 设置自定义字体时,在屏幕截图中显示结果。

enter image description here