and*_*per 9 android textview spanned spannable
我正在尝试使用一个简单的SpannableStringon TextView,基于我发现的UnderDotSpan类(这里).
原始的UnderDotSpan只在文本本身下方放置一个特定大小和颜色的点(不重叠).我正在尝试的是首先正常使用它,然后使用自定义的drawable而不是dot.
与正常的跨度使用相反,这个只是没有显示任何东西.甚至没有文字.
以下是正常跨度的完成方式:
val text = "1"
val timeSpannable = SpannableString(text)
timeSpannable.setSpan(ForegroundColorSpan(0xff00ff00.toInt()), 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.setText(timeSpannable);
Run Code Online (Sandbox Code Playgroud)
它将在TextView中显示绿色"1".
但是当我尝试下一个spannable时,它(整个TextView内容:文本和点)根本不显示:
val text = "1"
val spannable = SpannableString(text)
spannable.setSpan(UnderDotSpan(this@MainActivity, 0xFF039BE5.toInt(), textView.currentTextColor),
0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.setText(spannable, TextView.BufferType.SPANNABLE)
// this also didn't work: textView.setText(spannable)
Run Code Online (Sandbox Code Playgroud)
奇怪的是,在我使用的一个项目中,它在RecyclerView中运行良好,而在另一个项目中,它没有.
这是UnderDotSpan的代码:
class UnderDotSpan(private val mDotSize: Float, private val mDotColor: Int, private val mTextColor: Int) : ReplacementSpan() {
companion object {
@JvmStatic
private val DEFAULT_DOT_SIZE_IN_DP = 4
}
constructor(context: Context, dotColor: Int, textColor: Int) : this(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_DOT_SIZE_IN_DP.toFloat(), context.resources.displayMetrics), dotColor, textColor) {}
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?) = Math.round(paint.measureText(text, start, end))
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
if (TextUtils.isEmpty(text)) {
return
}
val textSize = paint.measureText(text, start, end)
paint.color = mDotColor
canvas.drawCircle(x + textSize / 2, bottom + mDotSize, mDotSize / 2, paint)
paint.color = mTextColor
canvas.drawText(text, start, end, x, y.toFloat(), paint)
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,TextView没有任何特殊属性,但无论如何我都会显示它:
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context="com.example.user.myapplication.MainActivity">
<TextView android:id="@+id/textView"
android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
Run Code Online (Sandbox Code Playgroud)
我试图从其他span类扩展,并尝试以其他方式将文本设置为TextView.
我还尝试过基于UnderDotSpan类的其他span类.例:
class UnderDrawableSpan(val drawable: Drawable, val drawableWidth: Int = drawable.intrinsicWidth, val drawableHeight: Int = drawable.intrinsicHeight, val margin: Int = 0) : ReplacementSpan() {
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int = Math.round(paint.measureText(text, start, end))
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
if (TextUtils.isEmpty(text))
return
val textSize = paint.measureText(text, start, end)
canvas.drawText(text, start, end, x, y.toFloat(), paint)
canvas.save()
canvas.translate(x + textSize / 2f - drawableWidth / 2f, y.toFloat() + margin)
if (drawableWidth != 0 && drawableHeight != 0)
drawable.setBounds(0, 0, drawableWidth, drawableHeight)
drawable.draw(canvas)
canvas.restore()
}
}
Run Code Online (Sandbox Code Playgroud)
在调试时,我发现draw函数甚至没有被调用,而getSize被调用(并返回> 0值).
为什么不能在TextView上显示跨度?
我使用它的方式有什么问题?
我该如何修复它并使用此跨度?
怎么会在其他更复杂的情况下起作用?
基本问题是没有设置高度ReplacementSpan.正如以下来源所述ReplacementSpan:
如果跨度覆盖整个文本,并且未设置高度,则不会为span调用draw(Canvas,CharSequence,int,int,float,int,int,int,Paint)}.
这是Archit Sureja发布的重复内容.在我的原始帖子中,我更新了ReplacementSpanin 的高度,getSize()但我现在实现了LineHeightSpan.WithDensity接口来做同样的事情.(感谢vovahost 在这里获取此信息.)
但是,您提出的其他问题需要解决.
您提供的项目引发的问题是该点不适合TextView它必须驻留的范围.你看到的是点的截断.如果点的大小超过文本宽度或高度,该怎么办?
首先,关于高度,chooseHeight()界面方法通过将点的大小添加到字体的有效高度来LineHeightSpan.WithDensity调整被认为是TextView字体底部的内容.为此,点的高度将添加到字体的底部:
fontMetricsInt.bottom = fm.bottom + mDotSize.toInt();
Run Code Online (Sandbox Code Playgroud)
(这是对这个使用了TextView填充的答案的最后一次迭代的改变.由于这个改变,类TextView不再需要了UnderDotSpan.虽然我已经添加了TextView,但它并不是真的需要.)
最后一个问题是,如果点宽于文本,则点在开始和结束处被截断.clipToPadding="false"在这里不起作用,因为点被截断不是因为它被剪裁到填充,而是因为它被剪切到我们所说的文本宽度所在getSize().为了解决这个问题,我修改了getSize()方法以检测点何时比文本测量宽,并增加返回值以匹配点的宽度.调用的新值mStartShim是必须应用于文本绘图的数量和用于使事物适合的点.
最后一个问题是点的中心是文本底部下方点的半径而不是直径,因此绘制点的代码更改draw()为:
canvas.drawCircle(x + textSize / 2, bottom.toFloat(), mDotSize / 2, paint)
Run Code Online (Sandbox Code Playgroud)
(我也改变了代码来进行Canvas翻译而不是添加偏移量.效果是一样的.)
结果如下:
activity_main.xml中
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:background="@android:color/white"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Run Code Online (Sandbox Code Playgroud)
MainActivity.java
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text = "1"
val spannable = SpannableString(text)
spannable.setSpan(UnderDotSpan(this@MainActivity, 0xFF039BE5.toInt(), textView.currentTextColor),
0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.setText(spannable, TextView.BufferType.SPANNABLE)
}
}
Run Code Online (Sandbox Code Playgroud)
UnderDotSpan.kt
// From the original UnderDotSpan: Also implement the LineHeightSpan.WithDensity interface to
// compute the height of our "dotted" font.
class UnderDotSpan(private val mDotSize: Float, private val mDotColor: Int, private val mTextColor: Int) : ReplacementSpan(), LineHeightSpan.WithDensity {
companion object {
@JvmStatic
private val DEFAULT_DOT_SIZE_IN_DP = 16
}
// Additional horizontal space to the start, if needed, to fit the dot
var mStartShim = 0;
constructor(context: Context, dotColor: Int, textColor: Int)
: this(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_DOT_SIZE_IN_DP.toFloat(),
context.resources.displayMetrics), dotColor, textColor)
// ReplacementSpan override to determine the size (length) of the text.
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
val baseTextWidth = paint.measureText(text, start, end)
// If the width of the text is less than the width of our dot, increase the text width
// to match the dot's width; otherwise, just return the width of the text.
mStartShim = if (baseTextWidth < mDotSize) ((mDotSize - baseTextWidth) / 2).toInt() else 0
return Math.round(baseTextWidth + mStartShim * 2)
}
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int,
y: Int, bottom: Int, paint: Paint) {
if (TextUtils.isEmpty(text)) {
return
}
val textSize = paint.measureText(text, start, end)
paint.color = mDotColor
canvas.save()
// Draw the circle in the horizontal center and under the text. Add in the
// offset (mStartShim) if we had to increase the length of the text to accommodate our dot.
canvas.translate(mStartShim.toFloat(), -mDotSize / 2)
// Draw a circle, but this could be any other shape or drawable. It just has
// to fit into the allotted space which is the size of the dot.
canvas.drawCircle(x + textSize / 2, bottom.toFloat(), mDotSize / 2, paint)
paint.color = mTextColor
// Keep the starting shim, but reset the y-translation to write the text.
canvas.translate(0f, mDotSize / 2)
canvas.drawText(text, start, end, x, y.toFloat(), paint)
canvas.restore()
}
// LineHeightSpan.WithDensity override to determine the height of the font with the dot.
override fun chooseHeight(charSequence: CharSequence, i: Int, i1: Int, i2: Int, i3: Int,
fontMetricsInt: Paint.FontMetricsInt, textPaint: TextPaint) {
val fm = textPaint.fontMetricsInt
fontMetricsInt.top = fm.top
fontMetricsInt.ascent = fm.ascent
fontMetricsInt.descent = fm.descent
// Our "dotted" font now must accommodate the size of the dot, so change the bottom of the
// font to accommodate the dot.
fontMetricsInt.bottom = fm.bottom + mDotSize.toInt();
fontMetricsInt.leading = fm.leading
}
// LineHeightSpan.WithDensity override that is needed to satisfy the interface but not called.
override fun chooseHeight(charSequence: CharSequence, i: Int, i1: Int, i2: Int, i3: Int,
fontMetricsInt: Paint.FontMetricsInt) {
}
}
Run Code Online (Sandbox Code Playgroud)
对于在文本下放置一个小的drawable的更一般情况,以下类是有效的,并且基于UnderDotSpan:
UnderDrawableSpan.java
public class UnderDrawableSpan extends ReplacementSpan implements LineHeightSpan.WithDensity {
final private Drawable mDrawable;
final private int mDrawableWidth;
final private int mDrawableHeight;
final private int mMargin;
// How much we need to jog the text to line up with a larger-than-text-width drawable.
private int mStartShim = 0;
UnderDrawableSpan(Context context, Drawable drawable, int drawableWidth, int drawableHeight,
int margin) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mDrawable = drawable;
mDrawableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(float) drawableWidth, metrics);
mDrawableHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(float) drawableHeight, metrics);
mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(float) margin, metrics);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
int bottom, @NonNull Paint paint) {
if (TextUtils.isEmpty(text)) {
return;
}
float textWidth = paint.measureText(text, start, end);
float offset = mStartShim + x + (textWidth - mDrawableWidth) / 2;
mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
canvas.save();
canvas.translate(offset, bottom - mDrawableHeight);
mDrawable.draw(canvas);
canvas.restore();
canvas.save();
canvas.translate(mStartShim, 0);
canvas.drawText(text, start, end, x, y, paint);
canvas.restore();
}
// ReplacementSpan override to determine the size (length) of the text.
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
float baseTextWidth = paint.measureText(text, start, end);
// If the width of the text is less than the width of our drawable, increase the text width
// to match the drawable's width; otherwise, just return the width of the text.
mStartShim = (baseTextWidth < mDrawableWidth) ? (int) (mDrawableWidth - baseTextWidth) / 2 : 0;
return Math.round(baseTextWidth + mStartShim * 2);
}
// LineHeightSpan.WithDensity override to determine the height of the font with the dot.
@Override
public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3,
Paint.FontMetricsInt fontMetricsInt, TextPaint textPaint) {
Paint.FontMetricsInt fm = textPaint.getFontMetricsInt();
fontMetricsInt.top = fm.top;
fontMetricsInt.ascent = fm.ascent;
fontMetricsInt.descent = fm.descent;
// Our font now must accommodate the size of the drawable, so change the bottom of the
// font to accommodate the drawable.
fontMetricsInt.bottom = fm.bottom + mDrawableHeight + mMargin;
fontMetricsInt.leading = fm.leading;
}
// Required but not used.
@Override
public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3,
Paint.FontMetricsInt fontMetricsInt) {
}
}
Run Code Online (Sandbox Code Playgroud)
使用以下可绘制的XML UnderDrawableSpan会产生以下结果:(drawable的宽度和高度设置为12dp.文本的字体大小为24sp.)
gradient_drawable.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="4dp"
android:height="4dp" />
<gradient
android:type="radial"
android:gradientRadius="60%p"
android:endColor="#e96507"
android:startColor="#ece6e1" />
</shape>
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
892 次 |
| 最近记录: |