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

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

小结

有了这么多优秀的开发工具,可以做出更高质量的Android应用。

当然了,“打铁还需自身硬”,想要写出优秀的代码,最重要的一点还是自身的技术水平,不然用再好的工具也不能发挥出它的全部实力。

在这里我也分享一份大佬自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
*这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值