本篇博客用来记录在日常的开发中TextView的诸多使用技巧,用以日后再次遇到后更加熟练的进行使用。
设置h5字体样式
mTextTip.setText(Html.fromHtml("我是谁"));
a. 很常见的如: android:drawableLeft = " ",即实现右边是文字左边的是图标的显示效果
b. setCompoundDrawables 的使用技巧
该API的功能就是设置Drawable在TextView控件的显示位置,可以在Java代码段很轻易的实现图标的切换和图标位置的变换。
以下为案例1 实现图标的切换:
public static void modifyTextViewDrawable(TextView v, Drawable drawable, int index) {
drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
//index 0:左 1:上 2:右 3:下
if (index == 0) {
v.setCompoundDrawables(drawable, null, null, null);
} else if (index == 1) {
v.setCompoundDrawables(null, drawable, null, null);
} else if (index == 2) {
v.setCompoundDrawables(null, null, drawable, null);
} else {
v.setCompoundDrawables(null, null, null, drawable);
}
}
解释: drawable.setBounds( )设置drawable将绘制在矩形的那个区域内,绘制点为该canvas的左上角
案例2其实差不多,贴出来
tvStatus = findViewById(R.id.tvStatus);
// 获得文件资源下的图片
Drawable drawableLeft = ContextCompat.getDrawable(this,R.mipmap.ic_location);
// 设置大小 设置图像绘制的矩形位置. 相对于canvas的左上角
drawableLeft.setBounds(0, 0,28,28);
// 设置符合的图片 方位为左
tvStatus.setCompoundDrawables(drawableLeft, null, null, null);
这里是没封装的,可以自由调整你所插入的图标的大小,这些都是OK的。
SpannableStringBuilder 本小节的重点
解释: 这是一个内容和标记都可以更改的文本类。
SpannableStringBuilder 和 SpannableString通过 setSpan( )来改变文本样式。其方法如下:
//SpannableString.java
// 参数- what传入各种Span类型的实例;
// 参数二/三 start和end标记要替代的文字内容的范围;
// 参数四 flags是用来标识在Span范围内的文本前后输入新的字符时是否把它们也应用这个效果
public void setSpan(Object what, int start, int end, int flags) {
super.setSpan(what, start, end, flags);
}
flag 取值:
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
:在 Span前面输入的字符不应用 Span的效果,在后面输入的字符应用Span效果。Spannable.SPAN_INCLUSIVE_EXCLUSIVE
:在 Span前面输入的字符应用 Span 的效果,在后面输入的字符不应用Span效果。Spannable.SPAN_INCUJSIVE_INCLUSIVE
:在 Span前后输入的字符都应用 Span 的效果。Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
:前后都不应用。what: 对应的各种Span,不同的Span对应不同的样式。实现不同的文字样式的重点就是在这里哦。已知的可用类有:
BackgroundColorSpan
: 文本背景色ForegroundColorSpan
: 文本颜色MaskFilterSpan
: 修饰效果,如模糊(BlurMaskFilter)浮雕RasterizerSpan
: 光栅效果StrikethroughSpan
: 删除线SuggestionSpan
: 相当于占位符UnderlineSpan
: 下划线AbsoluteSizeSpan
: 文本字体(绝对大小)DynamicDrawableSpan
: 设置图片,基于文本基线或底部对齐。ImageSpan
: 图片RelativeSizeSpan
: 相对大小(文本字体)ScaleXSpan
: 基于x轴缩放StyleSpan
: 字体样式:粗体、斜体等SubscriptSpan
: 下标(数学公式会用到)SuperscriptSpan
: 上标(数学公式会用到)TextAppearanceSpan
: 文本外貌(包括字体、大小、样式和颜色)TypefaceSpan
: 文本字体URLSpan
: 文本超链接ClickableSpan
: 点击事件案例1. 图像文字混合排放
private void actionImage() {
SpannableString spanString = new SpannableString("1 南京");
ImageSpan imageSpan = new ImageSpan(mDrawable, ImageSpan.ALIGN_BOTTOM);
// 将0-1进行替换
spanString.setSpan(imageSpan,0,1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tvTwo.setText(spanString);
}
案例2. 打印出美元符号并且字体颜色和后面不一致
/**
* @author zxl on 2018/8/15.
* discription: CharacterStyle是字体的基类,后面像
* 什么点击事件,背景啊 啥的都继承自该基类
*/
public class SpannableBg extends CharacterStyle {
/**
* 更新绘制的状态
* 可以添加颜色下划线之列
* @param ds
*/
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(Color.parseColor("#ff0000"));
// 这里的尺寸是 px 后面这里需要转换的
ds.setTextSize(38);
// 是否添加下划线
ds.setUnderlineText(false);
ds.clearShadowLayer();
}
}
SpannableString dollars = new SpannableString("$6000");
dollars.setSpan(new SpannableBg(),
0,
1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mTvColor.setText(dollars);
案例3: 带删除线的
private void setSpannableStyle() {
SpannableString dollars = new SpannableString("$6000");
dollars.setSpan(new SpannableBg(),
0,
1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
dollars.setSpan(new StrikethroughSpan(),1,dollars.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mTvColor.setText(dollars);
}
案例4: 点击事件( 这个有点难度的)
public interface ISpanClick {
void onClick(int position);
}
public class NameClickListener implements ISpanClick {
private String name;
public NameClickListener(String name) {
this.name = name;
}
@Override
public void onClick(int position) {
Log.e("数据","第几个: "+position+" "+name);
}
}
public class TextClickSpan extends ClickableSpan {
private ISpanClick mISpanClick;
private int mPosition;
public TextClickSpan(ISpanClick ISpanClick, int posiotion) {
mISpanClick = ISpanClick;
mPosition = posiotion;
}
@Override
public void onClick(View widget) {
mISpanClick.onClick(mPosition);
}
@Override
public void updateDrawState(TextPaint ds) {
// 这里继承的话 那么会重叠之前你的属性,所以在这
// 里不做操作
}
}
private void setSpannableStyle() {
SpannableString dollars = new SpannableString("$6000");
dollars.setSpan(new SpannableBg(),
0,
1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
dollars.setSpan(new StrikethroughSpan(),1,dollars.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mTvColor.setText(dollars);
dollars.setSpan(new TextClickSpan(new NameClickListener(
"我牛逼"), 0),
1,dollars.length()
,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mTvColor.setText(dollars);
/* 这句话才是重点吖,不加根本没法点击 背景是这个LinkMovementMethod在起作用*/
mTvColor.setMovementMethod(LinkMovementMethod.getInstance());
}
// mydairytestproject E/数据: 第几个: 0 我牛逼
运行结果如下,但是不好的是,点击了之后那么一直高亮显示,这个就应该是LinkMovementMethod的弊端吧,故而有必要去重写一个MovementMethod。来达到特定的效果。
案例5 SpanableStringBuider学习
private void actionExample(){
SpannableStringBuilder stringBuilder = new SpannableStringBuilder("");
stringBuilder.append("你好啊我的哥们\n");
String inner = "我最帅";
SpannableString spanInner = new SpannableString(inner);
spanInner.setSpan(new BackgroundColorSpan(Color.RED),
0,
spanInner.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
stringBuilder.append(spanInner);
stringBuilder.append("\n啧啧");
tvThree.setText(stringBuilder);
}
案例6 单击链接后,跳转我们自定义的活动.
/**
* 自定义单击url链接的动作...
*/
private void actionToActivity(){
String text = "跳转向活动";
SpannableString spanToActivity = new SpannableString(text);
spanToActivity.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Intent intent = new Intent(BgActivity.this,NextActivity.class);
startActivity(intent);
}
},0,text.length()-1,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tvThree.setText(spanToActivity);
// 在单击链接时 凡事要执行的动作 都是要设置MovementMethod对象
tvThree.setMovementMethod(LinkMovementMethod.getInstance());
}
案例7 究极版,自定义了事件传递
mTvColor.setMovementMethod(new CircleMovementMethod(R.color.transparent));
public class CircleMovementMethod extends BaseMovementMethod {
/**
* 默认颜色
*/
public final static int DEFAULT_COLOR = R.color.transparent;
/**
* 文本背景颜色
*/
private int mTextViewBgColorId ;
/**
* 图片背景颜色
*/
private int mClickableSpanBgClorId;
/**
* 背景颜色深的Span类
*/
private BackgroundColorSpan mBgSpan;
/**
* 点击的数组
*/
private ClickableSpan[] mClickLinks;
/**
* 响应时间 ----- 是响应Textd的还是响应内部的
*/
private boolean isPassToTv = true;
/**
* true:响应textview的点击事件, false:响应设置的clickableSpan事件
*/
public boolean isPassToTv() {
return isPassToTv;
}
private void setPassToTv(boolean isPassToTv){
this.isPassToTv = isPassToTv;
}
public CircleMovementMethod(){
mTextViewBgColorId = DEFAULT_COLOR;
mClickableSpanBgClorId = DEFAULT_COLOR;
}
public CircleMovementMethod(int clickableSpanBgClorId){
mClickableSpanBgClorId = clickableSpanBgClorId;
mTextViewBgColorId = DEFAULT_COLOR;
}
public CircleMovementMethod(int clickableSpanBgClorId, int textViewBgColorId){
mClickableSpanBgClorId = clickableSpanBgClorId;
mTextViewBgColorId = textViewBgColorId;
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN){
// 获得触摸点距离该控件左上角的坐标
int x = (int) event.getX();
int y = (int) event.getY();
// 减去Padding值
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
// 加上控件滑动的值
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
mClickLinks = buffer.getSpans(off, off, ClickableSpan.class);
if(mClickLinks.length > 0){
// 点击的是Span区域,不要把点击事件传递
setPassToTv(false);
Selection.setSelection(buffer,
buffer.getSpanStart(mClickLinks[0]),
buffer.getSpanEnd(mClickLinks[0]));
//设置点击区域的背景色
mBgSpan = new BackgroundColorSpan(UiUtils.getColor(mClickableSpanBgClorId));
buffer.setSpan(mBgSpan,
buffer.getSpanStart(mClickLinks[0]),
buffer.getSpanEnd(mClickLinks[0]),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}else{
setPassToTv(true);
// textview选中效果
widget.setBackgroundResource(mTextViewBgColorId);
}
}else if(action == MotionEvent.ACTION_UP){
if(mClickLinks.length > 0){
mClickLinks[0].onClick(widget);
if(mBgSpan != null){
buffer.removeSpan(mBgSpan);
}
}else{
if(mBgSpan != null){
buffer.removeSpan(mBgSpan);
}
}
Selection.removeSelection(buffer);
widget.setBackgroundResource(DEFAULT_COLOR);
}else if(action == MotionEvent.ACTION_MOVE){
}else{
if(mBgSpan != null){
buffer.removeSpan(mBgSpan);
}
widget.setBackgroundResource(DEFAULT_COLOR);
}
return Touch.onTouchEvent(widget, buffer, event);
}
}
这篇文章可以学习下:【Android】强大的SpannableStringBuilder
通常显示大幅文本的时候,设置行间距也是有必要熟练运用的,下面就整理下行间距的设置。这里和word 是一样的,也是有设置在原有1倍行距的基础上增加固定值行距和设置行距的倍数。
android:lineSpacingExtra: 设置额外的行距
android:lineSpacingMultiplier: 设置 x倍行距
// 调整行间距
android:lineSpacingExtra="3dp"
// 连接类型高亮显示
android:autoLink="email|phone"
参考学习连接如下
当限定行数时,设置省略号的吓死你hi的位置如下:
android:ellipsize=”start”—–省略号显示在开头 "...pedia"
android:ellipsize=”end”——省略号显示在结尾 "encyc..."
android:ellipsize=”middle”—-省略号显示在中间 "en...dia"
android:ellipsize=”marquee”–以横向滚动方式显示(需获得当前焦点时)
1. 在TextView中切忌别使用inputType,这样会抢夺到父组件的点击事件,即便是你未设置点击事件。亦是如此。
下面介绍下这个API: android:descendantFocusability="blocksDescendants"
该属性是当一个为view获取焦点时,定义viewGroup和其子控件两者之间的关系。
属性的值有三种:
beforeDescendants:viewgroup会优先其子类控件而获取到焦点
afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点
就是相当于该父控件直接消费掉点击事件,不得传递给子控件!
/**
* Created by apple on 2019/7/23.
* description: 可折叠的TextView
*/
public class ExpandTextView extends LinearLayout implements View.OnClickListener {
private TextView mTvContent,mTvExpand;
/*是否有重新绘制*/
private boolean mRelayout;
/*默认收起*/
private boolean mCollapsed = true;
/*动画执行时间*/
private int mAnimationDuration = 300;
/*是否正在执行动画*/
private boolean mAnimating;
/*设置内容最大行数,超过隐藏*/
private int mMaxCollapsedLines = 2;
/*这个linerlayout容器的高度*/
private int mCollapsedHeight;
/*内容tv真实高度(含padding)*/
private int mTextHeightWithMaxLines;
/*内容tvMarginTopAmndBottom高度*/
private int mMarginBetweenTxtAndBottom;
/*展开图片*/
private Drawable mExpandDrawable;
/*收起图片*/
private Drawable mCollapseDrawable;
public ExpandTextView(Context context) {
this(context,null);
}
public ExpandTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ExpandTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
/**
* 设置orientation 必须为垂直
* @param orientation
*/
@Override
public void setOrientation(int orientation) {
if (LinearLayout.HORIZONTAL == orientation) {
throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");
}
super.setOrientation(orientation);
}
/**
* 依据参数属性 设置排列方式
* @param attrs
*/
private void init(AttributeSet attrs) {
Log.e("expand", "init: 初始化了" );
mExpandDrawable = ContextCompat.getDrawable(getContext(), R.mipmap.ic_expand_down);
mExpandDrawable.setBounds(0,0,25,25);
mCollapseDrawable = ContextCompat.getDrawable(getContext(), R.mipmap.ic_expand_up);
mCollapseDrawable.setBounds(0,0,25,25);
setOrientation(VERTICAL);
// 在加载前设置不可见...
setVisibility(GONE);
}
/**
* 渲染完成后初始化view
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
initViews();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 如果没有改变则返回
if (!mRelayout || getVisibility() == View.GONE){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
mRelayout = false;
mTvExpand.setVisibility(GONE);
mTvContent.setMaxLines(Integer.MAX_VALUE);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//如果内容真实行数小于等于最大行数,不处理
if (mTvContent.getLineCount() <= mMaxCollapsedLines) {
return;
}
// 获取内容tv真实高度(含padding)进行保存
mTextHeightWithMaxLines = getRealTextViewHeight(mTvContent);
// 如果是收起状态,重新设置最大行数
if (mCollapsed) {
mTvContent.setMaxLines(mMaxCollapsedLines);
}
mTvExpand.setVisibility(View.VISIBLE);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCollapsed) {
// Gets the margin between the TextView's bottom and the ViewGroup's bottom
mTvContent.post(new Runnable() {
@Override
public void run() {
mMarginBetweenTxtAndBottom = getHeight() - mTvContent.getHeight();
}
});
// 保存这个容器的测量高度
mCollapsedHeight = getMeasuredHeight();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 当动画还在执行状态时,拦截事件,不让child处理
return mAnimating;
}
private void initViews() {
LayoutInflater.from(getContext()).inflate(R.layout.textview_expand_shink,this);
mTvContent = findViewById(R.id.expand_content);
mTvContent.setOnClickListener(this);
mTvExpand = findViewById(R.id.expand_collapse);
mTvExpand.setText(mCollapsed ? "全文" : "收起");
mTvExpand.setCompoundDrawables(null, null, mCollapsed ? mExpandDrawable : mCollapseDrawable, null);
mTvExpand.setOnClickListener(this);
}
/**
* 点击事件
* @param v
*/
@Override
public void onClick(View v) {
if (mTvExpand.getVisibility() != VISIBLE){
return;
}
mCollapsed = !mCollapsed;
mTvExpand.setText(mCollapsed ? "全文" : "收起");
mTvExpand.setCompoundDrawables(null, null, mCollapsed ? mExpandDrawable : mCollapseDrawable, null);
// 执行展开/收起动画
mAnimating = true;
ValueAnimator valueAnimator;
if (mCollapsed) {
valueAnimator = ValueAnimator.ofInt(getHeight(), mCollapsedHeight);
} else {
valueAnimator = ValueAnimator.ofInt(getHeight(), getHeight() +
mTextHeightWithMaxLines - mTvContent.getHeight());
}
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int animatedValue = (int) valueAnimator.getAnimatedValue();
mTvContent.setMaxHeight(animatedValue - mMarginBetweenTxtAndBottom);
getLayoutParams().height = animatedValue;
requestLayout();
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
// 动画结束后发送结束的信号
/// clear the animation flag
mAnimating = false;
if (mCollapsed){
mTvContent.setMaxLines(mMaxCollapsedLines);
mTvContent.setEllipsize(TextUtils.TruncateAt.END);
}
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
valueAnimator.setDuration(mAnimationDuration);
valueAnimator.start();
}
/**
* 设置内容
* @param text
*/
public void setText( CharSequence text) {
mRelayout = true;
mTvContent.setText(text);
setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
}
/**
* 获取内容
* @return
*/
public CharSequence getText() {
if (mTvContent == null) {
return "";
}
return mTvContent.getText();
}
/**
* 获取内容tv真实高度(含padding)
* @param textView
* @return
*/
private static int getRealTextViewHeight( TextView textView) {
int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
return textHeight + padding;
}
}