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

时间:2016-08-23 16:31:30

标签: java android textview drawing mapsforge

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

what I want

但它看起来像这样:

What I have

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

我目前的代码是:

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

3 个答案:

答案 0 :(得分:12)

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

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

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

onDraw()

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

View

attrs.xml

使用自定义属性,可以在布局XML中设置所有内容。请注意附加的XML命名空间,此处命名为<resources> <declare-styleable name="OutlineTextView" > <attr name="outlineColor" format="color" /> <attr name="outlineWidth" format="dimension" /> </declare-styleable> </resources> ,并在根app元素上指定。

LinearLayout

结果:

solutions like this

注意:

  • 如果您正在使用支持库,则<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,以确保在所有版本上正确处理着色和诸如此类的内容。

    < / LI>
  • 如果轮廓宽度与文本大小相比较大,则可能需要在AppCompatTextView上设置额外的填充以使事物保持在其范围内,尤其是在包裹宽度和/或高度时。这也是覆盖View s的问题。

  • 由于笔触样式,相对较大的轮廓宽度也会对某些字符(如“A”和“2”)产生不良的锐角效果。叠加的TextView s。

  • 也会出现这种情况
  • 只需将超类更改为TextView,并将EditText代替EditText,即可轻松将此类转换为android.R.attr.editTextStyle等效类三参数构造函数链调用。对于支持库,超类将为android.R.attr.textViewStyle,构造函数参数为AppCompatEditText

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

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

答案 1 :(得分:5)

我在hereherehere等其他帖子中尝试了所有黑客,提示和技巧。

它们都没有那么好或看起来那么好。

现在这就是你真正做到的事情(在OsmAnd app的来源中找到):

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

在活动的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);
    }
}

结果如下:

result screenshot

答案 2 :(得分:1)

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

我找到了使用OutlineSpan参见https://github.com/santaevpavel/OutlineSpan的替代解决方案。这比使布局层次结构复杂化多个TextView或使用反射效果更好,并且所需的更改最少。有关更多详细信息,请参见github页面。 例子

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 
相关问题