大家都熟悉微信朋友圈或者是贴吧里的某一条评论,

比如: 小A回复小B:大吉大利,今晚吃鸡,哈哈哈。

点击小A和小B可以跳转到用户页面,点击整个Item就会响应其它事件,比如弹出键盘输入回复。
要实现这样的效果其实很简单,先自定义TextView,通过SpannableStringBuilder设置富文本格式,然后通过setText设置就可以了,看起来简单,但里面其实是有一些坑的,比如我实现了这种效果后,但发现点击Item其它地方的时候没有响应别的事件了。本篇文章就跟大家分享这个小知识点。先看效果:

创建Bean对象

创建评论对象和用户对象CommentBean和CommentUserSpan

public class CommentBean {/* 评论内容 */private String comment;/* 评论人 */private UserBean user;/* 回复人 */private UserBean replyUser;/* 省略了get和set方法 */......
}public class UserBean {private String userId;private String userName;/* 省略了get和set方法 */......
}

bean对象没什么可说的。

自定义TextView

public class CommentTextView extends TextView {public CommentTextView(Context context) {this(context, null);}public CommentTextView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public CommentTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setMovementMethod(CommentUserMovementMethod.getInstance());}public void setText(CommentBean comment) {SpannableStringBuilder stringBuilder = new SpannableStringBuilder();if (comment.getUser() != null) {String str = comment.getUser().getUserName();stringBuilder.append(str);CommentUserSpan span = new CommentUserSpan(getContext(), comment.getUser());stringBuilder.setSpan(span, 0, str.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);}if (comment.getReplyUser() != null) {stringBuilder.append("回复");int start = stringBuilder.toString().length();String str = comment.getReplyUser().getUserName();stringBuilder.append(str);CommentUserSpan span = new CommentUserSpan(getContext(), comment.getReplyUser());stringBuilder.setSpan(span, start, start + str.length(),         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);stringBuilder.append(":");}stringBuilder.append(comment.getComment());setText(stringBuilder);}}

我们要想把CommentBean 中的两个用户对象转化成可点击的就需要借助ClickableSpan,通过SpannableStringBuilder 在特地位置setSpan就可以实现点击效果,ClickableSpan从名字上来看就知道是可点击的意思,上述代码中的CommentUserSpan就是自定义的继承自ClickableSpan用来实现点击事件的类。细心的朋友可能会发现构造里面有个setMovementMethod,没错,这个方法就是能够处理TextView中的触摸事件和点击事件的,有同学说不是有onTouchEvent方法吗?,对,但MovementMethod可以实现TextView中各种Span对象的处理,而且MovementMethod是onTouchEvent方法逻辑中的一部分,有兴趣的可以查看TextView的源码,我们来看看CommentUserSpan这个类:

public class CommentUserSpan extends ClickableSpan {private UserBean user;private Context context;private boolean isPressed;private int normalColor;private int pressedColor;public CommentUserSpan(Context context, UserBean commentUser) {super();this.user = commentUser;this.context = context;normalColor = Color.TRANSPARENT;pressedColor = context.getResources().getColor(R.color.colorPressed);}@Overridepublic void onClick(View widget) {Toast.makeText(context, "点击" + user.getUserName(), Toast.LENGTH_SHORT).show();}public void setPressed(boolean isPressed) {this.isPressed = isPressed;}@Overridepublic void updateDrawState(TextPaint ds) {ds.setColor(Color.BLUE);ds.bgColor = isPressed ? pressedColor : normalColor;}
}

只需重写其中的onClick方法和updateDrawState方法,onClick用来处理点击事件,updateDrawState用来处理我们想要的视觉效果,比如文字颜色,背景颜色等等,ds.bgColor可以实现用户点击后背景变暗的效果,效果跟selector一样。这个类很简单,我们来看看CommentUserMovementMethod类:

public class CommentUserMovementMethod extends BaseMovementMethod {@Overridepublic boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {int action = event.getAction();int x = (int) event.getX();int y = (int) event.getY();x -= widget.getTotalPaddingLeft();y -= widget.getTotalPaddingTop();x += widget.getScrollX();y += widget.getScrollY();Layout layout = widget.getLayout();int line = layout.getLineForVertical(y);int off = layout.getOffsetForHorizontal(line, x);CommentUserSpan[] link = buffer.getSpans(off, off, CommentUserSpan.class);CommentUserSpan span = null;if (link.length > 0) {span = link[0];}switch (action) {case MotionEvent.ACTION_DOWN:if (span != null) {span.setPressed(true);Selection.setSelection(buffer, buffer.getSpanStart(span), buffer.getSpanEnd(span));return true;} else {Selection.removeSelection(buffer);}break;case MotionEvent.ACTION_UP:if (span != null) {span.onClick(widget);span.setPressed(false);Selection.removeSelection(buffer);return true;}Selection.removeSelection(buffer);break;case MotionEvent.ACTION_MOVE:if (span != null) {span.setPressed(false);return true;} else {Selection.removeSelection(buffer);}break;default:if (span != null) {span.setPressed(false);}Selection.removeSelection(buffer);break;}return false;}public static MovementMethod getInstance() {if (sInstance == null) {sInstance = new CommentUserMovementMethod();}return sInstance;}private static CommentUserMovementMethod sInstance;
}

这个类看起来就稍显复杂,其实也就做了两件事,
1、检测点击区域有没有我们设定的CommentUserSpan,有的话就处理相应的逻辑。
2、根据事件类型,处理CommentUserSpan是否是按压状态,用来实现点击效果的。
Android为TextView实现了三种MovementMethod,分别是ArrowKeyMovementMethod、LinkMovementMethod和ScrollingMovementMethod,从名字上来看就能很容易知道LinkMovementMethod是用来处理超链接的、ScrollingMovementMethod是用来处理滚动的,ArrowKeyMovementMethod一眼看不出来,其实大家也都熟悉,就是长安Edittext会出现选择文本,然后弹出ContextMenu,剪切、复制、粘贴这些操作。
CommentUserMovementMethod 直接继承了基类BaseMovementMethod 。主要是重写其中的onTouchEvent方法,最关键的一行是Layout layout = widget.getLayout(); 然后结合event.getX()和event.getY()就可以定位到点击位置的文字,然后查找这个位置有没有我们要处理的CommentUserSpan,有的话就消费这个点击事件,没有的话就不消费事件,抛给父View处理。

到这里基本上就已经实现了效果,但是,有一个非常不好的体验。就是当我点击非用户区域文字的时候发现响应不了整个Item的点击事件了,也就是我想点击整个条目然后弹出键盘这个效果实现不了了,难道点击其它区域的事件也被CommentUserMovementMethod 消费了?于是debug一下,发现也没有消费啊,一时间没有思路了,但可以肯定的是,事件一定被TextView消费了,找了半天,终于发现了坑,还记得CommentTextView 中调了setMovementMethod方法吗?这是源码:

public final void setMovementMethod(MovementMethod movement) {if (mMovement != movement) {mMovement = movement;if (movement != null && !(mText instanceof Spannable)) {setText(mText);}fixFocusableAndClickableSettings();// SelectionModifierCursorController depends on textCanBeSelected, which depends on// mMovementif (mEditor != null) mEditor.prepareCursorControllers();}}

其中关键的一句fixFocusableAndClickableSettings(); 处理TextView的焦点和点击。

private void fixFocusableAndClickableSettings() {if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {setFocusable(true);setClickable(true);setLongClickable(true);} else {setFocusable(false);setClickable(false);setLongClickable(false);}}

进去一看,恍然大悟,了解事件分发机制的同学应该都知道,当View的Clickable和LongClickable为true时,onTouchEvent方法必然会返回true,坑就在这。知道了原因,问题自然迎刃而解。我们可以在调用setMovementMethod方法后再把Clickable和LongClickable设为false就行。

public CommentTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setMovementMethod(CommentUserMovementMethod.getInstance());setClickable(false);setLongClickable(false);}

好了,到这就完全实现微信朋友圈评论Item的效果了。

源码地址:

https://github.com/469412882/CommentTextApp

TextView实现点击部分文字跳转,实现微信朋友圈评论Item的显示效果相关推荐

  1. 文字 竖排居中_微信朋友圈文字如何设置居中居右?

    每次发朋友圈,选好图片,输入构思已久的文字内容,点击发布! 静静的等待着...会有谁给我点赞呢?会有谁给我评论呢?然后,就没有然后了...很多刷存在感的小伙伴和做微商的小伙伴是不是都有这样的困惑?构思 ...

  2. android 微信朋友圈 全功能,Android仿微信朋友圈文字展开全文功能 Android自定义TextView仿微信朋友圈文字展开全文功能...

    Android自定义TextView仿微信朋友圈文字信息,展开全文功能 代码及注释如下: 首先写一个xml文件 showmore.xml: android:orientation="vert ...

  3. H5静态页面跳转微信小程序;从外部浏览器,点击H5链接跳转打开微信小程序;以及在微信内直接点击H5链接打开微信小程序;

    参考链接 需求:从外部浏览器,点击H5链接跳转打开微信小程序:以及在微信内直接点击H5链接打开微信小程序: 步骤1: 小程序开发需要使用云开发创建项目,使用云开发生成的项目会自带云函数文件夹: 步骤2 ...

  4. BUG集合-微信分享点击之后无法跳转到微信app应用进行分享

    微信分享点击之后无法跳转到微信app应用进行分享 微信分享点击之后无法跳转到微信app应用进行分享的错误原因:提供的缩略图过大,所以无法跳转, 这个参数需要的图片不能过大 解决方案:1.换一张小的图片 ...

  5. android 微信朋友圈 全功能,Android自定义TextView仿微信朋友圈文字展开全文功能

    Android自定义TextView仿微信朋友圈文字信息,展开全文功能 代码及注释如下: 首先写一个xml文件 showmore.xml: android:orientation="vert ...

  6. jsp文字上下居中显示_微信朋友圈又有骚技巧,一键设置居中签名,好友傻眼了...

    今日推荐:微信朋友圈签名居中 适用:安卓.苹果 大家好我是小雷,又来给大家安利微信小技巧了,今天给大家分享如何让你的朋友圈签名居中显示.熟悉微信的朋友都知道,在微信设置了个性签名之后,往往会同步到朋友 ...

  7. js sdk 一键分享 微信_微信朋友圈分享自己拍的视频,一键开启这个设置,自带文字和音乐...

    你分享到朋友圈视频,还是简单的随手一拍,然后分享吗?怎样让你的分享的视频看起来高端.大气.上档次呢?其实微信视频新增编辑功能.能剪辑,能加文字和音乐,还能添加表情包.让你的视频更好看. 1.拍摄阶段 ...

  8. android导出微信朋友圈怎么发文字,微信朋友圈怎么发纯文字?看完这篇文章,你就知道该怎么操作了...

    经常使用微信聊天的朋友,一定会发现明明微信朋友圈只支持发视频或图片,然后配文字发送,为什么有些人的朋友圈可以发送纯文字呢?本期文章就教教大家如何使用微信"发纯文字"的朋友圈. 以安 ...

  9. 微信朋友圈怎么发文字?纯文字动态发布教程

    微信朋友圈怎么发文字?除了日常的聊天之外,我们还可以通过微信的朋友圈功能发表动态,分享自己的心情.比较常见的朋友圈动态都是文字+图片的类型,发布的操作也比较简单,直接通过微信->发现->朋 ...

最新文章

  1. facerec = dlib.face_recognition_model_v1()面部识别器用法
  2. 他给女朋友做了个树莓派复古相机,算法代码可自己编写,成本不到700元
  3. JavaScript原型-进阶者指南
  4. python自动登录教程_python实现校园网自动登录的示例讲解
  5. SQLite添加列的限制
  6. 那些年Android黑科技③:干大事不择手段
  7. 时间处理总结(二)oracle
  8. 简单计算机面试题库及答案_计算机专业复试面试问题含答案
  9. php中$tpl= add_member_info ;什么意思,DEDECMS会员信息在个人模板info和index的调用问题...
  10. BZOJ - 2783 树
  11. 21次课(安装软件包的三种方法、rpm包介绍、rpm工具用法、yum工具用法、 yum搭建本地仓库)...
  12. 无线网络技术学习总结
  13. 开源PDF文件处理工具箱
  14. java oracle 乱码_oracle中的数据库乱码的原因与解决
  15. OPencv 灰度直方图、直方图规定化
  16. Spring开发指南_夏昕 问题总结
  17. 计算机的发展是小报,电脑小报作品展示评价与交流.ppt
  18. 如何编写外挂 制作外挂 外挂教程
  19. 浏览器-错误 未能加载 PDF 文档
  20. 矩阵在游戏开发中的应用

热门文章

  1. Kotlin项目实战之手机影音---项目介绍、项目启动
  2. xp linux 桌面快捷方式,清理WinXP系统桌面上的快捷方式图标(转)
  3. 数字化发展-数字化转型网
  4. js获取应用服务器时间,JavaScript获取服务器端时间的方法
  5. 贵州大学计算机上机复试题,复试全程:2011贵州大学计算机学院复试经历、笔试真题和上机...
  6. 航五路服务器docker启动
  7. Linuxc基础 八
  8. c++快速实现比较三个数的大小
  9. bowtie和bowtie2使用条件区别及用法
  10. python字符串类型的计算公式