您的位置:首页 > 移动开发 > 微信开发

仿微信、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的点击背景效果如何设置以及如何解决两者点击时背景效果的冲突;

如果仅仅是通过以下代码:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: