Java 加上“不透明”;影子“;(大纲)到Android文本视图

Java 加上“不透明”;影子“;(大纲)到Android文本视图,java,android,textview,drawing,mapsforge,Java,Android,Textview,Drawing,Mapsforge,我有一个我想添加一个阴影的。它应该看起来像(100%不透明): 但看起来是这样的: 您可以看到当前阴影模糊并逐渐消失。我想要一个坚实、不透明的阴影。但是怎么做呢 我目前的代码是: <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/speedTextView" android:text="25 km/h"

我有一个我想添加一个阴影的。它应该看起来像(100%不透明):

但看起来是这样的:

您可以看到当前阴影模糊并逐渐消失。我想要一个坚实、不透明的阴影。但是怎么做呢

我目前的代码是:

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

我尝试了其他帖子中的所有技巧、技巧和窍门,比如,和

它们没有一个工作得那么好或者看起来那么好

现在,这就是您真正做到这一点的方式(可在应用程序的源代码中找到): 使用FrameLayout(其特点是将组件放置在彼此上方)并将两个文本视图放在同一位置

MainActivity.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:background="#445566">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:layout_weight="1">

        <TextView
            android:id="@+id/textViewShadowId"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:textSize="36sp"
            android:text="123 ABC" 
            android:textColor="#ffffff" />

        <TextView
            android:id="@+id/textViewId"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:textSize="36sp"
            android:text="123 ABC"
            android:textColor="#000000" />
    </FrameLayout>

</LinearLayout>
结果如下所示:


我想我可能会提供一种替代重叠的
TextView
s解决方案。此解决方案实现了一个自定义的
TextView
子类,该子类操作其
TextPaint
对象的属性,首先绘制轮廓,然后在其上绘制文本

使用此方法,您一次只需要处理一个
视图
,因此在运行时更改某些内容不需要调用两个单独的
TextView
s。这也应该使利用
TextView
的其他细节(如复合绘图)变得更容易,并使所有内容保持方形,没有冗余设置

反射用于避免调用
TextView
setTextColor()
方法,该方法会使
视图无效,并会导致无限的绘制循环,我相信这很可能是不适合您的原因。直接在
Paint
对象上设置颜色不起作用,因为
TextView
在其
onDraw()
方法中处理颜色,因此反射

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];
            }
        };
    }
}
通过自定义属性,可以在布局XML中设置所有内容。请注意附加的XML名称空间,这里名为
app
,在根
LinearLayout
元素上指定

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

结果是:


注:

  • 如果您使用的是支持库,则您的
    OutlineTextView
    类应该扩展
    AppCompatTextView
    ,以确保在所有版本上正确处理着色和其他内容

  • 如果与文本大小相比,轮廓宽度相对较大,则可能需要在
    视图
    上设置额外的填充,以将内容保持在其范围内,尤其是在包装宽度和/或高度时。这也可能与叠加的
    TextView
    s有关

  • 由于笔划样式,相对较大的轮廓宽度也可能导致某些字符(如“A”和“2”)出现不希望出现的锐角效果。重叠的
    TextView
    s也会出现这种情况

  • 只需将超类更改为
    EditText
    ,并在三参数构造函数链调用中传递
    android.R.attr.editTextStyle
    而不是
    android.R.attr.textViewStyle
    ,就可以轻松地将此类转换为等价的
    EditText
    。对于支持库,超类将是
    AppCompatEditText
    ,构造函数参数是
    R.attr.editTextStyle

  • 只是为了好玩:我想指出的是,你可以使用半透明的文字和/或轮廓颜色,并使用填充/笔划/填充和笔划样式,获得一些非常漂亮的效果。当然,这也可以通过叠加的
    TextView
    s解决方案实现

  • 从API级别28(Pie)开始,有一些特定的方法可以访问SDK中通常不可访问的成员,包括反射。尽管如此,令人惊讶的是,这个解决方案仍然有效,至少在可用的Pie模拟器上,对于本机
    TextView
    和支持
    appcompatitextView
    。我将更新,如果在未来的变化


我遇到了同样的问题,在
onDraw
中调用
setTextColor
导致无限绘制循环。我想使我的自定义文本视图在渲染文本时具有与大纲颜色不同的填充颜色。这就是为什么我在
onDraw
中多次调用
setTextColor

我使用
OutlineSpan
找到了另一种解决方案,请参见。这比使用多个文本视图或使用反射使布局层次结构复杂化要好,并且只需要很少的更改。有关更多详细信息,请参见github页面。 范例


到目前为止,提供的解决方案中可能存在的非重复项对我有效。我一直在尝试…您想要的解决方案是:设置笔划的绘制样式,然后设置笔划宽度。您图像中的白色轮廓就是笔划。我有一个MapsForge活动,它有自己的绘制方法。我没有让它在那里工作。我如何更改它以扩展编辑文本。我尝试了editText上的侦听器,但没有找到。将其更改为
扩展editText
(显然),然后将三参数构造函数链调用中的第三个参数更改为
android.R.attr.editTextStyle
。我不久前测试过它(并打算将其作为注释添加,但忘记了),我很确定这就是它的全部内容。@MuhammadBabar我不确定您的确切意思,因为“使用outlineTextView对象显式初始化它”。@MuhammadBabar啊,现在我明白您的意思了。:-)酷。很高兴你让它工作了。顺便说一句,你可能想在一两周后回来看看。我认为Q中关于反射的最新限制将打破这个现状,但我还没有机会测试。如果有,我会更新这个答案。只是一个提醒。干杯@Skyyy抱歉:。两个文本视图,太糟糕了solution@user924我真的不认为仅仅因为它有两个以上的观点就不好。这是最可靠、最简单的解决方案。这实际上是一个可行的解决方案。。但是事情
<resources>
    <declare-styleable name="OutlineTextView" >
        <attr name="outlineColor" format="color" />
        <attr name="outlineWidth" format="dimension" />
    </declare-styleable>
</resources>
<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>
val outlineSpan = OutlineSpan(
    strokeColor = Color.RED,
    strokeWidth = 4F
)
val text = "Outlined text"
val spannable = SpannableString(text)
spannable.setSpan(outlineSpan, 0, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

// Set text of TextView
binding.outlinedText.text = spannable