Tekst konturu Android Textview

83

Czy istnieje prosty sposób, aby tekst miał czarny kontur? Mam widoki tekstu, które będą miały różne kolory, ale niektóre kolory nie są tak dobrze widoczne na moim tle, więc zastanawiałem się, czy istnieje łatwy sposób na uzyskanie czarnego konturu lub czegoś innego, co będzie działać? Wolałbym nie tworzyć niestandardowego widoku, tworzyć płótna i tym podobne.

Falmarri
źródło
7
Każdy, kto czyta to pytanie i rozważa użycie rozwiązania Paint-Stroke, może zauważyć, że w systemie Android 4.4 jest błąd związany z obrysami . Jeśli rozmiar tekstu przekracza 256 pikseli, powoduje to bardzo dziwne renderowanie obrysu. Rozwiązaniem jest narysowanie konturu / kreski za pomocą alternatywnej metody przedstawionej w tej odpowiedzi . Nie chciałem tego spamować przy każdej odpowiedzi typu Stroke, więc umieszczając ją tutaj, aby ludzie byli świadomi i uratowali ich smutek, przez który przeszedłem.
Tony Chan,

Odpowiedzi:

56

Możesz umieścić cień za tekstem, co często może poprawić czytelność. Spróbuj poeksperymentować z półprzezroczystymi czarnymi cieniami na zielonym tekście w 50%. Szczegóły, jak to zrobić, znajdziesz tutaj: Android - cień na tekście?

Aby naprawdę dodać obrys wokół tekstu, musisz zrobić coś bardziej skomplikowanego , na przykład: Jak narysować tekst z obramowaniem w MapView w systemie Android?

Steve Pomeroy
źródło
3
Należy pamiętać, że w systemie Android 4.4 jest błąd z obrysami . Jeśli rozmiar tekstu przekracza 256 pikseli, powoduje to bardzo dziwne renderowanie obrysu. Rozwiązaniem jest narysowanie konturu / kreski alternatywną metodą przedstawioną w tej odpowiedzi .
Tony Chan,
Czy ten komentarz dotyczy widoku tekstu lub rozmiaru czcionki?
John
cień nie jest wystarczająco dobry, biały tekst na białym tle nadal wygląda naprawdę źle z tym czarnym cieniem
user924
81

efekt konturu można uzyskać za pomocą cienia w 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"
Rafał
źródło
to powinno być najlepsze rozwiązanie. To było wspaniałe! Dzięki
Renan Bandeira
5
Nie powoduje to powstania konspektu, ponieważ jest on widoczny tylko z dwóch stron.
zakaz geoinżynierii
Dla mnie idealne!!
Ely Dantas
ten cień jest bardzo słaby dla konturu
user924
55

Trochę późno, ale MagicTextView między innymi wykona kontury tekstu.

wprowadź opis obrazu tutaj

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

Uwaga: zrobiłem to i publikuję więcej ze względu na przyszłych podróżników niż OP. To spam z pogranicza, ale poruszanie się na temat, może akceptowalne?

ABentSpoon
źródło
1
Witaj, jak możemy dodać takie obramowania do tekstu wprowadzanego w EditText?
TilalHusain
Jakieś pomysły na temat EditText?
Piotr
dreamText.setStroke (4, Color.BLACK); dreamText.setTextColor (Color.WHITE); Używam tych ustawień, ale kolor mojego tekstu jest przezroczysty, ale widzę czarny kontur. Co jest nie tak?
Muhammad Umar
jest w porządku, ale tak naprawdę nie dodaje obramowania. Pobiera raczej tekst i używa zewnętrznej krawędzi jako obramowania, co nie daje takiego samego efektu wizualnego.
Warpzit,
1
To rozwiązanie powoduje onDrawwywołanie w sposób rekurencyjny z powodu wywołania setTextColorwewnątrz onDraw.
Sermilion
23

To dość stare pytanie, ale nadal nie widzę pełnych odpowiedzi. Dlatego zamieszczam to rozwiązanie, mając nadzieję, że ktoś borykający się z tym problemem może uznać je za przydatne. Najprostszym i najbardziej efektywnym rozwiązaniem jest przesłonięcie metody onDraw klasy TextView. Większość implementacji, które widziałem, używa metody drawText do narysowania obrysu, ale to podejście nie uwzględnia całego wyrównania formatowania i zawijania tekstu. W rezultacie często obrys i tekst kończą się w różnych miejscach. Poniższe podejście wykorzystuje super.onDraw do narysowania zarówno obrysu, jak i wypełnienia części tekstu, więc nie musisz martwić się o resztę. Oto kroki

  1. Rozszerz klasę TextView
  2. Zastąp metodę onDraw
  3. Ustaw styl malowania na FILL
  4. wywołaj klasę nadrzędną w aplikacji Draw, aby renderować tekst w trybie wypełnienia.
  5. zapisz bieżący kolor tekstu.
  6. Ustaw bieżący kolor tekstu na kolor obrysu
  7. Ustaw styl malowania na Stroke
  8. Ustaw szerokość obrysu
  9. I ponownie wywołaj klasę nadrzędną onDraw, aby narysować obrys nad wcześniej renderowanym tekstem.

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

To wszystko. Ta klasa używa niestandardowych atrybutów XML, aby umożliwić określanie koloru i szerokości obrysu z plików układu XML. Dlatego musisz dodać te atrybuty do pliku attr.xml w podfolderze „values” w folderze „res”. Skopiuj i wklej następujące elementy w pliku 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>

Gdy skończysz, możesz użyć niestandardowej klasy StrokedTextView w swoich plikach układu XML, a także określić kolor i szerokość obrysu. Oto przykład

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

Pamiętaj, aby zastąpić nazwę pakietu nazwą pakietu projektu. Dodaj również przestrzeń nazw xmlns w pliku układu, aby użyć niestandardowych atrybutów XML. Możesz dodać następujący wiersz w węźle głównym pliku układu.

xmlns:strokeAttrs="http://schemas.android.com/apk/res-auto"
Nouman Hanif
źródło
2
Cóż za wspaniałe, eleganckie rozwiązanie! Zaimplementowałem to i działa dobrze. Właśnie zmieniłem textStrokeWidth na wymiar (i a.getDimensionPixelSize). Dzięki!
dgmltn
1
Działa dobrze, dzięki. Ponieważ konspekt przejął cały tekst, zmieniłem kolejność w moim przypadku: najpierw malowany jest kontur, a potem tekst.
mdiener
2
Działa świetnie. Nie używaj widoku projektu Android Studio do testowania konturów, reprezentacja nie jest wystarczająco dokładna. Właśnie spędziłem 2 godziny na debugowaniu nie-problemu.
llmora
7
To rozwiązanie spowoduje nieskończoną liczbę onDraw, ponieważ wywołania setTextColor są nieważne.
Guliash
2
Właściwie @Guliash ma rację. Po przetestowaniu wywołanie tej metody powoduje nieskończoną pętlę wywoływania samej siebie z powodu invalidate()wywołania ukrytego w wewnętrznym działaniu setTextColor. O ile nie chcesz skopiować każdego ostatniego wiersza kodu TextViewdo swojej własnej klasy, jedynym sposobem obejścia tego jest brutalne wymuszenie dostępu do prywatnego mCurTextColor pola TextViewprzy użyciu Reflection. Zobacz tę odpowiedź, aby z grubsza zobaczyć, jak to zrobić. Po prostu użyj field.set(this, colorInt)zamiast używać field.get().
VerumCH
23

Framework obsługuje cień tekstu, ale nie obsługuje konturu tekstu. Jest jednak pewien podstęp: cień jest półprzezroczysty i blaknie. Przerysuj cień kilka razy, a cała alfa zostanie zsumowana, a wynikiem jest zarys.

Bardzo prosta implementacja rozszerza TextViewi zastępuje draw(..)metodę. Za każdym razem, gdy wymagane jest losowanie, nasza podklasa wykonuje 5-10 rysunków.

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" />
Zsolt Safrany
źródło
3
Dziękuję Ci bardzo. Jednak wolę używać tej metody: „@Override protected void onDraw (Canvas canvas) {for (int i = 0; i <5; i ++) {super.onDraw (canvas); }} '
IHeartAndroid
1
Dodatkowe informacje: Należy zaimplementować przynajmniej ctor z Context i AttributeSet. W przeciwnym razie napotkasz. java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet
Bevor
15

Właśnie próbowałem wymyślić, jak to zrobić i nie mogłem znaleźć dobrego przewodnika w Internecie, ale w końcu to rozgryzłem. Jak zasugerował Steve Pomeroy, musisz zrobić coś bardziej zaangażowanego. Aby uzyskać efekt konturu tekstu, rysujesz tekst dwukrotnie: raz grubym konturem, a następnie drugi raz rysujemy tekst główny nad konturem. Ale zadanie jest łatwiejsze, ponieważ można bardzo łatwo dostosować jeden z przykładów kodu dostarczonych z SDK, a mianowicie ten pod tą nazwą w katalogu SDK: "/ samples / android- / ApiDemos / src / com / example / android /apis/view/LabelView.java ”. Które można również znaleźć w witrynie programisty Androida tutaj .

W zależności od tego, co robisz, bardzo łatwo jest zauważyć, że będziesz musiał dokonać tylko drobnych modyfikacji w tym kodzie, takich jak zmiana go na rozszerzenie z TextView itp. Zanim odkryłem ten przykład, zapomniałem nadpisać onMeasure () (co musisz zrobić oprócz zastąpienia onDraw (), jak wspomniano w przewodniku „Tworzenie niestandardowych komponentów” na stronie Android Developer), co jest jednym z powodów, dla których miałem problemy.

Kiedy już to zrobisz, możesz zrobić to, co ja:

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

Tak więc, aby uzyskać efekt konturu tekstu, rysujesz tekst dwukrotnie: raz grubym konturem, a następnie drugi raz rysujemy tekst główny nad konturem.

sversch
źródło
9

kredyt do @YGHM dodaj obsługę cienia wprowadź opis obrazu tutaj

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 zdefiniować

<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 poniżej

<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" />
Arthur
źródło
8

Oto sztuczka, którą znalazłem, która działa lepiej niż IMO obrysu MagicTextView

@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);
}
VinZen
źródło
I jakby zobaczyć, że prawa strona TextView jest przycięty - i kontur nie jest w pełni korzystać z tej strony ... jakby to wybiega z pokoju
RoundSparrow hilltx
7
Jeszcze jedna rzecz. Podejrzewam, że setTextColor wymusza przerysowanie - co powoduje nieskończoną pętlę wywoływania tego onDraw w kółko. Podczas testowania zaleca się umieszczenie logcata lub innego wskaźnika w tej metodzie.
RoundSparrow hilltx
@RoundSparrowhilltx jest poprawne. Jak wspomniałem w komentarzu do innej podobnej odpowiedzi, podejrzewam, że jedynym sposobem obejścia tego braku kopiowania i wklejania całości TextViewdo własnej klasy jest użycie Reflection, aby uzyskać bezpośredni dostęp do prywatnego mCurTextColor pola w TextView. Ta odpowiedź zawiera ogólne wytyczne, jak to zrobić. Jeśli chcesz, aby podpowiedź i tekst łącza również miały obrys, będziesz musiał również zmienić mHintTextColori mLinkTextColor. Niestety zmiana mTextColornic nie daje, ponieważ jest tylko przywoływana.
VerumCH,
in will loop onDraw forever
user924
8

Napisałem klasę, aby wykonywać tekst z konturem i nadal obsługiwać wszystkie inne atrybuty i rysowanie normalnego widoku tekstu.

w zasadzie używa super.onDraw(Canves canvas)on the, TextViewale rysuje dwa razy z różnymi stylami.

mam nadzieję że to pomoże.

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>
YGHM
źródło
a.recycle () na TypedArray brakuje
Leos Literak,
5

Możesz to zrobić programowo za pomocą poniższego fragmentu kodu. To zapewnia białe litery z czarnym tłem:

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

Parametry metody to promień, dx, dy, kolor. Możesz je zmienić dla swoich potrzeb.

Mam nadzieję, że pomogę komuś, kto programowo tworzy TextView i nie ma go w XML.

Pozdrawiam społeczność stackOverflow!

Farmer
źródło
2

Stworzyłem bibliotekę na podstawie odpowiedzi Noumana Hanifa z kilkoma dodatkami. Na przykład naprawienie błędu, który powodował pośrednią nieskończoną pętlę w wywołaniach View.invalidate ().

OTOH, biblioteka obsługuje również konturowany tekst w widżetach EditText, ponieważ był to mój prawdziwy cel i wymagał nieco więcej pracy niż TextView.

Oto link do mojej biblioteki: https://github.com/biomorgoth/android-outline-textview

Podziękowania dla Nouman Hanif za wstępny pomysł na rozwiązanie!

biomorgoth
źródło
2

Chcę dodać rozwiązanie, aby rozwiązać problem z wydajnością. Na przykład odpowiedź @YGHM i kilku innych działa, ale powoduje nieskończone wezwanie, onDrawponieważ setTextColordzwoni invalidate(). Aby go rozwiązać, musisz również nadpisać invalidate()i dodać zmienną isDrawing, którą ustawisz true, gdy onDraw()jest w toku i rysuje pociągnięciem. Niepoprawne zwróci wartość, jeśli zmienna to true.

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

Twój onDraw będzie wyglądał następująco:

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)
    }
  }
Sermilion
źródło
1

MagicTextView jest bardzo przydatny do tworzenia czcionki obrysowej, ale w moim przypadku powoduje błąd taki jak ten, spowodowany atrybutami powielania tła ustawionymi przez MagicTextView

więc musisz edytować attrs.xml i 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));
}
}
Matsumoto Kazuya
źródło
1

Znalazłem prosty sposób na zarysowanie widoku bez dziedziczenia po TextView . Napisałem prostą bibliotekę, która używa Spannable Androida do konspektu tekstu. Takie rozwiązanie daje możliwość obrysowania tylko części tekstu.

Odpowiedziałem już na to samo pytanie ( odpowiedź )

Klasa:

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

}

Biblioteka: OutlineSpan

Pavel Santaev
źródło
1
nie obsługuje tekstu
wielowierszowego
0

Więc chcesz obrysować widok tekstu? Niestety nie ma prostego sposobu na zrobienie tego stylizacją. Będziesz musiał utworzyć inny widok i umieścić widok tekstu na górze, dzięki czemu widok nadrzędny (ten, na którym znajduje się), będzie tylko kilka pikseli większy - powinno to stworzyć kontur.

xil3
źródło
Hmm, to brzmi bardziej jak ból, niż to jest warte. Jedyne, na czym mi zależy, to czytelny zielony tekst na białym tle (w tej chwili trochę trudny do odczytania) img88.imageshack.us/i/devicez.png Czerwony wygląda dobrze. Może gdybym po prostu zmienił na ciemniejszą zieleń, ale naprawdę chciałbym uzyskać jakiś kontur lub coś takiego
Falmarri
Czy w takim razie próbujesz zarysować sam tekst? To wciąż nie jest możliwe, chyba że zrobisz własny niestandardowy TextView, ale prawdopodobnie wymaga to więcej pracy niż jest warte. Prawdopodobnie łatwiej jest po prostu uczynić go ciemnozielonym.
xil3
2
Jedna drobna prośba od osoby, która nie widzi kolorów w kolorach czerwonym / zielonym: rozważ dodanie alternatywnej reprezentacji tych samych informacji o kolorze czerwonym / zielonym, ponieważ często trudno nam jest zobaczyć ciemnozieloną i ciemnoczerwoną. Może też strzałka w górę / w dół?
Steve Pomeroy
To dobra uwaga, Steve. Prawdopodobnie dodam to w przyszłości.
Falmarri
0

Oto najprostszy sposób, jaki mogłem znaleźć, rozszerzając 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);
}
}

wtedy wystarczy dodać następujące elementy do pliku attrs.xml

<declare-styleable name="CustomTextView">
    <attr name="stroke" format="float"/>
</declare-styleable>

a teraz będziesz mógł ustawić szerokość obrysu app:stroke, zachowując wszystkie inne pożądane właściwości TextView. moje rozwiązanie rysuje tylko obrys bez wypełnienia. to sprawia, że ​​jest to nieco prostsze niż inne. poniżej zrzut ekranu z wynikiem, ustawiając niestandardową czcionkę w moim niestandardowym widoku tekstu.

wprowadź opis obrazu tutaj

quealegriamasalegre
źródło