Текст структуры текстового представления Android

83

Есть ли простой способ сделать текст черным? У меня есть текстовые представления, которые будут разных цветов, но некоторые цвета не так хорошо отображаются на моем фоне, поэтому мне было интересно, есть ли простой способ получить черный контур или что-то еще, что подойдет для этой работы? Я бы предпочел не создавать собственный вид, создавать холст и тому подобное.

Фалмарри
источник
7
Для всех, кто читает этот вопрос и рассматривает возможность использования решения Paint-Stroke, обратите внимание, что в Android 4.4 есть ошибка со штрихами . Если размер текста превышает 256 пикселей, это приводит к очень странному рендерингу обводки. Обходной путь - нарисовать контур / обводку альтернативным методом, представленным в этом ответе . Я не хотел спамить это на каждый ответ типа Stroke, поэтому поместил его здесь, чтобы люди знали и спасли их от горя, через которое я прошел.
Тони Чан

Ответы:

56

Вы можете добавить тень за текстом, что часто может улучшить читаемость. Попробуйте поэкспериментировать с 50% полупрозрачными черными тенями на зеленом тексте. Подробности о том, как это сделать, находятся здесь: Android - тень на тексте?

Чтобы действительно добавить обводку вокруг текста, вам нужно сделать что-то более сложное, например: как нарисовать текст с рамкой на MapView в Android?

Стив Помрой
источник
3
Обратите внимание, что в Android 4.4 есть ошибка со штрихами . Если размер текста превышает 256 пикселей, это приводит к очень странному рендерингу обводки. Обходной путь - нарисовать контур / обводку альтернативным методом, представленным в этом ответе .
Тони Чан
Этот комментарий относится к текстовому представлению или к размеру шрифта?
Джон
тень недостаточно хороша, белый текст на белом фоне все еще выглядит очень плохо с этой черной тенью
user924
81

Эффект контура может быть достигнут с помощью тени в 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"
Рафал
источник
это должно быть лучшим решением. Это было здорово! Спасибо
Ренан Бандейра
5
Это не приводит к контуру, поскольку он отображается только с двух сторон.
ban-geoengineering
Идеально для меня!!
Ely Dantas
эта тень очень слабая для контуров
user924
55

Итак, немного поздно, но MagicTextView , помимо прочего, выполняет контуры текста.

введите описание изображения здесь

<com.qwerjk.better_text.MagicTextView
    xmlns:qwerjk="http://schemas.android.com/apk/res/com.qwerjk.better_text"
    android:textSize="78dp"
    android:textColor="#ff333333"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    qwerjk:strokeColor="#FFff0000"
    qwerjk:strokeJoinStyle="miter"
    qwerjk:strokeWidth="5"
    android:text="Magic" />

Примечание: я сделал это и публикую больше для будущих путешественников, чем OP. Это пограничный спам, но, возможно, приемлемо быть по теме?

ABentSpoon
источник
1
Здравствуйте, как мы можем добавить такие границы к тексту, вводимому в EditText?
TilalHusain
Есть идеи по поводу EditText?
Петр
dreamText.setStroke (4, Color.BLACK); dreamText.setTextColor (Color.WHITE); Я ИСПОЛЬЗУЮ эти настройки, но мой цвет текста прозрачный, но я вижу черный контур. Что случилось?
Мухаммад Умар
это нормально, но на самом деле это не добавляет границы. Он скорее берет текст и использует внешний край в качестве границы, что не дает такого же визуального результата.
Warpzit
1
Это решение вызывает onDrawрекурсивный вызов из-за вызова setTextColorвнутри onDraw.
Сермилион,
23

Это довольно старый вопрос, но до сих пор я не вижу полных ответов. Итак, я публикую это решение, надеясь, что кто-то, борющийся с этой проблемой, сочтет его полезным. Самое простое и эффективное решение - переопределить метод onDraw класса TextView. Большинство реализаций, которые я видел, используют метод 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);
       }            
    }
    

Вот и все. Этот класс использует настраиваемые атрибуты XML, чтобы можно было указать цвет и ширину штриха из файлов макета XML. Следовательно, вам необходимо добавить эти атрибуты в ваш файл attr.xml в подпапку «values» в папке «res». Скопируйте и вставьте следующее в свой файл 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>

Когда вы закончите с этим, вы можете использовать настраиваемый класс StrokedTextView в своих файлах макета XML, а также указать цвет и ширину штриха. Вот пример

<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"
Нуман Ханиф
источник
2
Какое прекрасное элегантное решение! Я реализовал это, и он хорошо работает. Я просто изменил textStrokeWidth на размер (и a.getDimensionPixelSize). Благодаря!
dgmltn
1
Хорошо работает, спасибо. Поскольку контур занял весь текст, я изменил порядок в моем случае: сначала нарисован контур, а затем текст.
mdiener
2
Работает отлично. Не используйте представление дизайна Android Studio для проверки контуров, представление недостаточно точное. Просто потратил 2 часа на отладку без проблем.
llmora 01
7
Это решение вызовет бесконечное количество onDraw, потому что вызовы setTextColor недействительны.
Гулиаш
2
Собственно, @Guliash прав. После тестирования, как только этот метод вызывается, он вызывает бесконечный цикл вызова из-за invalidate()вызова, скрытого во внутренней работе setTextColor. Если вы не хотите копировать каждую последнюю строку кода из TextViewв свой собственный класс, единственный способ обойти это, который я вижу, - это грубый доступ к частному mCurTextColor полю с TextViewиспользованием Reflection. См. Этот ответ, чтобы примерно увидеть, как это сделать. Просто используйте field.set(this, colorInt)вместо использования field.get().
VerumCH
23

Фреймворк поддерживает тень текста, но не поддерживает контур текста. Но есть одна хитрость: тень - это нечто полупрозрачное и блеклое. Перерисуйте тень пару раз, и вся альфа будет суммирована, и в результате получится контур.

Очень простая реализация расширяет 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" />
Жолт Сафрани
источник
3
Большое спасибо. Однако я предпочитаю использовать этот метод: '@Override protected void onDraw (Canvas canvas) {for (int i = 0; i <5; i ++) {super.onDraw (canvas); }} '
IHeartAndroid
1
Дополнительная информация: необходимо реализовать хотя бы ctor с Context и AttributeSet. Иначе вы столкнетесь. java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet
Бевор
15

Я просто пытался понять, как это сделать, и не мог найти хорошего руководства в Интернете, но в конце концов понял это. Как предложил Стив Помрой, вам нужно сделать что-то более активное. Чтобы получить эффект обведенного текста, вы рисуете текст дважды: один раз толстым контуром, а затем второй раз мы рисуем основной текст поверх контура. Но задача упрощается, потому что вы можете очень легко адаптировать один из примеров кода, поставляемых с SDK, а именно тот, который находится под этим именем в вашем каталоге SDK: "/ samples / android- / ApiDemos / src / com / example / android /apis/view/LabelView.java ". Его также можно найти на веб-сайте разработчика Android здесь .

В зависимости от того, что вы делаете, очень легко увидеть, что вам нужно будет только внести незначительные изменения в этот код, например изменить его для расширения из TextView и т. Д. Прежде чем я обнаружил этот образец, я забыл переопределить onMeasure () (который вы должны сделать в дополнение к переопределению onDraw (), как указано в руководстве «Создание пользовательских компонентов» на веб-сайте разработчика Android), что является одной из причин, по которой у меня возникли проблемы.

Как только вы это сделаете, вы сможете делать то, что сделал я:

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

Итак, чтобы получить эффект обрисованного текста, вы рисуете текст дважды: один раз толстым контуром, а затем второй раз мы рисуем основной текст поверх контура.

сверш
источник
9

кредит @YGHM добавить теневую поддержку введите описание изображения здесь

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 определить

<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" />
Артур
источник
8

Вот трюк, который, как я обнаружил, работает лучше, чем ход IMO 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
источник
Я как бы вижу, что правая сторона TextView обрезается - и контур не полностью прорисовывается с этой стороны ... как будто он иссякает
RoundSparrow hilltx
7
Еще кое-что. Я подозреваю, что setTextColor вызывает перерисовку, что вызывает бесконечный цикл этого onDraw, вызываемого снова и снова. Во время тестирования рекомендуется использовать логарифм или другой индикатор в этом методе.
RoundSparrow hilltx
@RoundSparrowhilltx правильный. Как я уже упоминал в комментарии к другому аналогичному ответу, я подозреваю, что единственный способ избежать копирования и вставки всего объекта TextViewв ваш собственный класс - это использовать Reflection для прямого доступа к частному mCurTextColor полю в TextView. Этот ответ дает общее руководство, как это сделать. Если вы хотите, чтобы текст подсказки и ссылки также имел обводку, вам также придется изменить mHintTextColorи mLinkTextColor. К сожалению, изменение mTextColorничего не дает, поскольку на него только ссылаются.
VerumCH
in будет цикл onDraw вечно
user924
8

Я написал класс для выполнения текста с контуром и по-прежнему поддерживает все другие атрибуты и рисование обычного текстового представления.

он в основном использует super.onDraw(Canves canvas)on the, 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>
YGHM
источник
a.recycle () в TypedArray отсутствует
Леос Литерак
5

Вы можете сделать это программно с помощью приведенного ниже фрагмента. Это дает белые буквы на черном фоне:

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

Параметры метода: радиус, dx, dy, цвет. Вы можете изменить их под свои нужды.

Надеюсь, я помогу кому-то, кто создает TextView программно и не имеет его внутри xml.

Приветствую сообщество stackOverflow!

Фермер
источник
2

Я создал библиотеку на основе ответа Нумана Ханифа с некоторыми дополнениями. Например, исправление ошибки, которая вызывала косвенный бесконечный цикл при вызовах View.invalidate ().

OTOH, библиотека также поддерживает выделенный текст в виджетах EditText, поскольку это была моя настоящая цель, и для этого потребовалось немного больше работы, чем для TextView.

Вот ссылка на мою библиотеку: https://github.com/biomorgoth/android-outline-textview

Спасибо Nouman Hanif за первоначальную идею решения!

биоморгот
источник
2

Я хочу добавить решение, чтобы решить проблему с производительностью. Например, ответ @YGHM и некоторых других выполняет свою работу, но вызывает бесконечный вызов из- onDrawза setTextColorвызовов invalidate(). Поэтому, чтобы решить эту проблему, вам также необходимо переопределить invalidate()и добавить переменную, isDrawingкоторую вы установите true, когда она onDraw()выполняется и рисуется штрихом. invalidate вернется, если переменная есть true.

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

Ваш onDraw будет выглядеть так:

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

MagicTextView очень полезно сделать обводки шрифта, но в моем случае, это может вызвать ошибку , как эта этой ошибку , вызванной дублирование фоновых атрибутов , которые устанавливают на MagicTextView

поэтому вам нужно отредактировать attrs.xml и 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

Я нашел простой способ конструировать вид без наследования от TextView . Я написал простую библиотеку, в которой для выделения текста используется Android Spannable . Это решение дает возможность очертить только часть текста.

Я уже ответил на тот же вопрос ( ответ )

Класс:

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

Павел Сантаев
источник
1
он не поддерживает многострочный текст
user924
0

Итак, вы хотите обвести текстовое поле обводкой? К сожалению, нет простого способа сделать это с помощью стиля. Вам нужно будет создать другое представление и разместить текстовое представление поверх, сделав родительский вид (тот, над которым он находится) всего на несколько пикселей больше - это должно создать контур.

xil3
источник
Хм, это звучит как большая боль, чем того стоит. Все, что меня волнует, - это зеленый текст, читаемый на белом фоне (сейчас его трудно читать) img88.imageshack.us/i/devicez.png Красный выглядит нормально. Может быть, если я просто перейду на более темный зеленый, но я действительно хотел бы получить какой-то контур или что-то в этом роде
Фалмарри
Значит, вы пытаетесь очертить сам текст? Это все еще невозможно, если вы не создадите свой собственный TextView, но, вероятно, это больше работы, чем она того стоит. Наверное, проще сделать его темно-зеленым.
xil3 06
2
Одна небольшая просьба от человека, страдающего дальтонизмом на красный / зеленый: пожалуйста, подумайте о добавлении альтернативного представления той же самой информации о красном / зеленом, поскольку нам часто трудно увидеть темно-зеленый и темно-красный. Может быть, еще и стрелка вверх / вниз?
Steve Pomeroy
Это хороший аргумент, Стив. Я, наверное, добавлю это в будущем.
Falmarri 08
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.

введите описание изображения здесь

Quealegriamasalegre
источник