Is there a simple way to have text be able to have a black outline? I have textviews that will be different colors, but some of the colors don't show up on my background so well, so I was wondering if there's an easy way to get a black outline or something else that will do the job? I'd prefer not to have to create a custom view and make a canvas and such.
-
8For anyone reading this question and considering using the Paint-Stroke solution, please note there is a [**bug with strokes in Android 4.4**](https://code.google.com/p/android/issues/detail?id=62800). If the text size if above 256 pixels it results in very weird stroke rendering. A workaround is to draw the outline/stroke with the alternative method [**presented in this answer**](http://stackoverflow.com/a/5817510/708906). I didn't want to spam this on every Stroke-type answer, so putting it here to make people aware and save them the grief I went through. – Tony Chan Jun 12 '14 at 01:54
-
1Possible duplicate of [Add opaque "shadow" (outline) to Android TextView](http://stackoverflow.com/questions/39106454/add-opaque-shadow-outline-to-android-textview) – juergen d Sep 02 '16 at 10:12
16 Answers
outline effect can be achieved using shadow in 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"

- 1,165
- 1
- 7
- 4
-
1
-
9This does not result in an outline as it only shows on two sides. – ban-geoengineering Jul 30 '18 at 18:52
-
-
4
So, little late, but MagicTextView will do text outlines, amongst other things.
<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" />
Note: I made this, and am posting more for the sake of future travelers than the OP. It's borderline spam, but being on-topic, perhaps acceptable?

- 5,007
- 1
- 26
- 23
-
1Hello, how can we add borders like these on text being entered in EditText? – TilalHusain Jan 24 '14 at 18:40
-
-
dreamText.setStroke(4, Color.BLACK); dreamText.setTextColor(Color.WHITE); i AM USing these settings but my text color is transparent hoewvre i can see black outline. What is wrong? – Muhammad Umar Feb 10 '15 at 12:26
-
it's okay, but it doesn't really add a border. It rather takes the text and uses the outher edge as the border which doesn't give the same visual result. – Warpzit Jun 12 '15 at 08:48
-
2This solution causes `onDraw` to be called in a recursive manner because of the call `setTextColor` inside of `onDraw`. – Sermilion Feb 13 '19 at 18:00
You can put a shadow behind the text, which can often help readability. Try experimenting with 50% translucent black shadows on your green text. Details on how to do this are over here: Android - shadow on text?
To really add a stroke around the text, you need to do something a bit more involved, like this: How do you draw text with a border on a MapView in Android?

- 1
- 1

- 10,071
- 6
- 34
- 37
-
3Please note there is a [**bug with strokes in Android 4.4**](https://code.google.com/p/android/issues/detail?id=62800). If the text size if above 256 pixels it results in very weird stroke rendering. A workaround is to draw the outline/stroke with the alternative method [**presented in this answer**](http://stackoverflow.com/a/5817510/708906). – Tony Chan Jun 12 '14 at 01:51
-
-
shadow isn't good enough, white text on white background layout still looks really bad with that black shadow – user924 Sep 20 '20 at 13:32
It is quite an old question but still I don't see any complete answers. So I am posting this solution, hoping that someone struggling with this problem might find it useful. The simplest and most effective solution is to override TextView class' onDraw method. Most implementations I have seen use drawText method to draw the stroke but that approach doesn't account for all the formatting alignment and text wrapping that goes in. And as a result often the stroke and text end up at different places. Following approach uses super.onDraw to draw both the stroke and fill parts of the text so you don't have to bother about rest of the stuff. Here are the steps
- Extend TextView class
- Override onDraw method
- Set paint style to FILL
- call parent class on Draw to render text in fill mode.
- save current text color.
- Set current text color to your stroke color
- Set paint style to Stroke
- Set stroke width
And call parent class onDraw again to draw the stroke over the previously rendered text.
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); } }
That is all. This class uses custom XML attributes to enable specifying stroke color and width from the XML layout files. Therefore, you need to add these attributes in your attr.xml file in subfolder 'values' under folder 'res'. Copy and paste the following in your attr.xml file.
<?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>
Once you are done with that, you can use the custom StrokedTextView class in your XML layout files and specify stroke color and width as well. Here is an example
<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" />
Remember to replace package name with your project's package name. Also add the xmlns namespace in the layout file in order to use custom XML attributes. You can add the following line in your layout file's root node.
xmlns:strokeAttrs="http://schemas.android.com/apk/res-auto"

- 764
- 9
- 13
-
2What a great, elegant solution! I implemented this and it works well. I just changed textStrokeWidth to a dimension (and a.getDimensionPixelSize). Thanks! – dgmltn Dec 19 '15 at 05:02
-
@SohailZahid Can you please be more specific about what you see in the outcome. Do you get errors? I use it in API 23 and it works fine. – Nouman Hanif May 29 '16 at 02:31
-
@SohailZahid that is strange. Did you test on API level lower than 23? The example code above will not show an outline unless you provide a stroke width value greater than zero. Can you make sure if an appropriate strokewidth value is provided? – Nouman Hanif May 29 '16 at 02:53
-
i have provided stroke value in xml not show then then i add programmatically. same result – Sohail Zahid May 29 '16 at 03:05
-
Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/113237/discussion-between-nouman-hanif-and-sohail-zahid). – Nouman Hanif May 29 '16 at 03:34
-
1Works good, thanks. As they outline took over the whole text, I changed the order in my case: first the outline is painted and then the text. – mdiener Nov 11 '16 at 16:15
-
2Works great. Do not use Android Studio design view to test the outlines, the representation is not accurate enough. Just spent 2h debugging a non-issue. – llmora Dec 01 '16 at 22:48
-
10This solution will cause infinite number of onDraw, because setTextColor calls invalidate. – Guliash Mar 29 '18 at 14:58
-
@Guliash, the solution is good. setTextColor() will produce only 1 extra invalidate due to the change of the color. So the onDraw of the solution will produce 2 extra invalidates. If you check and deep into the source of setTextColor() you will notice that it makes sure to invalidate only if the color has changed. – PerracoLabs May 19 '18 at 09:52
-
Best balance of simplicity and capability in this thread, if you ask me. A couple simple changes should be made, however. 1 - as @dgmltn mentioned, it's easiest to style `textStrokeWidth` as a `dimension`, then grab it via `a.getDimensionPixelSize` and skip the `dpToPx` conversion. 2 - as @mdiener mentioned, swap the order of the paints: first record the text color, set to stroke color, and paint stroke; then revert color and paint fill. This is because stroke goes both on the inside AND outside of the text, so if it is painted 2nd it covers part of the text. (Also, don't extend `Button`.) – VerumCH Aug 19 '18 at 09:37
-
3Actually, @Guliash is correct. After testing, once this method is called it causes an infinite loop of calling itself due to the `invalidate()` call buried in the inner workings of `setTextColor`. Unless you want to copy every last line of code from `TextView` into your own class, the only way around this that I can see is to brute-force access the _private_ `mCurTextColor` field of `TextView` using Reflection. See [this answer](https://stackoverflow.com/a/3603647/3617803) to see roughly how to do it. Just use `field.set(this, colorInt)` instead of using `field.get()`. – VerumCH Aug 19 '18 at 10:17
-
1
-
The framework supports text-shadow but does not support text-outline. But there is a trick: shadow is something that is translucent and fades. Redraw the shadow a couple of times and all the alpha gets summed up and the result is an outline.
A very simple implementation extends TextView
and overrides the draw(..)
method. Every time a draw is requested our subclass does 5-10 drawings.
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" />

- 13,290
- 6
- 50
- 62
-
3Thank you very much. However I rather use this method: '@Override protected void onDraw(Canvas canvas) { for (int i = 0; i < 5; i++) { super.onDraw(canvas); } }' – JoachimR Sep 07 '14 at 11:07
-
1Additional info: One has to implement at least the ctor with Context and AttributeSet. Otherwise you run into. `java.lang.NoSuchMethodException:
[class android.content.Context, interface android.util.AttributeSet` – Bevor Oct 27 '18 at 08:53
I've just been trying to figure out how to do this and couldn't find a good guide online but eventually figured it out. As Steve Pomeroy suggested, you do have to do something more involved. In order to get the outlined text effect, you draw the text twice: once with a thick outline and then the second time we draw the main text over the outline. But, the task is made easier because you can very easily adapt one of the code samples provided with the SDK, namely the one under this name in your SDK directory: "/samples/android-/ApiDemos/src/com/example/android/apis/view/LabelView.java". Which can also found on the Android developer website here.
Depending on what you're doing, it's very easy to see you will only need to make minor modifications to that code, such as changing it to extend from TextView, etc. Before I discovered this sample I forgot to override onMeasure() (which you must do in addition to overriding onDraw() as is mentioned in the "Building Custom Components" guide on the Android Developer website), which is part of why I was having trouble.
Once you've done that, you can do what I did:
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);
}
So, in order to get the outlined text effect, you draw the text twice: once with a thick outline and then the second time we draw the main text over the outline.

- 856
- 8
- 22
credit to @YGHM add shadow support
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 define
<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 code below
<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" />

- 189
- 1
- 6
-
-
1Calling `super.setTextColor()` from `onDraw()` will cause to view invalidation that will lead to calling `onDraw()` again. This will be an infinite loop. – Sergey Stasishin Dec 27 '21 at 14:44
You can do this programmatically with the below snippet. That provides white letters with black background:
textView.setTextColor(Color.WHITE);
textView.setShadowLayer(1.6f,1.5f,1.3f,Color.BLACK);
The parameters of the method are radius,dx,dy,color. You can change them for you specific needs.
I hope I will help someone that creates TextView programmatically and not having it inside xml.
Cheers to the stackOverflow community!

- 2,700
- 11
- 16
I want to add a solution in order to solve the performance issue. For example, the answer of @YGHM and a few others does the job, but it causes infinite call of onDraw
because setTextColor
calls invalidate()
. So in order to solve it, you also need to override invalidate()
and add a variable isDrawing
that you will set to true
, when onDraw()
is in progress and drawing with a stroke. invalidate will return if the variable is true
.
override fun invalidate() {
if (isDrawing) return
super.invalidate()
}
Your onDraw will look like this:
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)
}
}

- 1,920
- 3
- 35
- 54
-
3Why is it so few upvotes? This is a really great solution for all these "infinite loops" and "hidden api reflexion" problems! – Sergey Stasishin Dec 27 '21 at 15:48
-
Here's the trick I found that works better than MagicTextView's 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);
}

- 313
- 3
- 13
-
I sort of see that the right side of the TextView is being cropped - and the outline isn't fully draw on that side... as if it runs out of room – RoundSparrow hilltx Jul 11 '14 at 14:29
-
8One more thing. I suspect setTextColor is forcing a redraw - which causes an endless loop of this onDraw being called over and over. Putting a logcat or other indicator in this method is advised while testing. – RoundSparrow hilltx Jul 11 '14 at 17:10
-
@RoundSparrowhilltx is correct. As I mentioned in a comment to another similar answer, I suspect the only way around this short of copying and pasting the entirety of `TextView` into your own class is to use Reflection to directly access the _private_ `mCurTextColor` field in `TextView`. [This answer](https://stackoverflow.com/a/3603647/3617803) provides a general guideline of how to do this. If you want hint and link text to have a stroke as well, you will also have to change `mHintTextColor` and `mLinkTextColor`. Unfortunately changing `mTextColor` does nothing, as it is only referenced. – VerumCH Aug 19 '18 at 10:22
-
I've written a class to perform text with outline and still support all the other attributes and drawing of a normal text view.
it basically uses the super.onDraw(Canves canvas)
on the TextView
but draws twice with different styles.
hope this helps.
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>

- 131
- 1
- 9
I have created a library based on Nouman Hanif's answer with some additions. For example, fixing a bug that caused an indirect infinite loop on View.invalidate() calls.
OTOH, the library also supports outlined text in EditText widgets, as it was my real goal and it needed a bit more work than TextView.
Here is the link to my library: https://github.com/biomorgoth/android-outline-textview
Thanks to Nouman Hanif for the initial idea on the solution!

- 1
- 1

- 630
- 4
- 8
I found simple way to outline view without inheritance from TextView. I had wrote simple library that use Android's Spannable for outlining text. This solution gives possibility to outline only part of text.
I already had answered on same question (answer)
Class:
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)
}
}
Library: OutlineSpan

- 845
- 7
- 17
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));
}
}

- 1,515
- 1
- 16
- 19
So you want a stroke around the textview? Unfortunately there is no simple way to do it with the styling. You'll have to create another view and place your textview over-top, making the parent view (the one it's on top of) just a few pixels bigger - this should create an outline.

- 16,305
- 8
- 63
- 97
-
Hmm, that sounds like more of a pain than it's worth. All I care about is having green text readable on a white background (right now it's kind of hard to read) http://img88.imageshack.us/i/devicez.png/ The red looks fine. Maybe if I just change to a darker green, but I really wish I could get some kind of outline or something – Falmarri Jul 06 '10 at 02:09
-
Are you trying to outline the text itself then? That's still not really possible, unless you do your own custom TextView, but it's probably more work than it's worth. It's probably easier just to make it dark green. – xil3 Jul 06 '10 at 08:25
-
2One minor request from someone who is red/green colorblind: please consider adding in an alternative representation of the same red/green information, as seeing dark green vs. dark red is often hard for us. Maybe an up/down arrow as well? – Steve Pomeroy Jul 07 '10 at 15:14
-
Here is the simplest way I could find by extending 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);
}
}
then you only need to add the following to the attrs.xml file
<declare-styleable name="CustomTextView">
<attr name="stroke" format="float"/>
</declare-styleable>
and now you will be able to set the stroke widht by app:stroke
while retaining all other desirable properties of TextView. my solution only draws the stroke w/o a fill. this makes it a bit simpler than the others. bellow a screencapture with the result while setting a custom font to my customtextview.

- 2,887
- 1
- 13
- 35