手机QQ空间浏览好友动态时,可以直接对动态评论,点击某条评论,动态列表自动滚动,使输入框刚好在该评论下面,而不会覆盖住评论内容。如下图所示,
首先要实现输入框刚好在输入面板上面,且动态列表不会被挤上去。可以使用对话框的形式,这样输入框不会影响原有的布局,弹出的对话框布局如下所示,点击EditText时,红色块的内容将位于输入法上面。在这里我把ScrollerView的背景设为透明。其实QQ空间的输入框也是以对话框的形式弹出,因为弹出对话框时,原本全屏的布局突然多了一条状态栏。
接着就是要让点击的评论刚好在评论输入框上面,可以使用ListView.smoothScrollBy(distance, duration)让列表滚动到相应位置,但是难点是如何计算出滚动的距离distance。
如下图所示,我们主要计算弹出输入面板后的输入框和评论之间的距离(绿色线条的长度)。通过View.getLocationOnScreen()方法可以计算出view在屏幕上的坐标(x,y),那么列表滑动的距离distance = listview的y坐标 - 输入框的y坐标。
/**
* 弹出评论对话框
*/
public static void inputComment(final Activity activity, final ListView listView,
final View btnComment, final User receiver,
final InputCommentListener listener) {
final ArrayList commentList = (ArrayList) btnComment.getTag(KEY_COMMENT_SOURCE_COMMENT_LIST);
String hint;
if (receiver != null) {
if (receiver.mId == MainActivity.sUser.mId) {
hint = "我也说一句";
} else {
hint = "回复 " + receiver.mName;
}
} else {
hint = "我也说一句";
}
// 获取评论的位置,不要在CommentDialogListener.onShow()中获取,onShow在输入法弹出后才调用,
// 此时btnComment所属的父布局可能已经被ListView回收
final int[] coord = new int[2];
if (listView != null) {
btnComment.getLocationOnScreen(coord);
}
//弹出评论对话框
showInputComment(activity, hint, new CommentDialogListener() {
@Override
public void onClickPublish(final Dialog dialog, EditText input, final TextView btn) {
final String content = input.getText().toString();
if (content.trim().equals("")) {
Toast.makeText(activity, "评论不能为空", Toast.LENGTH_SHORT).show();
return;
}
btn.setClickable(false);
final long receiverId = receiver == null ? -1 : receiver.mId;
Comment comment = new Comment(MainActivity.sUser, content, receiver);
commentList.add(comment);
if (listener != null) {
listener.onCommitComment();
}
dialog.dismiss();
Toast.makeText(activity, "评论成功", Toast.LENGTH_SHORT).show();
}
/**
* onShow在输入法弹出后才调用
* @param inputViewCoordinatesInScreen 输入框距离屏幕顶部(不包括状态栏)的位置[left,top]
*/
@Override
public void onShow(int[] inputViewCoordinatesInScreen) {
if (listView != null) {
// 点击某条评论则这条评论刚好在输入框上面,点击评论按钮则输入框刚好挡住按钮
int span = btnComment.getId() == R.id.btn_input_comment ? 0 : btnComment.getHeight();
listView.smoothScrollBy(coord[1] + span - inputViewCoordinatesInScreen[1], 1000);
}
}
@Override
public void onDismiss() {
}
});
}
最后要弄的就是评论中,评论者、接收者和评论内容用不同的颜色显示,且点击时有点击效果。这里可以通过下面的代码实现,
TextView.setText(Html.fromHtml(content, imageGettor, tagHandler))
自定义标签,通过自定义的标签解析类Html.TagHandler来响应不同标签的操作。这里我自定义了commentator、receiver、content标签,列入一条评论的字符串形式为“
/**
* 显示评论的自定义Html标签解析器
*/
public class CustomTagHandler implements Html.TagHandler {
//自定义标签
public static final String TAG_COMMENTATOR = "commentator"; // 评论者
public static final String TAG_RECEIVER = "receiver"; // 评论接收者,即对谁评论
public static final String TAG_CONTENT = "content"; // 评论内容
...
/**
* 解析自定义标签
*/
@Override
public void handleTag(boolean opening, String tag, final Editable output, XMLReader xmlReader) {
if (!tag.toLowerCase().equals(TAG_COMMENTATOR) && !tag.toLowerCase().equals(TAG_RECEIVER)
&& !tag.toLowerCase().equals(TAG_CONTENT)) {
return;
}
if (opening) { //开始标签
// 记录标签内容的起始索引
int mStart = output.length();
if (tag.toLowerCase().equals(TAG_COMMENTATOR)) {
mMaps.put(KEY_COMMENTATOR_START, mStart);
} else if (tag.toLowerCase().equals(TAG_RECEIVER)) {
mMaps.put(KEY_RECEIVER_START, mStart);
} else if (tag.toLowerCase().equals(TAG_CONTENT)) {
mMaps.put(KEY_CONTENT_START, mStart);
}
} else { // 结束标签
int mEnd = output.length(); //标签内容的结束索引
if (tag.toLowerCase().equals(TAG_COMMENTATOR)) {
int mStart = mMaps.get(KEY_COMMENTATOR_START);
output.setSpan(new TextAppearanceSpan(mContext, R.style.Comment),
mStart, mEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
output.setSpan(mCommentatorSpan, mStart, mEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (tag.toLowerCase().equals(TAG_RECEIVER)) {
int mStart = mMaps.get(KEY_RECEIVER_START);
output.setSpan(new TextAppearanceSpan(mContext, R.style.Comment),
mStart, mEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
output.setSpan(mReceiverSpan, mStart, mEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (tag.toLowerCase().equals(TAG_CONTENT)) {
int mStart = mMaps.get(KEY_CONTENT_START);
output.setSpan(new TextAppearanceSpan(mContext, R.style.Comment),
mStart, mEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
output.setSpan(mContentSpan, mStart, mEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
...
}
在ListView中,因为Item里面的子View使用了ClickableSpan,导致ListView的OnItemClickListener失效,解决的方法可以在getView中加入下列代码,阻止ListView里面的子View拦截焦点。
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView != null) {
//防止ListView的OnItemClick与item里面子view的点击发生冲突
((ViewGroup) convertView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
}
设置点击的文字背景色:
难点都已经解决了,最后实现的效果如下:
完整demo:https://github.com/1993hzw/QZoneComment
由于本人的毕业论文的题目为基于android的QQ空间模拟系统,所以对android版的QQ空间研究了不少,山寨了其核心功能。之后有机会继续跟大家分享经验,谢谢支持。