Android TextView 缩进指定距离

2020-11-23 15:42:58 浏览数 (1)

最近产品汪和运营商讨下来决定要做商品促销活动,然后设计妹子给到最终的效果图

第一感觉就是 so so easy 嘛,加个标签,费不了什么事儿。第一印象记得 Spanable 可以更改对应文字的颜色和背景,设置设置点击事件。 接着,发现了一个问题,上面说到的 Spanable 只能实现全色的背景,不能实现这种边框的背景。看来这种方案是行不通的。

第一感觉不奏效,那么就要分析下这种效果,我想到以下两种方案。

第一种方案就是是否可以直接给 TextView 设置指定的留白呢?就是前面的标签是一个控件,TextView 留白便签控件宽度 margin值。这个方案需要解决的问题是,这里是否有相关的 Api 可以直接设置每行留白的距离,另外首行标签和文字居中对齐问题,毕竟设计师都是像素眼,没有按要求对齐,行距不对都可能无法验收。

第二种方案就是取巧,将 title 的 TextView 拆分为两个 TextView,第一行直接就是线性水平布局,第二行再是一个独立的TextView。这里需要解决的问题是,我怎么获取 TextView 第一行显示的文字,然后截取剩余的文字单独显示在第二行。这种方法实现似乎没有第一种优雅,但是可以轻松避开第一行标签和 title 文字居中对齐的问题。

在否定一种方案和提出新的两种方案后,可以看看后两种方案到底可以怎么实现。

第一种方案:

这里需要使用到 SpannableString 这个类,接着就是主角 LeadingMarginSpan 这个类。

A paragraph style affecting the leading margin. There can be multiple leading margin spans on a single paragraph; they will be rendered in order, each adding its margin to the ones before it. The leading margin is on the right for lines in a right-to-left paragraph. LeadingMarginSpans should be attached from the first character to the last character of a single paragraph.

一句话,它可以给 TextView 每行设置指定的头间距,找到相关 API 之后,接着计算出标签的整体宽度。

代码语言:javascript复制
LeadingMarginSpan.Standard what =
    new LeadingMarginSpan.Standard(width, 0);
spannableString.
    setSpan(what, 0, spannableString.length(), SpannableString.SPAN_INCLUSIVE_INCLUSIVE);

LeadingMarginSpan 是接口,内部的 Standard 看名字就知道是它的标准实现,它有两个构造方法,Standard(int every)Standard(int first, int rest) ,这个就是指定 TextView 每行的缩进值的,一个参数的就是给每一行都设置同样的值,最后当然就是调用两个参数的方法,两个参数的就是指定第一行和其他行的缩进值。

接着看下 SpannableStringsetSpan() 的方法,这里需要设置四个参数,第一个就是我们创建出来的 LeadingMarginSpan ,第二个和第三个其实就是第一个对象的作用范围,第四个参数控制范围的边界包含情况。我们这里不是给具体第几个到第几个的字设置属性,所以后面的 start、end 以及边界限制随便写都会生效的。

对于第四个参数,就是对上下边界是否包含自己的限定,这里你只需要认识这两个单词就好,「EXCLUSIVE」 就是不包含,「INCLUSIVE 」就是包含。所以这里就有四种情况,当然这个不是这次的重点。

第二种方案:

这里需要使用到 Layout 个类, TextView 使用它管理文字显示。

A base class that manages text layout in visual elements on the screen. For text that will be edited, use a {@link DynamicLayout}, which will be updated as the text changes. For text that will not change, use a {@link StaticLayout}.

通过这个 Layout,我们就可以获取到 TextView 每行的内容,然后就可以决定第二行是否显示及其内容。

代码语言:javascript复制
Layout layout = first.getLayout();
int lineEnd = layout.getLineEnd(0);

上面的 lineEnd 就是第一行文字显示的数量,拿到之后,就可以判断下,如果和总长度相等,那就说明第一行就可以显示完全,第二行根据具体情况,控制显示与否。如果小于总长度,那么久截取出剩余文字,用于第二行 TextView 显示。

到这里,两种方案实现完毕,接着再聊一个问题,那就是测量时机,这里的需求总是出现在列表页面,这就涉及到一个计算时机问题,这里我的解决方案是添加一个 addOnPreDrawListener 的方式,这个方法是每次绘制之前都会调用,比较符合列表的刷新。

最终效果:

方案一(左边)方案二(右边)

方案一(左边)方案二(右边)

详细的代码:

代码语言:javascript复制
//方案一:将文字查分为两个两个textview 显示
    public static void calculatetag1(textview first, textview second, final string text) {
        viewtreeobserver observer = first.getviewtreeobserver();
        observer.addonpredrawlistener(new viewtreeobserver.onpredrawlistener() {
            @override
            public boolean onpredraw() {
                layout layout = first.getlayout();
                int lineend = layout.getlineend(0);
                string substring = text.substring(0, lineend);
                string substring1 = text.substring(lineend, text.length());
                log.i("tag", "ongloballayout:"  " end:"   lineend);
                log.i("tag", "ongloballayout: 第一行的内容::"   substring);
                log.i("tag", "ongloballayout: 第二行的内容::"   substring1);
                if (textutils.isempty(substring1)) {
                    second.setvisibility(view.gone);
                    second.settext(null);
                } else {
                    second.setvisibility(view.visible);
                    second.settext(substring1);
                }
                first.getviewtreeobserver().removeonpredrawlistener(
                        this);
                return false;
            }
        });

    }
    //方案二:动态设置缩进距离的方式
    public static void calculatetag2(textview tag, textview title, final string text) {
        viewtreeobserver observer = tag.getviewtreeobserver();
        observer.addonpredrawlistener(new viewtreeobserver.onpredrawlistener() {
            @override
            public boolean onpredraw() {
                spannablestring spannablestring = new spannablestring(text);
               //这里没有获取margin的值,而是直接写死的
                leadingmarginspan.standard what = new leadingmarginspan.standard(tag.getwidth()   dip2px(tag.getcontext(), 10), 0);
                spannablestring.setspan(what, 0, spannablestring.length(), spannablestring.span_inclusive_inclusive);
                title.settext(spannablestring);
                tag.getviewtreeobserver().removeonpredrawlistener(
                        this);
                return false;
            }
        });
    }

    public static int dip2px(context context, double dpvalue) {
        float density = context.getresources().getdisplaymetrics().density;
        return (int) (dpvalue * density   0.5);
     }

PS:SpannableStringBuilder 阔以用于快速给 TextView 设置Span,最后看了下某东的效果,它的标签不是一个独立的控件,看样子或许是使用的 ImageSpan 来实现。但是 ImageSpan 默认不是居中对齐,解决方案可以看看。

强势刷一波存在,示例相关代码地址(https://github.com/lovejjfg/PowerRecyclerView),欢迎点赞,对了,觉得我搞得复杂或者有更简单的实现请留言一起讨论下咯。??

作者:lovejjfg 链接:http://www.jianshu.com/p/03ec9865c942

相关推荐

RxFile 一款选择多媒体文件的精巧的工具

技术 - 思维 - 感悟

END

0 人点赞