Android UI之TextView常用技巧整理

1、简介

本篇博客用来记录在日常的开发中TextView的诸多使用技巧,用以日后再次遇到后更加熟练的进行使用。

2、TextView技巧使用明细

设置h5字体样式
mTextTip.setText(Html.fromHtml("我是谁"));

2.1 TextView 带图标显示

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其实差不多,贴出来

Android UI之TextView常用技巧整理_第1张图片

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的。

2.2

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);

 

 Android UI之TextView常用技巧整理_第2张图片

案例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);
    }

 Android UI之TextView常用技巧整理_第3张图片

案例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。来达到特定的效果。

 Android UI之TextView常用技巧整理_第4张图片

案例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);
    }

Android UI之TextView常用技巧整理_第5张图片

案例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

2.3 TextView行间距

通常显示大幅文本的时候,设置行间距也是有必要熟练运用的,下面就整理下行间距的设置。这里和word 是一样的,也是有设置在原有1倍行距的基础上增加固定值行距和设置行距的倍数。

 android:lineSpacingExtra:        设置额外的行距

android:lineSpacingMultiplier:   设置 x倍行距

// 调整行间距
android:lineSpacingExtra="3dp"

// 连接类型高亮显示
android:autoLink="email|phone"

2.4 TextView 内容过多,显示不全时,实现带滚动条,上下滑动

参考学习连接如下

2.5 自定义组合TextView

2.6 一些不错的关于TextView的框架 

2.7 其他

当限定行数时,设置省略号的吓死你hi的位置如下: 

android:ellipsize=”start”—–省略号显示在开头 "...pedia"
android:ellipsize=”end”——省略号显示在结尾  "encyc..."
android:ellipsize=”middle”—-省略号显示在中间 "en...dia"
android:ellipsize=”marquee”–以横向滚动方式显示(需获得当前焦点时)

2.8 错误总结

1. 在TextView中切忌别使用inputType,这样会抢夺到父组件的点击事件,即便是你未设置点击事件。亦是如此。

下面介绍下这个API: android:descendantFocusability="blocksDescendants"

该属性是当一个为view获取焦点时,定义viewGroup和其子控件两者之间的关系。

属性的值有三种:

        beforeDescendants:viewgroup会优先其子类控件而获取到焦点

        afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点

        blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

 就是相当于该父控件直接消费掉点击事件,不得传递给子控件!

3.0 综合案例 -- 可折叠TextView

Android UI之TextView常用技巧整理_第6张图片

/**
 * 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;
    }
}

 



    
    

 

你可能感兴趣的:(常用控件)