TextView
TextView类继承自View类,TextView控件的功能是向用户显示文本的内容,但不允许编辑,而其子类EditView允许用户进行编辑。
以下为TextView常用属性及对应方法说明
文字透明度的设置: android:alpha="0.5" 范围为0-1 0代表完全透明 1代表完全显示
在文本左边放入图片: android:drawableLeft="@drawable/ic_launcher"
//跑马灯效果
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever" 滚动重复次数限制
android:focusable="true" //获取聚焦
android:focusableInTouchMode="true" //触摸方式的获取焦点
<!-- 阴影显示文字(带影子显示) 下面属性分别代表: 字体颜色, X方向阴影倾斜度,Y方向..,阴影半径--> <TextView android:shadowColor="#ff0000" android:shadowDx="1" android:shadowDy="1" android:shadowRadius="3.0"/>
自定义TextView实现跑马灯效果: 一出生就能获取焦点(自欺欺人的感觉)
使用:
常用属性:
属性名称 |
描述 |
android:autoLink |
设置是否当文本为URL链接/email/电话号码/map时,文本显示为可点击的链接。可选值(none/web/email/phone/map/all) |
android:autoText |
如果设置,将自动执行输入值的拼写纠正。此处无效果,在显示输入法并输入的时候起作用。 |
android:bufferType |
指定getText()方式取得的文本类别。选项editable 类似于StringBuilder可追加字符, 也就是说getText后可调用append方法设置文本内容。spannable 则可在给定的字符区域使用样式,参见这里1、这里2。 |
android:capitalize |
设置英文字母大写类型。此处无效果,需要弹出输入法才能看得到,参见EditText此属性说明。 |
android:cursorVisible |
设定光标为显示/隐藏,默认显示。 |
android:digits |
设置允许输入哪些字符。如“1234567890.+-*/%\n()” |
android:drawableBottom |
在text的下方输出一个drawable,如图片。如果指定一个颜色的话会把text的背景设为该颜色,并且同时和background使用时覆盖后者。 |
android:drawableLeft |
在text的左边输出一个drawable,如图片。 |
android:drawablePadding |
设置text与drawable(图片)的间隔,与drawableLeft、drawableRight、drawableTop、drawableBottom一起使用,可设置为负数,单独使用没有效果。 |
android:drawableRight |
在text的右边输出一个drawable,如图片。 |
android:drawableTop |
在text的正上方输出一个drawable,如图片。 |
android:editable |
设置是否可编辑。这里无效果,参见EditView。 |
android:editorExtras |
设置文本的额外的输入数据。在EditView再讨论。 |
android:ellipsize |
设置当文字过长时,该控件该如何显示。有如下值设置:”start”—–省略号显示在开头;”end”——省略号显示在结尾;”middle”—-省略号显示在中间;”marquee” ——以跑马灯的方式显示(动画横向移动) |
android:freezesText |
设置保存文本的内容以及光标的位置。参见:这里。 |
android:gravity |
设置文本位置,如设置成“center”,文本将居中显示。 |
android:hint |
Text为空时显示的文字提示信息,可通过textColorHint设置提示信息的颜色。此属性在EditView中使用,但是这里也可以用。 |
android:imeOptions |
附加功能,设置右下角IME动作与编辑框相关的动作,如actionDone右下角将显示一个“完成”,而不设置默认是一个回车符号。这个在EditText中再详细说明,此处无用。 |
android:imeActionId |
设置IME动作ID。在EditText再做说明 |
android:imeActionLabel |
设置IME动作标签。在EditText再做说明。 |
android:includeFontPadding |
设置文本是否包含顶部和底部额外空白,默认为true。 |
android:inputMethod |
为文本指定输入法,需要完全限定名(完整的包名)。例如:com.google.android.inputmethod.pinyin,但是这里报错找不到。 |
android:inputType |
设置文本的类型,用于帮助输入法显示合适的键盘类型。在EditText中再详细说明,这里无效果。 |
android:linksClickable |
设置链接是否点击连接,即使设置了autoLink。 |
android:marqueeRepeatLimit |
在ellipsize指定marquee的情况下,设置重复滚动的次数,当设置为 |
android:ems |
设置TextView的宽度为N个字符的宽度。这里测试为一个汉字字符宽度,如图: |
android:maxEms |
设置TextView的宽度为最长为N个字符的宽度。与ems同时使用时覆盖ems选项。 |
android:minEms |
设置TextView的宽度为最短为N个字符的宽度。与ems同时使用时覆盖ems选项。 |
android:maxLength |
限制显示的文本长度,超出部分不显示。 |
android:lines |
设置文本的行数,设置两行就显示两行,即使第二行没有数据。 |
android:maxLines |
设置文本的最大显示行数,与width或者layout_width结合使用,超出部分自动换行,超出行数将不显示。 |
android:minLines |
设置文本的最小行数,与lines类似。 |
android:lineSpacingExtra |
设置行间距。 |
android:lineSpacingMultiplier |
设置行间距的倍数。如”1.2” |
android:numeric |
如果被设置,该TextView有一个数字输入法。此处无用,设置后唯一效果是TextView有点击效果,此属性在EditText将详细说明。 |
android:password |
以小点”.”显示文本 |
android:phoneNumber |
设置为电话号码的输入方式。 |
android:privateImeOptions |
设置输入法选项,此处无用,在EditText将进一步讨论。 |
android:scrollHorizontally |
设置文本超出TextView的宽度的情况下,是否出现横拉条。 |
android:selectAllOnFocus |
如果文本是可选择的,让他获取焦点而不是将光标移动为文本的开始位置或者末尾位置。EditText中设置后无效果。 |
android:shadowColor |
指定文本阴影的颜色,需要与shadowRadius一起使用。效果: |
android:shadowDx |
设置阴影横向坐标开始位置。 |
android:shadowDy |
设置阴影纵向坐标开始位置。 |
android:shadowRadius |
设置阴影的半径。设置为0.1就变成字体的颜色了,一般设置为3.0的效果比较好。 |
android:singleLine |
设置单行显示。如果和layout_width一起使用,当文本不能全部显示时,后面用“…”来表示。如android:text="test_ singleLine " android:singleLine="true" android:layout_width="20dp"将只显示“t…”。如果不设置singleLine或者设置为false,文本将自动换行 |
android:text |
设置显示文本. |
android:textAppearance |
设置文字外观。如“?android:attr/textAppearanceLargeInverse
”这里引用的是系统自带的一个外观,?表示系统是否有这种外观,否则使用默认的外观。可设置的值如下:textAppearanceButton/textAppearanceInverse/textAppearanceLarge/textAppearanceLargeInverse/textAppearanceMedium/textAppearanceMediumInverse/textAppearanceSmall/textAppearanceSmallInverse |
android:textColor |
设置文本颜色 |
android:textColorHighlight |
被选中文字的底色,默认为蓝色 |
android:textColorHint |
设置提示信息文字的颜色,默认为灰色。与hint一起使用。 |
android:textColorLink |
文字链接的颜色. |
android:textScaleX |
设置文字缩放,默认为1.0f。分别设置0.5f/1.0f/1.5f/2.0f效果如下:
|
android:textSize |
设置文字大小,推荐度量单位”sp”,如”15sp” |
android:textStyle |
设置字形[bold(粗体) 0, italic(斜体) 1, bolditalic(又粗又斜) 2] 可以设置一个或多个,用“|”隔开 |
android:typeface |
设置文本字体,必须是以下常量值之一:normal 0, sans 1, serif 2, monospace(等宽字体) 3] |
android:height |
设置文本区域的高度,支持度量单位:px(像素)/dp/sp/in/mm(毫米) |
android:maxHeight |
设置文本区域的最大高度 |
android:minHeight |
设置文本区域的最小高度 |
android:width |
设置文本区域的宽度,支持度量单位:px(像素)/dp/sp/in/mm(毫米),与layout_width的区别看这里。 |
android:maxWidth |
设置文本区域的最大宽度 |
android:minWidth |
设置文本区域的最小宽度 |
TextView设置个别字体样式
text = "<font color='#A7A7A7'>已完成</font>";
tv.setText(Html.fromHtml(text));
tvPrice.setText((Html.fromHtml("<font color= '#FF0000'>" +"¥" + "<big>"+"示例文字" +"</big></font> " +"起")));
如上,结合HTML可以调节textview中文字的不同颜色和不同大小,但是Android中只支持<font>标签的color和face标签,不支持size标签,所以文字的大小只能通过标签<big>或者<small>来相对调节,经过笔者测试,<big>标签可以嵌套使用,效果也是嵌套增长,例如“<big><big>我是示例文字</big></big>实现后的效果,比<big>我是示例文字</big>的表现效果要big了一点,同理,我认为<small>标签也是可以嵌套使用的。
富文本效果:
Android同一个TextView里响应多个区域点击事件
组合使用SpannableString:
fun setSpannableStringBuilder(textView: TextView) { textView.text = SpannableStringBuilder() .append(currentSpannableString("第一个", Color.parseColor("#ff0000"))) .append(currentSpannableString("第2个", Color.parseColor("#00ff00"))) .append(currentSpannableString( "第3个", Color.parseColor("#0000ff"), textSizeSp = 30f )) .append(currentSpannableString( "第4个", Color.parseColor("#00ffff"), textSizeDp = 30 )) .append(currentSpannableString( "第5个", Color.parseColor("#00ffff"), textSizeDp = 30, textClick = { Log.e("tag","点击了该文本") } )) //如果设置了点击事件,需要加上这个 textView.setMovementMethod(LinkMovementMethod.getInstance()) } /** * 设置颜色、大小、点击事件 */ fun currentSpannableString(text: String, textColor: Int = -1, textClick: (() -> Unit)? = null, textSizeDp: Int = -1, textSizeSp: Float = -1f): SpannableString { return SpannableString(text).apply { //颜色 setSpan(ForegroundColorSpan(textColor), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) //大小 if (textSizeSp >= 0) { setSpan(AbsoluteSizeSpan(sp2px(textSizeSp)), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } else if (textSizeDp >= 0) { setSpan(AbsoluteSizeSpan(textSizeDp, true), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } if (textClick != null) { //点击事件的 setSpan(object : ClickableSpan() { override fun onClick(widget: View) { textClick.invoke() } override fun updateDrawState(ds: TextPaint) { //super.updateDrawState(ds) //取消下划线 ds.color = textColor ds.bgColor = Color.TRANSPARENT } }, 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } } fun sp2px(spValue: Float): Int { val fontScale = Resources.getSystem().displayMetrics.scaledDensity return (spValue * fontScale + 0.5f).toInt() }
添加图片的时候需要图片居中(图片比文字小的情况):
public class MyCenterImageSpan extends ImageSpan { public MyCenterImageSpan(@NonNull Context context, int resourceId) { super(context, resourceId); } @Override public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) { Paint.FontMetricsInt fm = paint.getFontMetricsInt(); Drawable drawable = getDrawable(); int transY = (y + fm.descent + y + fm.ascent) / 2 - drawable.getBounds().bottom / 2; canvas.save(); canvas.translate(x, transY); drawable.draw(canvas); canvas.restore(); } }
当图片比文本高度大的时候,需要移动文本:ImageSpan图片和文字实现垂直居中
public class VerticalAlignImageSpan extends ImageSpan { public VerticalAlignImageSpan(Drawable d) { super(d); } public VerticalAlignImageSpan(Drawable d, int verticalAlignment) { super(d, verticalAlignment); } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { Drawable drawable = getDrawable(); Paint.FontMetrics fontMetrics = paint.getFontMetrics(); int transY = (int) ((y + fontMetrics.ascent + y + fontMetrics.descent) / 2 - (drawable.getBounds().bottom + drawable .getBounds().top) / 2); Log.d("VerticalAlignImageSpan", "transY-> " + transY); canvas.save(); canvas.translate(x, transY); drawable.draw(canvas); canvas.restore(); } }
当使用ClickableSpan,会出现一些问题:
解决方法:https://www.ctolib.com/topics-112400.html
Span总结很到位的文章:https://mp.weixin.qq.com/s/ji9MRaMhpVM711lDYL0LFw (包含图文、Span点击事件冲突)
Span图文居中显示
import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.graphics.drawable.Drawable import android.text.style.ImageSpan //图文居中显示 class ImageVerticalCenterSpan(drawable: Bitmap) : ImageSpan(drawable) { override fun getSize( paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt? ): Int { val drawable = drawable val rect = drawable.bounds fm?.run { val fmPaint = paint.fontMetricsInt val fontHeight = fmPaint.descent - fmPaint.ascent val drHeight = rect.bottom - rect.top val centerY = fmPaint.ascent + fontHeight / 2 ascent = centerY - drHeight / 2 top = ascent bottom = centerY + drHeight / 2 descent = bottom } return rect.right } override fun draw( canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint ) { val drawable = drawable canvas.save() val fmPaint = paint.fontMetricsInt val fontHeight = fmPaint.descent - fmPaint.ascent val centerY = y + fmPaint.descent - fontHeight / 2 val transY = centerY - (drawable.bounds.bottom - drawable.bounds.top) / 2 canvas.translate(x, transY.toFloat()) drawable.draw(canvas) canvas.restore() } }
TextView的ClickableSpan、OnClickListener、OnLongClickListener冲突的问题
https://blog.csdn.net/SonnyJack/article/details/88849870
//tv.movementMethod = LinkMovementClickMethod.getInstance() tv.setOnTouchListener(IMCustomTouchListener()) //自己处理touch事件,touch事件里 响应ClickableSpan
文字添加图片背景
import android.content.Context import android.graphics.* import android.graphics.drawable.Drawable import android.text.style.ReplacementSpan import android.util.TypedValue import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat /** * 文本添加图片背景 */ class ImageBackgroundSpan(private val context: Context, private val textColor: Int, @DrawableRes private val drawableRes: Int) : ReplacementSpan() { private val textSizeSp = 11f //sp private val padding = 3.dp // 左右各3dp间距 文本与图片之间的距离 private val paddingInternal = 10.dp //图片内部与文字的水平间距 private val paddingInternalVertical = 2.dp //图片内部与文字的垂直间距 private val drawable: Drawable? = ContextCompat.getDrawable(context, drawableRes) override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { // 设置临时文本大小 val originalTextSize = paint.textSize paint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSizeSp, context.resources.displayMetrics) // 设置宽度为文字宽度加6dp val textWidth = paint.measureText(text, start, end) .toInt() + (padding * 2) + paddingInternal * 2 // 恢复原始文本大小 paint.textSize = originalTextSize return textWidth } override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { val originalColor = paint.color val originalTextSize = paint.textSize // 设置临时文本大小 paint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSizeSp, context.resources.displayMetrics) // 画圆角矩形背景 val textWidth: Float = paint.measureText(text, start, end) val rectWidth: Float = textWidth + paddingInternal * 2 val rectLeft = x + padding val rectRight = rectLeft + rectWidth val rectTop = top.toFloat() - paddingInternalVertical val rectBottom = bottom.toFloat() + paddingInternalVertical val rect = Rect(rectLeft.toInt(), rectTop.toInt(), rectRight.toInt(), rectBottom.toInt()) // 设置图片的大小和位置 drawable?.setBounds(rect) // 绘制图片背景 drawable?.draw(canvas) // 计算文字垂直居中的位置 val textHeight = paint.descent() - paint.ascent() val textOffset = (rect.height() - textHeight) / 2 - paint.ascent() // 绘制文字 paint.color = textColor canvas.drawText(text, start, end, x + paddingInternal + padding, rectTop + textOffset, paint) // 恢复原始paint设置 paint.color = originalColor paint.textSize = originalTextSize } }
使用与效果
binding.tvImageIdent.text = SpannableStringBuilder() .append(fissionActivityInfoBean.winInfo?.imageIdent) .append(quickBuildSpan(mActivity?.getString(R.string.立即使用) ?: "", object : ClickableSpan(){ override fun onClick(widget: View) { } override fun updateDrawState(ds: TextPaint) { super.updateDrawState(ds) ds.isUnderlineText = false } },ImageBackgroundSpan(mActivity!!, Color.BLACK, R.drawable.ic_fission_btn_bg_end))) .append("尾巴数据")
如何更简洁地实现富文本 Span (包含Span api)
https://juejin.cn/post/7126510374114820132#heading-5
半个汉子与一个汉子的宽度占位
Android布局中的空格以及占一个汉字宽度的空格的实现 (半个汉子宽度:  一个汉子宽度:\u3000)
半个汉子宽度需要写xml
<string name="spaces"> </string>
TextView显示Html代码:
import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.Html; import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.transition.Transition; import com.yitkeji.zhengeili.utils.BaseTools; /** * TextView显示Html中的文本与图片,图片自适应 * * 使用:tv.setText(Html.fromHtml(text,new MyImageGetter(tvTitle.getContext(),tvContent),null)); */ public class MyImageGetter implements Html.ImageGetter { private final int width,height; private URLDrawable urlDrawable = null; private TextView textView; private Context context; public MyImageGetter(Context context, TextView textView) { this.textView = textView; this.context = context; width = BaseTools.getWindowWidth(context); height = BaseTools.getWindowHeigh(context); } @Override public Drawable getDrawable(final String source) { urlDrawable = new URLDrawable(); Glide.with(context).asBitmap().load(source).apply(new RequestOptions()).into(new SimpleTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { double h = width * 1.0 / resource.getWidth() * resource.getHeight(); //修改图片的宽高,使得宽度为屏幕宽度,高度自适应 urlDrawable.bitmap = setImgSize(resource,width, (int) h); //设置边界值,不设置图片会覆盖在文字上 urlDrawable.setBounds(0, 0, width, (int) h); //更新TextView textView.setText(textView.getText()); } }); return urlDrawable; } /** * 改变bitmap的宽高 * @param bm * @param newWidth * @param newHeight * @return */ public Bitmap setImgSize(Bitmap bm, int newWidth ,int newHeight){ // 获得图片的宽高. int width = bm.getWidth(); int height = bm.getHeight(); // 计算缩放比例. float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; // 取得想要缩放的matrix参数. Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); // 得到新的图片. Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true); return newbm; } public class URLDrawable extends BitmapDrawable { public Bitmap bitmap; @Override public void draw(Canvas canvas) { super.draw(canvas); if (bitmap != null) { canvas.drawBitmap(bitmap, 0, 0, getPaint()); } } } }
金额递增与递减的变动动画:
import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.TextView; import java.math.BigDecimal; import java.text.DecimalFormat; /** * 数字增加动画的 TextView * // 设置前缀 mNumberAnimTextView.setPrefixString("¥"); // 设置后缀 mNumberAnimTextView.setPostfixString("%"); // 设置动画时长 mNumberAnimTextView.setDuration(2000); // 设置数字增加范围 mNumberAnimTextView.setNumberString("19.75", "99.75"); // 禁用动画 mNumberAnimTextView1.setEnableAnim(false); // 设置最终值,开始动画 mNumberAnimTextView.setNumberString("98765432.75"); */ @SuppressLint("AppCompatCustomView") public class NumberAnimTextView extends TextView { /** * 起始值 默认 0 */ private String mNumStart = "0"; /** * 结束值 */ private String mNumEnd; /** * 动画总时间 默认 2000 毫秒 */ private long mDuration = 2000; /** * 前缀 */ private String mPrefixString = ""; /** * 后缀 */ private String mPostfixString = ""; /** * 是否开启动画 */ private boolean mIsEnableAnim = true; /** * 是否是整数 */ private boolean isInt; private ValueAnimator animator; public NumberAnimTextView(Context context) { super(context); } public NumberAnimTextView(Context context, AttributeSet attrs) { super(context, attrs); } public NumberAnimTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setNumberString(String number) { setNumberString("0", number); } public void setNumberString(String numberStart, String numberEnd) { mNumStart = numberStart; mNumEnd = numberEnd; if (checkNumString(numberStart, numberEnd)) { // 数字合法 开始数字动画 start(); } else { // 数字不合法 直接调用 setText 设置最终值 setText(mPrefixString + numberEnd + mPostfixString); } } public void setEnableAnim(boolean enableAnim) { mIsEnableAnim = enableAnim; } public void setDuration(long mDuration) { this.mDuration = mDuration; } public void setPrefixString(String mPrefixString) { this.mPrefixString = mPrefixString; } public void setPostfixString(String mPostfixString) { this.mPostfixString = mPostfixString; } /** * 校验数字的合法性 * * @param numberStart 开始的数字 * @param numberEnd 结束的数字 * @return 合法性 */ private boolean checkNumString(String numberStart, String numberEnd) { String regexInteger = "-?\\d*"; isInt = numberEnd.matches(regexInteger) && numberStart.matches(regexInteger); if (isInt) { return true; } String regexDecimal = "-?[1-9]\\d*.\\d*|-?0.\\d*[1-9]\\d*"; if ("0".equals(numberStart)) { if (numberEnd.matches(regexDecimal)) { return true; } } if (numberEnd.matches(regexDecimal) && numberStart.matches(regexDecimal)) { return true; } return false; } private void start() { if (!mIsEnableAnim) { // 禁止动画 setText(mPrefixString + format(new BigDecimal(mNumEnd)) + mPostfixString); return; } animator = ValueAnimator.ofObject(new BigDecimalEvaluator(), new BigDecimal(mNumStart), new BigDecimal(mNumEnd)); animator.setDuration(mDuration); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { BigDecimal value = (BigDecimal) valueAnimator.getAnimatedValue(); setText(mPrefixString + format(value) + mPostfixString); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { setText(mPrefixString + mNumEnd + mPostfixString); } }); animator.start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (animator != null) { animator.cancel(); } } /** * 格式化 BigDecimal ,小数部分时保留两位小数并四舍五入 * * @param bd BigDecimal * @return 格式化后的 String */ private String format(BigDecimal bd) { StringBuilder pattern = new StringBuilder(); if (isInt) { pattern.append("#,###"); } else { int length = 0; String[] s1 = mNumStart.split("\\."); String[] s2 = mNumEnd.split("\\."); String[] s = s1.length > s2.length ? s1 : s2; if (s.length > 1) { // 小数部分 String decimals = s[1]; if (decimals != null) { length = decimals.length(); } } pattern.append("#,##0"); if (length > 0) { pattern.append("."); for (int i = 0; i < length; i++) { pattern.append("0"); } } } DecimalFormat df = new DecimalFormat(pattern.toString()); return df.format(bd); } private static class BigDecimalEvaluator implements TypeEvaluator { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { BigDecimal start = (BigDecimal) startValue; BigDecimal end = (BigDecimal) endValue; BigDecimal result = end.subtract(start); return result.multiply(new BigDecimal(fraction)).add(start); } } }
效果图:
”
TextView文本尾部添加标签,支持自动换行
https://blog.csdn.net/qq_26287435/article/details/100305849
TextView超过固定行数显示"...展开全部"
/** * @param maxLines 最大行数 * //setContent(textView, "的滴滴答答滴滴答答滴滴答答的我是测试文本的长度超过会怎样啊哦耶", 2) */ private fun setContent(textView: TextView?, content: String, maxLines: Int) { textView?.apply { post { val width = measuredWidth - paddingLeft - paddingRight //实例化StaticLayout 传入相应参数 val staticLayout = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { StaticLayout.Builder.obtain(content, 0, content.length, paint, width) .setAlignment(Layout.Alignment.ALIGN_NORMAL) .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier) .setIncludePad(includeFontPadding) .build() } else { @Suppress("DEPRECATION") StaticLayout( content, paint, width, Layout.Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineSpacingExtra, includeFontPadding ) } val lineCount = staticLayout.lineCount text = if (lineCount > maxLines) { //文本需要折叠,展示折叠的样子 val foldSpan = SpannableString("展开") //给展开设置颜色 foldSpan.setSpan(ForegroundColorSpan(resources.getColor(R.color.color_bc9f77)), 0, foldSpan.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) //给展开设置粗体 foldSpan.setSpan(StyleSpan(Typeface.BOLD), 0, foldSpan.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) val index = staticLayout.getLineStart(maxLines) // 得到指定行的开始字符位置,0代表第一行 val substring = content.substring(0, index - foldSpan.length - 1) val spb = SpannableStringBuilder() spb.append(substring) spb.append("...") spb.append(foldSpan) spb } else { //不需要折叠 val spb = SpannableStringBuilder() spb.append(content) spb } } } }
https://www.jianshu.com/p/04dcf505c870 (带动画的展开折叠)
Android 如何优雅地实现@人功能?——仿微博、仿QQ、仿微信、零入侵、高扩展性
https://www.jianshu.com/p/83176fb89aed