我的TextView
中有一个Activity,我想添加一个阴影。它应该看起来像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"
/>
答案 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
结果:
注意:
如果您正在使用支持库,则<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
上设置额外的填充以使事物保持在其范围内,尤其是在包裹宽度和/或高度时。这也是覆盖View
s的问题。
由于笔触样式,相对较大的轮廓宽度也会对某些字符(如“A”和“2”)产生不良的锐角效果。叠加的TextView
s。
只需将超类更改为TextView
,并将EditText
代替EditText
,即可轻松将此类转换为android.R.attr.editTextStyle
等效类三参数构造函数链调用。对于支持库,超类将为android.R.attr.textViewStyle
,构造函数参数为AppCompatEditText
。
只是为了好玩:我会指出你可以使用半透明的颜色为文本和/或轮廓获得一些非常漂亮的效果,并使用填充/描边/填充和描边样式。当然,这也可以通过叠加的R.attr.editTextStyle
解决方案实现。
从API级别28(Pie)开始,存在某些,包括反映访问SDK中通常无法访问的成员。尽管如此,令人惊讶的是,对于原生
TextView
和支持TextView
,此解决方案至少在可用的Pie模拟器上仍然有效。如果将来发生变化,我会更新。
答案 1 :(得分:5)
我在here,here和here等其他帖子中尝试了所有黑客,提示和技巧。
它们都没有那么好或看起来那么好。
使用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);
}
}
结果如下:
答案 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