仿微信、QQ评论点击事件
2017-10-30 12:39
465 查看
本文是针对含有类似于QQ空间、微信朋友圈模块的App,在评论布局以及评论者的点击事件部分,仿照qq微信的处理逻辑来写的一篇思路总结文章,不知道具体效果的可以打开微信朋友圈,在有评论的地方点击几下试试效果,并也同时思考下如果让你做,该怎么实现?!
先来看下具体要实现的效果:
效果一:
1.用户名和评论内容字体颜色区分;
2.用户名的点击事件;
3.整条评论的点击事件;
4.用户名的点击背景效果;
5.整条评论的点击背景效果;
效果二:
无论所点击的评论在屏幕中的任何位置,点击后都会自动紧挨评论输入框之上;
先说效果一,可能大家会觉得,用SpannableString,分分钟搞定,是的,当然是要用SpannableString,一般思路就是:
通过SpannableString.setSpan,设置整条评论内容中评论者昵称的颜色(ForegroundColorSpan)以及点击事件(ClickableSpan),接着textview.setText,最后再设置textview的点击事件textview.setOnClickListener;
其实大致就是这个思路,只不过真正实现起来就会遇到不少问题:
1.某段字符的点击事件也就是使用了ClickableSpan后,会跟textview的点击事件冲突;
2.字符的点击背景和整个textview的点击背景效果如何设置以及如何解决两者点击时背景效果的冲突;
如果仅仅是通过以下代码:
那么ClickableSpan的点击事件就会跟textView的点击事件冲突,点击时只会响应textView的点击事件,那么此时就需要重写TextView中的performClick(模拟点击),onTouchEvent,以及LinkMovementMethod中的onTouchEvent方法;上面所说的冲突,其实就是在点击(触摸touch)用户名时,最终的事件流程走到了textview的performClick并执行了onClick:
点击用户名:
textview-onTouchEvent>>LinkMovementMethod-onTouchEvent>>textview-performClick>>textview-onClick
我们需要做的就是阻断textview-performClick>>textview-onClick的事件走向,具体做法就是在LinkMovementMethod-onTouchEvent中判断是否点击了用户名(也就是设置了ClickableSpan的那段字符),然后在textview的performClick方法中直接返回true即可阻断!
当解决完点击事件冲突后,可以给整个textview设置背景,但怎么给其中的某段字符设置背景呢,答案肯定还是通过setSpan(BackgroundColorSpan),但这样一来这段字符就被设置成了固定背景,我们需要的是点击时有背景,手指抬起时就不需要有背景了,所以需要在LinkMovementMethod-onTouchEvent中的ACTION_DOWN和ACTION_UP中动态设置就可以了!
另外如果设置了ClickSpan就不需要再单独设置字符颜色了,可以直接在ClickSpan的updateDrawState方法中设置:
够罗嗦,上代码,整个重写的TextView以及LinkMovementMethod代码如下:
接下来说效果二该如何实现?我首先想到的就是如何获取评论textview在整个屏幕中的y坐标(textview离屏幕顶部的高度),然后再获取键盘高度,屏幕高度-键盘高度=键盘顶部离屏幕顶部的高度,最后在点击评论时,RecyclerView只需scroll键盘顶部离屏幕顶部的高度-textview离屏幕顶部的高度即可!
ok,首先来获取textview的坐标,其实很好做,通过textview的onTouch来获取:
getRawY是触点在整个屏幕中的y坐标,而getY是触点在该view中的y坐标,那么为什么不直接取Y = motionEvent.getRawY(),而是来了Y = motionEvent.getRawY() + (view.getHeight() - motionEvent.getY())这么个计算呢?
并不难理解,view.getHeight() 是整个textview高度,motionEvent.getY())是触点离textview顶部的高度,motionEvent.getRawY()是触点离屏幕顶部的高度,那么最终得到的结果就是textview*最底部*离屏幕顶部的高度了,对吧?!
接下来需要获取软键盘的高度,但sdk里面并没有直接提供获取键盘高度的api,那么我们就需要通过view的高度高度变化来动态获取键盘高度:
注意:mRootView是除了状态栏后的整个view,记住千万不要带上ActionBar,主题要设置为NoActionBar才能精确计算!
mRootView.getRootView().getHeight()是整个屏幕高度,mRootView.getHeight()是mRootView可见部分的高度,SysUtils.getStatusBarHeight(this)是获取状态栏高度,而commentTextY就是上面获取到的评论textview最底部离屏幕顶部的高度,再结合上上图,应该可以理解吧!因为我们还有一个输入框的高度50dp(这里尽量设置为固定高度方便计算),
所以计算时也需要减掉SysUtils.Dp2Px(this,50)像素,另外 ,大家还需要了解RecyclerView的scrollTo和scrollBy的区别,到此,就可以实现需要实现的效果了!
最后再说一些题外话,是关于整个动态列表中图片的布局问题,常见的就是九宫格布局,在前一篇博客中我介绍过,是使用RecyclerView嵌套RecyclerView做的,还是比较烦方便的,可以适合大部分人的需求,但是还有一些有特殊需求的,比如:
点击图中所画位置时需要触发item的点击事件,我们知道,如果用RecyclerView嵌套RecyclerView的做法,图片区域是一整块矩形区域,点击图中所画区域时其实还是点击的内嵌的RecyclerView,而无法触发item的点击事件,那该怎么实现呢?思来想去,最简单的办法还是动态addView最方便:
三个LinearLayout,通过判断图片数量来决定显示隐藏,以及动态创建ImageView并动态addView,看上去很low的样子,但可以很好的解决这个需求,实现起来也比较方便!
demo地址:http://download.csdn.net/download/baiyuliang2013/10044646
先来看下具体要实现的效果:
效果一:
1.用户名和评论内容字体颜色区分;
2.用户名的点击事件;
3.整条评论的点击事件;
4.用户名的点击背景效果;
5.整条评论的点击背景效果;
效果二:
无论所点击的评论在屏幕中的任何位置,点击后都会自动紧挨评论输入框之上;
先说效果一,可能大家会觉得,用SpannableString,分分钟搞定,是的,当然是要用SpannableString,一般思路就是:
通过SpannableString.setSpan,设置整条评论内容中评论者昵称的颜色(ForegroundColorSpan)以及点击事件(ClickableSpan),接着textview.setText,最后再设置textview的点击事件textview.setOnClickListener;
其实大致就是这个思路,只不过真正实现起来就会遇到不少问题:
1.某段字符的点击事件也就是使用了ClickableSpan后,会跟textview的点击事件冲突;
2.字符的点击背景和整个textview的点击背景效果如何设置以及如何解决两者点击时背景效果的冲突;
如果仅仅是通过以下代码:
SpannableString spannableString=new SpannableString("xxx"); spannableString.setSpan(new ClickableSpan() { @Override public void onClick(View view) { } },0,1,Spannable.SPAN_INCLUSIVE_EXCLUSIVE); textView.setText(spannableString); textView.setMovementMethod(LinkMovementMethod.getInstance());//启用ClickableSpan点击事件 textView.setOnClickListener{...}
那么ClickableSpan的点击事件就会跟textView的点击事件冲突,点击时只会响应textView的点击事件,那么此时就需要重写TextView中的performClick(模拟点击),onTouchEvent,以及LinkMovementMethod中的onTouchEvent方法;上面所说的冲突,其实就是在点击(触摸touch)用户名时,最终的事件流程走到了textview的performClick并执行了onClick:
点击用户名:
textview-onTouchEvent>>LinkMovementMethod-onTouchEvent>>textview-performClick>>textview-onClick
我们需要做的就是阻断textview-performClick>>textview-onClick的事件走向,具体做法就是在LinkMovementMethod-onTouchEvent中判断是否点击了用户名(也就是设置了ClickableSpan的那段字符),然后在textview的performClick方法中直接返回true即可阻断!
当解决完点击事件冲突后,可以给整个textview设置背景,但怎么给其中的某段字符设置背景呢,答案肯定还是通过setSpan(BackgroundColorSpan),但这样一来这段字符就被设置成了固定背景,我们需要的是点击时有背景,手指抬起时就不需要有背景了,所以需要在LinkMovementMethod-onTouchEvent中的ACTION_DOWN和ACTION_UP中动态设置就可以了!
另外如果设置了ClickSpan就不需要再单独设置字符颜色了,可以直接在ClickSpan的updateDrawState方法中设置:
@Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setColor(color); ds.setUnderlineText(false); }
够罗嗦,上代码,整个重写的TextView以及LinkMovementMethod代码如下:
@SuppressLint("AppCompatCustomView")
public class ClickTextView extends TextView {
Context context;
public ClickTextView(Context context) {
super(context);
this.context = context;
setTextSize(14);
setPadding(0, 5, 0, 5);
setBackgroundResource(R.drawable.view_selector);
setTextColor(Color.parseColor("#2B3A50"));
}
public ClickTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ClickTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setReplyText(WxCircleModel.ReplyModel replyModel) {
SpannableString spannableString;
if (TextUtils.isEmpty(replyModel.getReply_to_username())) {
spannableString = new SpannableString(replyModel.getReply_username() + ":" + replyModel.getReply_content());
spannableString.setSpan(new CustomClickableSpan(context, replyModel, 1), 0, replyModel.getReply_username().length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
} else {
spannableString = new SpannableString(replyModel.getReply_to_username() + " 回复 " + replyModel.getReply_username() + ":" + replyModel.getReply_content());
spannableString.setSpan(new CustomClickableSpan(context, replyModel, 2), 0, replyModel.getReply_to_username().length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new CustomClickableSpan(context, replyModel, 1), replyModel.getReply_to_username().length() + 4, replyModel.getReply_to_username().length() + 4 + replyModel.getReply_username().length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
setText(spannableString);
setMovementMethod(CustomLinkMovementMethod.getInstance());
setHighlightColor(Color.parseColor("#00000000"));
}
/**
* 自定义ClickableSpan
*/
public static class CustomClickableSpan extends ClickableSpan {
Context context;
int color;
WxCircleModel.ReplyModel replyModel;
int clicktype;
public CustomClickableSpan(Context context, WxCircleModel.ReplyModel replyModel, int clicktype) {
this.context = context;
this.replyModel = replyModel;
this.clicktype = clicktype;
color = Color.parseColor("#5B6FCA");
}
@Override
public void onClick(View widget) {
switch (clicktype) {
case 1://reply
ToastUtil.showToast(context, replyModel.getReply_username());
break;
case 2://reply to
ToastUtil.showToast(context, replyModel.getReply_to_username());
break;
}
}
@Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setColor(color); ds.setUnderlineText(false); }
}
public boolean isClick;//内部链接是否被点击
@Override
public boolean performClick() {
if (isClick) {
return true;
}
return super.performClick();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
isClick = false;
setBackgroundResource(R.drawable.view_selector);
return super.onTouchEvent(event);
}
/**
* 自定义LinkMovementMethod
*/
public static class CustomLinkMovementMethod extends LinkMovementMethod {
static CustomLinkMovementMethod sInstance;
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
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);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
buffer.setSpan(new BackgroundColorSpan(Color.parseColor("#00000000")), buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (action == MotionEvent.ACTION_DOWN) {
buffer.setSpan(new BackgroundColorSpan(Color.parseColor("#E0E0E0")), buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
if (widget instanceof ClickTextView) {
((ClickTextView) widget).isClick = true;
widget.setBackground(null);
}
return true;
} else {
Selection.removeSelection(buffer);
super.onTouchEvent(widget, buffer, event);
return false;
}
}
return Touch.onTouchEvent(widget, buffer, event);
}
public static CustomLinkMovementMethod getInstance() {
if (sInstance == null) {
sInstance = new CustomLinkMovementMethod();
}
return sInstance;
}
}
}
接下来说效果二该如何实现?我首先想到的就是如何获取评论textview在整个屏幕中的y坐标(textview离屏幕顶部的高度),然后再获取键盘高度,屏幕高度-键盘高度=键盘顶部离屏幕顶部的高度,最后在点击评论时,RecyclerView只需scroll键盘顶部离屏幕顶部的高度-textview离屏幕顶部的高度即可!
ok,首先来获取textview的坐标,其实很好做,通过textview的onTouch来获取:
textView.setOnTouchListener((view, motionEvent) -> { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) Y = motionEvent.getRawY() + (view.getHeight() - motionEvent.getY()); return false; });
getRawY是触点在整个屏幕中的y坐标,而getY是触点在该view中的y坐标,那么为什么不直接取Y = motionEvent.getRawY(),而是来了Y = motionEvent.getRawY() + (view.getHeight() - motionEvent.getY())这么个计算呢?
并不难理解,view.getHeight() 是整个textview高度,motionEvent.getY())是触点离textview顶部的高度,motionEvent.getRawY()是触点离屏幕顶部的高度,那么最终得到的结果就是textview*最底部*离屏幕顶部的高度了,对吧?!
接下来需要获取软键盘的高度,但sdk里面并没有直接提供获取键盘高度的api,那么我们就需要通过view的高度高度变化来动态获取键盘高度:
mRootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { float keyBoardH = mRootView.getRootView().getHeight() - SysUtils.getStatusBarHeight(this) - mRootView.getHeight();//键盘高度 if (keyBoardH > 300) {//键盘弹起 float viewVisibleH = mRootView.getHeight() + SysUtils.getStatusBarHeight(this)-SysUtils.Dp2Px(this,50); mRecyclerView.scrollBy(0, (int) (commentTextY - viewVisibleH)); } });
注意:mRootView是除了状态栏后的整个view,记住千万不要带上ActionBar,主题要设置为NoActionBar才能精确计算!
mRootView.getRootView().getHeight()是整个屏幕高度,mRootView.getHeight()是mRootView可见部分的高度,SysUtils.getStatusBarHeight(this)是获取状态栏高度,而commentTextY就是上面获取到的评论textview最底部离屏幕顶部的高度,再结合上上图,应该可以理解吧!因为我们还有一个输入框的高度50dp(这里尽量设置为固定高度方便计算),
所以计算时也需要减掉SysUtils.Dp2Px(this,50)像素,另外 ,大家还需要了解RecyclerView的scrollTo和scrollBy的区别,到此,就可以实现需要实现的效果了!
最后再说一些题外话,是关于整个动态列表中图片的布局问题,常见的就是九宫格布局,在前一篇博客中我介绍过,是使用RecyclerView嵌套RecyclerView做的,还是比较烦方便的,可以适合大部分人的需求,但是还有一些有特殊需求的,比如:
点击图中所画位置时需要触发item的点击事件,我们知道,如果用RecyclerView嵌套RecyclerView的做法,图片区域是一整块矩形区域,点击图中所画区域时其实还是点击的内嵌的RecyclerView,而无法触发item的点击事件,那该怎么实现呢?思来想去,最简单的办法还是动态addView最方便:
三个LinearLayout,通过判断图片数量来决定显示隐藏,以及动态创建ImageView并动态addView,看上去很low的样子,但可以很好的解决这个需求,实现起来也比较方便!
demo地址:http://download.csdn.net/download/baiyuliang2013/10044646
相关文章推荐
- 仿QQ侧滑删除,Listview上下滑动,Listview的iteam的点击事件等bug的解决
- 友盟分享成功以后,点击“留在QQ”,"留在微信"...
- 微信 全跳转二级菜单(无点击事件)
- 从点击事件看微信小程序的数据传递
- Android 实现微信,QQ的程序前后台切换:back键切换后台;点击通知栏恢复前台。
- Android仿微信、qq点击右上角加号弹出操作框
- 微信小程序 wx:for 点击事件
- 微信小程序开发-点击事件,获取元素id,页面传值 & 获取js里的数据
- 仿微博、微信、qq 点击缩略图, 查看高清图 UI 组件
- 仿QQ微信向左滑动点击删除条目的经典案例
- 微信小程序的点击元素跳转页面事件
- 微信小程序--鼠标事件 & 点击事件返回值的target分析
- Android RichText 让Textview轻松的支持富文本(图像ImageSpan、点击效果等等类似QQ微信聊天)
- jquery 商品评论 星星点击 事件
- 微信小程序 循环列表添加点击事件和样式
- 给EditText的drawableRight属性的图片设置点击事件 分类: 学习笔记 android 2015-07-06 13:20 134人阅读 评论(0) 收藏
- 微信小程序中点击View中任何位置都可以触发相应事件的解决办法
- 微信小程序之绑定点击事件实例详解
- 微信小程序例子——实现button点击事件改变数据值
- Android知识点——TaskStackBuilder(类似于微信、QQ等点击通知栏)