【需求解决系列之三】Android 自定义可展开收回的ExpandableTextView

int startPosition = mDynamicLayout.getLineStart(index);
//获取指定行的行宽
float lineWidth = mDynamicLayout.getLineWidth(index);

下面这个图会对上面的参数进行简单的说明:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有了这些东西经过简单的计算我们就可以获取到我们需要截取的内容长度。对原内容进行截取再拼接上“展开”或“收回”即可!

/**

  • 计算原内容被裁剪的长度
  • @param endPosition
  • @param startPosition
  • @param lineWidth
  • @param endStringWith
  • @param offset
  • @return
    */
    private int getFitPosition(int endPosition, int startPosition, float lineWidth,
    float endStringWith, float offset, String aimContent) {
    //最后一行需要添加的文字的字数
    int position = (int) ((lineWidth - (endStringWith + offset)) * (endPosition - startPosition)/ lineWidth);

if (position < 0) return endPosition;
//计算最后一行需要显示的正文的长度
float measureText = mPaint.measureText(
(aimContent.substring(startPosition, startPosition + position)));
//如果最后一行需要显示的正文的长度比最后一行的长减去“展开”文字的长度要短就可以了 否则加个空格继续算
if (measureText <= lineWidth - endStringWith) {
return startPosition + position;
} else {
return getFitPosition(endPosition, startPosition, lineWidth, endStringWith, offset + mPaint.measureText(" "));
}
}

二、如何识别文字中的@用户

使用正则表达式对原内容进行匹配,下面是正则表达式:

@[\w\p{InCJKUnifiedIdeographs}-]{1,26}

将匹配到内容做一下记录,最后再使用SpannableStringBuilder对匹配到的内容设置可点击的span并设置其他颜色等具体样式。在以下代码中,我们将匹配到的信息的内容和位置信息保存下来,后面会用到的。对于@用户这块,后面会提到怎么添加高亮显示和添加点击事件。

//对@用户 进行正则匹配
Pattern pattern = Pattern.compile(regexp_mention, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(newResult.toString());
List<FormatData.PositionData> datasMention = new ArrayList<>();
while (matcher.find()) {
//将匹配到的内容进行统计处理
datasMention.add(new FormatData.PositionData(matcher.start(), matcher.end(), matcher.group(), LinkType.MENTION_TYPE));
}

三、如何识别文字中的链接

在开始的时候,找了很多的匹配文字中链接的正则表达式,后来发现好多都有问题。联想到TextView本身就有对链接跳转的支持,就想着TextView的内部一定有相关的正则来匹配,后来查看TextView的源码,发现还真有。

对于链接,后面会提到怎么添加高亮显示和添加点击事件。下面是匹配链接的代码:

List<FormatData.PositionData> datas = new ArrayList<>();
//对链接进行正则匹配
Pattern pattern = AUTOLINK_WEB_URL;
Matcher matcher = pattern.matcher(content);
StringBuffer newResult = new StringBuffer();
int start = 0;
int end = 0;
int temp = 0;
while (matcher.find()) {
start = matcher.start();
end = matcher.end();
newResult.append(content.toString().substring(temp, start));
//将匹配到的内容进行统计处理
datas.add(new FormatData.PositionData(newResult.length() + 1, newResult.length() + 2 + TARGET.length(), matcher.group(), LinkType.LINK_TYPE));
newResult.append(" " + TARGET + " ");
temp = end;
}

除了对链接进行匹配以外,我们还需要将识别到的链接用掩码隐藏起来。如何掩码呢?也就是把原文中的链接用“网页链接”替换掉。那么如何替换掉呢?上面的代码中我们会获取到对应的链接以及链接所在的位置,那么我们只需要使用“网页链接”替换掉匹配到的链接即可。

//newResult是最终会显示在页面上的内容容器
newResult.append(content.toString().substring(end, content.toString().length()));

四、处理@用户,链接和“展开”或者“收回”三者的高亮显示和点击事件

对于@用户,链接和“展开”或者“收回”三者的实现,最终都是使用SpannableStringBuilder来处理。之前我们在对原内容进行解析的时候,将匹配到的链接或者@用户进行了存储,并且存储了他们所在的位置(start,end)以及类型。

//定义类型的枚举类型
public enum LinkType {
//普通链接
LINK_TYPE,
//@用户
MENTION_TYPE
}

有了这些数据的集合,我们只需要遍历这些数据,并分别对这些数据进行setSpan处理,并且在setSpan的过程中设置字体颜色,以及点击事件的回调即可。

//处理链接或者@用户
private void dealLinksOrMention(FormatData formatData,SpannableStringBuilder ssb) {
List<FormatData.PositionData> positionDatas = formatData.getPositionDatas();
HH:
for (FormatData.PositionData data : positionDatas) {
if (data.getType().equals(LinkType.LINK_TYPE)) {
int fitPosition = ssb.length() - getHideEndContent().length();
if (data.getStart() < fitPosition) {
SelfImageSpan imageSpan = new SelfImageSpan(mLinkDrawable, ImageSpan.ALIGN_BASELINE);
//设置链接图标
ssb.setSpan(imageSpan, data.getStart(), data.getStart() + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
//设置链接文字样式
int endPosition = data.getEnd();
if (fitPosition > data.getStart() + 1 && fitPosition < data.getEnd()) {
endPosition = fitPosition;
}
if (data.getStart() + 1 < fitPosition) {
ssb.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
if (linkClickListener != null)
linkClickListener.onLinkClickListener(LinkType.LINK_TYPE, data.getUrl());
}

@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(mLinkTextColor);
ds.setUnderlineText(false);
}
}, data.getStart() + 1, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
} else {
int fitPosition = ssb.length() - getHideEndContent().length();
if (data.getStart() < fitPosition) {
int endPosition = data.getEnd();
if (fitPosition < data.getEnd()) {
endPosition = fitPosition;
}
ssb.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
if (linkClickListener != null)
linkClickListener.onLinkClickListener(LinkType.MENTION_TYPE, data.getUrl());
}

@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(mLinkTextColor);
ds.setUnderlineText(false);
}
}, data.getStart(), endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
}

/**

  • 设置 “展开”
  • @param ssb
  • @param formatData
    */
    private void setExpandSpan(SpannableStringBuilder ssb,FormatData formatData){
    int index = currentLines - 1;
    int endPosition = mDynamicLayout.getLineEnd(index);
    int startPosition = mDynamicLayout.getLineStart(index);
    float lineWidth = mDynamicLayout.getLineWidth(index);

String endString = getHideEndContent();

//计算原内容被截取的位置下标
int fitPosition =
getFitPosition(endPosition, startPosition, lineWidth, mPaint.measureText(endString), 0);

ssb.append(formatData.formatedContent.substring(0, fitPosition));

//在被截断的文字后面添加 展开 文字
ssb.append(endString);

int expendLength = TextUtils.isEmpty(mEndExpandContent) ? 0 : 2 + mEndExpandContent.length();
ssb.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
action();
}

@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
d开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值