美文网首页Android
Android中SpannableString使用(二)---I

Android中SpannableString使用(二)---I

作者: __Witness | 来源:发表于2018-11-30 16:57 被阅读0次

    2018-11-30,发现又是周五啦,心情美美哒。想着再过几个小时就要下班过周末了,感觉自己立马兴奋无比了,再想想明天和小伙伴约了篮球,又要暴虐他们,一下就要膨胀了。但是一直秉持着做人要低调的我,还是要冷静一下,毕竟还没下班么。所以先来写一个文章吧。

    上篇Android中SpannableString使用(一)---如何在一个TextView中显示不同样式的文字中,简单的介绍了SpannableString在Android开发中的一些简单使用,还有一些Span的简单使用。

    这一篇我就来介绍一个

    Android中使用ImageSpan实现简单的图文展示方案

    最近项目中有一个这个的页面需求:


    需求效果图

    其实这个要注意的也就是如何实现小图标在文字的最后面显示出来。

    第一反应想到的方法就是给TextView加上drawableRight,于是就撸了一波代码

        <TextView
            android:id="@+id/tv1"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="@dimen/sp_14"
            android:textColor="@color/text_333333"
            android:text="花和尚 送 小二哥 一个鼓掌"
            android:drawableRight="@mipmap/aaaa"
            android:drawablePadding="@dimen/dp_5"/>
    
        <TextView
            android:id="@+id/tv2"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:textSize="@dimen/sp_14"
            android:textColor="@color/text_333333"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌"
            android:drawableRight="@mipmap/aaaa"
            android:drawablePadding="@dimen/dp_5"/>
    
    drawableRight方式效果

    很显然用这种方式实现的时候,当文字只有一行的时候,可以达到想要的效果,可是换行之后就不可以了。所以继续想想其他的方法呗。

    接下来就说说使用ImageSpan的方式实现

    直接先上代码

    String s = "花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥  ";
    //创建SpannableString对象
    SpannableString string = new SpannableString(s);
    //创建ImageSpan对象
    ImageSpan imageSpan = new ImageSpan(mContext, R.mipmap.aaaa, DynamicDrawableSpan.ALIGN_BASELINE);
    //给SpannableString对象设置ImageSpan
    string.setSpan(imageSpan, s.length() - 1, s.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    tv3.setText(string);
    

    简单的几句代码就可以实现图标一直确保在最后一个文字后面


    使用ImageSpan的效果

    看完这张图,很明显我在图上标注了两条线。仔细观察,我画的线都是放在小图标的下边缘,然而却在文字的不同高度上。这说明小图标和文字的对齐方式不同,这是为什么呢?我们就来看看ImageSpan的源码吧。

    public ImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment)
    

    ImageSpan的构造方法有好几种,因为实例代码中使用的这种,就说说这个吧。

    三个参数

    1,Context context:上下文
    2,@DrawableRes int resourceId:DrawableResId
    3,int verticalAlignment:对齐方式(系统中提供了两种)

       /**
        * A constant indicating that the bottom of this span should be aligned
        * with the bottom of the surrounding text, i.e., at the same level as the
        * lowest descender in the text.
        */
       public static final int ALIGN_BOTTOM = 0;
       
       /**
        * A constant indicating that the bottom of this span should be aligned
        * with the baseline of the surrounding text.
        */
       public static final int ALIGN_BASELINE = 1;
    
    • DynamicDrawableSpan.ALIGN_BASELINE:以基线对齐
    • DynamicDrawableSpan.ALIGN_BOTTOM:以底部对齐

    这个就是上面图中的两条线位置不同的原因。
    写在这里有突然想到一个问题,我们在实际开发中布局的时候,很多时候用到的是垂直居中。而ImageSpan中只提供了ALIGN_BASELINE和ALIGN_BOTTOM,我们怎么实现垂直居中的效果呢。接下来继续逼逼。

    首先看看ImageSpan的源码

    public class ImageSpan extends DynamicDrawableSpan{}
    

    并没有什么特别的东西

    再看看其父类DynamicDrawableSpan

        @Override
        public int getSize(Paint paint, CharSequence text,
                             int start, int end,
                             Paint.FontMetricsInt fm) {
            Drawable d = getCachedDrawable();
            Rect rect = d.getBounds();
    
            if (fm != null) {
                fm.ascent = -rect.bottom; 
                fm.descent = 0; 
    
                fm.top = fm.ascent;
                fm.bottom = 0;
            }
    
            return rect.right;
        }
    
        @Override
        public void draw(Canvas canvas, CharSequence text,
                         int start, int end, float x, 
                         int top, int y, int bottom, Paint paint) {
            Drawable b = getCachedDrawable();
            canvas.save();
            
            int transY = bottom - b.getBounds().bottom;
            if (mVerticalAlignment == ALIGN_BASELINE) {
                transY -= paint.getFontMetricsInt().descent;
            }
    
            canvas.translate(x, transY);
            b.draw(canvas);
            canvas.restore();
        }
    

    getSize(...)函数:返回一个int的rect.right,也就是图片的宽度。同时在这个函数中也对文字的ascent和descent两个值进行了修改。

            /**
             * The recommended distance above the baseline for singled spaced text.
             */
            public int   ascent;
            /**
             * The recommended distance below the baseline for singled spaced text.
             */
            public int   descent;
    

    (注:ascent,descent等参数的含义在文章的最后面有图文解释)

    draw(...)函数:在这个函数里面是根据不同的对齐参数进行图片的绘制。

    就从这两个函数入手修改竖直居中的对齐方式

    • 我们得创建一个VerticalImageSpan继承ImageSpan,并重写以上的两个函数。
    public class VerticalImageSpan extends ImageSpan {
        
        public VerticalImageSpan(Context context, int resourceId) {
            super(context, resourceId);
        }
        
    }
    
    • 修改draw(...)方法来实现居中
        @Override
        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
            Drawable b = getDrawable();
            canvas.save();
            int transY = 0;
            //获得将要显示的文本高度 - 图片高度除2 = 居中位置+top(换行情况)
            transY = ((bottom - top) - b.getBounds().bottom) / 2 + top;
            //偏移画布后开始绘制
            canvas.translate(x, transY);
            b.draw(canvas);
            canvas.restore();
        }
    
    • 还要修改getSize(...)函数,重新设置文字的ascent和descent
        @Override
        public int getSize(Paint paint, CharSequence text, int start, int end,
                           Paint.FontMetricsInt fm) {
            Drawable d = getDrawable();
            Rect rect = d.getBounds();
            if (fm != null) {
                Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
                //获得文字、图片高度
                int fontHeight = fmPaint.bottom - fmPaint.top;
                int drHeight = rect.bottom - rect.top;
                
                int top = drHeight / 2 - fontHeight / 4;
                int bottom = drHeight / 2 + fontHeight / 4;
    
                fm.ascent = -bottom;
                fm.top = -bottom;
                fm.bottom = top;
                fm.descent = top;
            }
            return rect.right;
        }
    
    现在就可以直接使用了。看看三种对齐方式的效果图(为了更加明显我换了一个大一点的图标) 三种不同的对齐方式

    好了,到这里基本已经结束了。
    效果图中文字还需要改变颜色,这里就不做了。上篇中我已经提到过不同的span可以组合使用实现复杂的展示效果,我们只需要再给SpannableString设置一个ForegroundColorSpan就可以实现改变颜色了。

    最后再补充一点Android中TextView编辑时结构

    textview的结构
    • 基准点是baseline
    • ascent:是baseline之上至字符最高处的距离
    • descent:是baseline之下至字符最低处的距离
    • leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
    • top:是指的是最高字符到baseline的值,即ascent的最大值
    • bottom:是指最低字符到baseline的值,即descent的最大值

    终于写完了。浪浪浪浪浪起来!!!

    相关文章

      网友评论

        本文标题:Android中SpannableString使用(二)---I

        本文链接:https://www.haomeiwen.com/subject/jukhcqtx.html