2

I will start by explaining that what I want to acquire isn't a border around the view itself, but actually it's a border around the text inside textview.

I already tried to set shadow inside the textview and onto style.xml, and none of these solutions worked. I was trying to make the code below works but I am far from a good dev so I don't know even how to use it. All I know is that it involves reflection (not that I know what that means).

My java class (OutlineTextView)

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.widget.TextView;

import androidx.appcompat.widget.AppCompatTextView;

import com.example.detetiveinvestigativo.R;

import java.lang.reflect.Field;

public class OutlineTextView extends AppCompatTextView {
    private Field colorField;
    private int textColor;
    private int outlineColor;

    public OutlineTextView(Context context) {
        this(context, null);
    }

    public OutlineTextView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.textViewStyle);
    }

    public OutlineTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        try {
            colorField = TextView.class.getDeclaredField("mCurTextColor");
            colorField.setAccessible(true);

            // If the reflection fails (which really shouldn't happen), we
            // won't need the rest of this stuff, so we keep it in the try-catch

            textColor = getTextColors().getDefaultColor();

            // These can be changed to hard-coded default
            // values if you don't need to use XML attributes

            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.OutlineTextView);
            outlineColor = a.getColor(R.styleable.OutlineTextView_outlineColor, Color.TRANSPARENT);
            setOutlineStrokeWidth(a.getDimensionPixelSize(R.styleable.OutlineTextView_outlineWidth, 0));
            a.recycle();
        }
        catch (NoSuchFieldException e) {
            // Optionally catch Exception and remove print after testing
            e.printStackTrace();
            colorField = null;
        }
    }

    @Override
    public void setTextColor(int color) {
        // We want to track this ourselves
        // The super call will invalidate()

        textColor = color;
        super.setTextColor(color);
    }

    public void setOutlineColor(int color) {
        outlineColor = color;
        invalidate();
    }

    public void setOutlineWidth(float width) {
        setOutlineStrokeWidth(width);
        invalidate();
    }

    private void setOutlineStrokeWidth(float width) {
        getPaint().setStrokeWidth(2 * width + 1);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // If we couldn't get the Field, then we
        // need to skip this, and just draw as usual

        if (colorField != null) {
            // Outline
            setColorField(outlineColor);
            getPaint().setStyle(Paint.Style.STROKE);
            super.onDraw(canvas);

            // Reset for text
            setColorField(textColor);
            getPaint().setStyle(Paint.Style.FILL);
        }

        super.onDraw(canvas);
    }

    private void setColorField(int color) {
        // We did the null check in onDraw()
        try {
            colorField.setInt(this, color);
        }
        catch (IllegalAccessException | IllegalArgumentException e) {
            // Optionally catch Exception and remove print after testing
            e.printStackTrace();
        }
    }

    // Optional saved state stuff

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.textColor = textColor;
        ss.outlineColor = outlineColor;
        ss.outlineWidth = getPaint().getStrokeWidth();
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        textColor = ss.textColor;
        outlineColor = ss.outlineColor;
        getPaint().setStrokeWidth(ss.outlineWidth);
    }

    private static class SavedState extends BaseSavedState {
        int textColor;
        int outlineColor;
        float outlineWidth;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            textColor = in.readInt();
            outlineColor = in.readInt();
            outlineWidth = in.readFloat();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(textColor);
            out.writeInt(outlineColor);
            out.writeFloat(outlineWidth);
        }

        public static final Parcelable.Creator<SavedState>
                CREATOR = new Parcelable.Creator<SavedState>() {

            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="OutlineTextView" >
        <attr name="outlineColor" format="color" />
        <attr name="outlineWidth" format="dimension" />
    </declare-styleable>
</resources>

my_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    tools:context=".Interface.CharacterSelection.CharacterSelectionFragment"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="7dp"
    android:paddingBottom="7dp"
    android:background="@drawable/wood_texture"
    android:clickable="true"
    android:focusable="false"
    android:id="@+id/cs_parent_layout">

<androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/cs_textview_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/select_characters"
        android:textSize="25sp"
        android:textColor="@color/white"
        android:fontFamily="@font/joystix_monospace"
        app:outlineColor="@color/black"
        app:outlineWidth="4dp"
        android:gravity="center"
        android:layout_marginBottom="7dp"
        android:clickable="false"/>
</androidx.appcompat.widget.LinearLayoutCompat>

How do I use this class? How do I convert AppCompatTextView to OutlineTextView and what do I need to make it works?

EDIT:

This is what I want to do, it's literally put a black border around the text: click here to see the image

Community
  • 1
  • 1
SecretX
  • 140
  • 1
  • 14
  • Hi, do you want to put a border around the entire text of the TextView or a part of the text from it? – Zain Oct 06 '19 at 01:37
  • @Zain I've uploaded an image with an example of how do I need the text of the textview to looks like. – SecretX Oct 06 '19 at 01:49
  • I am not sure of the border; do you mean to have a blank inner gap for the text letters ? – Zain Oct 06 '19 at 01:57
  • also in your xml component, use OutlineTextView instead of AppCompatTextView – Zain Oct 06 '19 at 02:01
  • @Zain no, I mean the black border around the text. – SecretX Oct 06 '19 at 02:21
  • Check this post: [How do you draw text with a border](https://stackoverflow.com/questions/1723846/how-do-you-draw-text-with-a-border-on-a-mapview-in-android) it offers more simple ways. – MaCoda Oct 06 '19 at 02:22
  • @Zain yep I saw that earlier but I have no idea how do I use Canvas, Paint and draw method... Darn man why can't google just add a text border property to TextViews (and EditTexts)? – SecretX Oct 06 '19 at 02:25

2 Answers2

1

You can use shadows. The code below creates a black shadow around the text. To change the color change shadowColor field. The shadowDy is the vertical offset of the shadow and the shadowDx horizontal offset.

android:shadowColor="#000000"
android:shadowRadius="5"
android:shadowDy="2"
android:shadowDx="2"
Chris Papantonis
  • 720
  • 7
  • 18
  • Ok, I did what you suggested, here are the results - before: https://i.imgur.com/8Y3DdhH.png and after: https://i.imgur.com/Yu8UDMC.png I mean, it DID worked, but not as expected, as I don't need a shadow for my text, but a outline/border with a solid color and fixed size around my text (like 6dp for example). Got anymore ideas? – SecretX Oct 06 '19 at 15:07
1

Just make a drawabale named box_border.xml in your Drawable folder.

<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">

    <stroke android:color="@color/navy"/>
    <stroke android:width="1dp"/>
    <corners android:radius="00dp"/>
</shape>

And set it as the backgraound of you text view.

<TextView
        android:id="@+id/cs_textview_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/select_characters"
        android:textSize="25sp"
        android:textColor="@color/white"
        android:background="@drawable/box_border"
        />
</androidx.appcompat.widget.LinearLayoutCompat>

Hope it helps. Use it in as many text Views as required. Please post a pic if you want something else.

user14253444
  • 67
  • 1
  • 13