前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android TextView 缩进指定距离

Android TextView 缩进指定距离

作者头像
developerHaoz
发布2022-05-13 13:37:20
6640
发布2022-05-13 13:37:20
举报

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

最后效果图

第一感觉就是 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/PowerText 最新代码。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档