添加不透明的“阴影” (概述)到Android TextView [英] Add opaque "shadow" (outline) to Android TextView

查看:188
本文介绍了添加不透明的“阴影” (概述)到Android TextView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有



但它看起来像这样:



< a href =https://i.stack.imgur.com/vdCA4.png =noreferrer>



你可以看到当前的阴影模糊不清。我想要一个坚实,不透明的阴影。但是如何?



我目前的代码是:

 < TextView 
android:layout_width =wrap_content
android:layout_height =wrap_content
android:id =@ + id / speedTextView
android:text =25 km / h

android:textSize =24sp
android:textStyle =bold
android:textColor =#000000
android:shadowColor =#ffffff
android:shadowDx =0
android:shadowDy =0
android:shadowRadius =6
/>


解决方案

我想我可能会提供替代覆盖的 TextView 的解决方案。此解决方案实现了一个自定义 TextView 子类,它操纵其 TextPaint 对象的属性以首先绘制轮廓,然后绘制文本在它之上。



使用这个,你一次只需要处理一个查看,所以改变了一些东西在运行时不需要在两个单独的 TextView 上调用。这也应该更容易利用 TextView 的其他细节 - 比如复合drawables - 并保持一切正方形,没有多余的设置。



反射用于避免调用 TextView setTextColor()方法,该方法使<$ c无效$ c>查看,并会导致无限的绘制循环,我相信,这很可能是为什么






注意:




  • 目前我的测试平台非常有限,但仔细查看源版本,我认为这应该可以在API 8(Froyo)上一直工作,向上通过至少API 23(Marshmallow)。


  • 如果轮廓宽度与文本大小相比相对较大,则可能需要在文本大小上设置其他填充。 查看以保持在其范围内,特别是在包裹宽度和/或高度时。这也是覆盖 TextView 的问题。


  • 相对较大的轮廓宽度也可以由于笔画风格,会对某些字符产生不良的锐角效果 - 如A和2。叠加的 TextView 也会出现这种情况。


  • 只是为了好玩:我会指出您可以使用半透明颜色为文本和/或轮廓获得一些非常漂亮的效果,并使用填充/描边/填充和描边样式。当然,这也可以使用重叠的 TextView 的解决方案。



I have a TextView in my Activity to which I want to add a shadow. It is supposed to look like in OsmAnd (100% opaque):

But it looks like this:

You can see that the current shadow is blurred and fades away. I want a solid, opaque shadow. But how?

My current code is:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/speedTextView"
    android:text="25 km/h"

    android:textSize="24sp"
    android:textStyle="bold"
    android:textColor="#000000"
    android:shadowColor="#ffffff"
    android:shadowDx="0"
    android:shadowDy="0"
    android:shadowRadius="6"
/>

解决方案

I thought I might offer an alternative to the overlayed TextViews solution. This solution implements a custom TextView subclass which manipulates its TextPaint object's properties to first draw the outline, and then draw the text on top of it.

Using this, you need only deal with one View at a time, so changing something at runtime won't require calls on two separate TextViews. This should also make it easier to utilize other niceties of TextView - like compound drawables - and keep everything square, without redundant settings.

Reflection is used to avoid calling TextView's setTextColor() method, which invalidates the View, and would cause an infinite draw loop, which, I believe, is most likely why solutions like this didn't work for you. Setting the color directly on the Paint object doesn't work, due to how TextView handles that in its onDraw() method, hence the reflection.

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.view.View.BaseSavedState;
import android.widget.TextView;
import java.lang.reflect.Field;


public class OutlineTextView extends TextView {
    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];
            }
        };
    }
}

If using the custom XML attributes, the following needs to be in your <resources>, which you can do by just sticking this file in your res/values/ folder, or adding to the one already there. If you don't wish to use the custom attributes, you should remove the relevant attribute processing from the View's third constructor.

attrs.xml

<resources>
    <declare-styleable name="OutlineTextView" >
        <attr name="outlineColor" format="color" />
        <attr name="outlineWidth" format="dimension" />
    </declare-styleable>
</resources>

With the custom attributes, everything can be setup in the layout XML. Note the additional XML namespace, here named app, and specified on the root LinearLayout element.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#445566">

    <com.example.testapp.OutlineTextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="123 ABC"
        android:textSize="36sp"
        android:textColor="#000000"
        app:outlineColor="#ffffff"
        app:outlineWidth="2px" />

</LinearLayout>

The results:


Notes:

  • I have a very limited testbed at the moment, but looking through the source versions, I think this should work from all the way back at API 8 (Froyo), up through at least API 23 (Marshmallow).

  • If the outline width is relatively large compared to the text size, it might be necessary to set additional padding on the View to keep things within their bounds, especially if wrapping the width and/or height. This would be a concern with the overlayed TextViews, too.

  • Relatively large outline widths can also result in undesirable sharp corner effects on certain characters - like "A" and "2" - due to the stroke style. This would also occur with the overlayed TextViews.

  • Just for fun: I would point out that you can get some pretty nifty effects using translucent colors for the text and/or outline, and playing with the fill/stroke/fill-and-stroke styles. This, of course, would be possible with the overlayed TextViews solution, as well.

这篇关于添加不透明的“阴影” (概述)到Android TextView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆