向Android TextView添加不透明的"阴影"(轮廓)

jue*_*n d 13 java android drawing textview mapsforge

我有一个TextView在我的活动中,我想添加一个阴影.它应该看起来像OsmAnd(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"
/>
Run Code Online (Sandbox Code Playgroud)

Mik*_* M. 16

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

使用它,您只需要一次处理一个View,因此在运行时更改某些内容不需要在两个单独的TextViews 上进行调用.这也应该更容易利用其他细节TextView- 比如复合绘图 - 并保持一切正方形,没有多余的设置.

反思是用来避免调用TextViewsetTextColor()方法,该方法将失效View,并会导致无限循环抽奖,其中,我认为,最有可能是为什么这样的解决方案,为你没有工作.直接在Paint对象上设置颜色不起作用,因为TextViewonDraw()方法中的句柄因此是反射.

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];
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

如果使用自定义XML属性,则需要使用以下内容<resources>,只需将此文件粘贴到文件res/values/夹中,或添加到已存在的文件中即可.如果您不想使用自定义属性,则应从View第三个构造函数中删除相关的属性处理.

attrs.xml

<resources>
    <declare-styleable name="OutlineTextView" >
        <attr name="outlineColor" format="color" />
        <attr name="outlineWidth" format="dimension" />
    </declare-styleable>
</resources>
Run Code Online (Sandbox Code Playgroud)

使用自定义属性,可以在布局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>
Run Code Online (Sandbox Code Playgroud)

结果:

截图


笔记:

  • 如果您正在使用支持库,那么您的OutlineTextView类应该扩展AppCompatTextView,以确保在所有版本上适当地处理着色和诸如此类的东西.

  • 如果轮廓宽度与文本大小相比相对较大,则可能需要在其上设置额外的填充View以使事物保持在其边界内,尤其是在包裹宽度和/或高度时.这也是覆盖TextViews 的关注点.

  • 由于笔划样式,相对较大的轮廓宽度也会对某些字符(如"A"和"2")产生不良的锐角效果.叠加的TextViews 也会发生这种情况.

  • 这个类可以很容易地转换为EditText等价,只需将超类更改为EditText,并在三参数构造函数链调用android.R.attr.editTextStyle中代替android.R.attr.textViewStyle.对于支持库,超类将是AppCompatEditText构造函数参数R.attr.editTextStyle.

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

  • 从API级别28(Pie)开始,非SDK接口存在某些限制,包括访问SDK中通常无法访问的成员的反射.尽管如此,令人惊讶的是,对于原生TextView和支持,这个解决方案至少在可用的Pie仿真器上仍然有效AppCompatTextView.如果将来发生变化,我会更新.

  • @Skyyy 抱歉:[下面的答案](/sf/answers/3967391361/)。 (2认同)

jue*_*n d 7

我在这里,这里这里尝试了其他帖子中的所有黑客,技巧和窍门.

他们都没有那么好或看起来那么好.

现在这就是你真正做到的事情(在OsmAnd应用程序的源代码中找到):

您使用FrameLayout(具有将其组件相互叠加的特性)并将2个TextView放在同一位置.

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>
Run Code Online (Sandbox Code Playgroud)

onCreate您的活动方法中,您可以设置阴影TextView的笔触宽度,并将其从FILL更改为STROKE:

import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //here comes the magic
        TextView textViewShadow = (TextView) findViewById(R.id.textViewShadowId);
        textViewShadow.getPaint().setStrokeWidth(5);
        textViewShadow.getPaint().setStyle(Paint.Style.STROKE);
    }
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

结果截图