最近公司配套智能自行车的App要做发布骑行活动的功能,这就不可避免的要模仿微信朋友圈的很多功能了,这篇文章主要介绍如何做出和微信一样的列表评论效果。
下面先放出实际运行效果:
首先介绍实现这个功能需要经历以下几步:
1)封装方法,通过代码控制弹出和隐藏系统输入法;
2)封装方法,获得手机键盘输入法的高度;
3)代码控制ListView上下滚动指定的距离;
4)自定义一个评论的输入Editext框和发送按钮的布局放在activity中,要求高度写死,并且 alignParentButton 设置为true,在系统输入法显示的时候设置为 Visible,平时设置为 Gone(这个太简单,下面就不写了);
5)在用户点击评论时,计算出ListView需要上下滚动的距离;
6)封装方法,通过代码模拟点击事件确保评论输入的EditText获得焦点;
下面就按照以上知识点的顺序上代码:
1)弹出和隐藏系统输入法:
(1)显示系统输入法的关键代码(如果此时输入法已经显示,调用它就隐藏输入法)
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
(2)强制隐藏系统输入法的关键代码
InputMethodManager imm = (InputMethodManager) mInstance.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(windowToken, 0);
// weindowToken参数可以通过页面上任意控件的 getWindowToken() 方法获得,我一般习惯用listview调用此方法
2)获得手机键盘输入法的高度
由于这个高度不是统一的,所以每次显示系统输入法的时候最好都要调用它进行测量,以下为关键代码:
// 当输入法弹出完毕之后,获取当前页面窗口的显示范围,并以此计算输入法的高度
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); // 获取当前手机屏幕显示内容的范围
// 以下两行代码用于获取手机屏幕整体高度
WindowManager windowManager = (WindowManager) context.getSystemService(context.WINDOW_SERVICE);
int screenHeight = windowManager.getDefaultDisplay().getHeight(); // 得到手机屏幕整体高度
// 屏幕高度 - 当前正在显示内容范围的高度 = 键盘实际高度
int keyBoardHeight = screenHeight - rect.bottom;
方法调用注意事项:由于系统键盘弹出需要时间,因此需要在调用弹出系统输入法之后的 500毫秒 才能开始获取键盘高度,键盘处于隐藏状态时 keyBoardHeight 为 0
3)代码控制ListView上下滚动指定的距离
(这部分代码出自百度经验:http://jingyan.baidu.com/article/e6c8503c0564cbe54f1a18e7.html)
获得了键盘的高度后,就要让 ListView 被评论的条目在键盘弹出后正好置于评论输入框的上方了。但是ListView并没有暴露此方法,因此我们需要反射ListView的 trackMotionScroll() 方法来控制ListView控件的滚动距离了。
以下是关键代码:
/**
* ListView自动向上移动需调用的方法
* @param listview 需要自动向上移动的ListView控件
* @param activity ListView 所在的Activity
* @param y ListView 需要滚动的像素距离,正数为向上滚动,负数向下滚动
*/
public static void scrollVertical(final ListView listView, Activity activity, final int y) {
if (listView == null)
return;
activity.runOnUiThread(new Runnable() { //执行自动化测试的时候模拟滑动需要进入UI线程操作
@Override
public void run() {
invokeMethod(listView, "trackMotionScroll", new Object[]{-y, -y}, new Class[]{int.class, int.class});
}
});
}
// 以下是上面代码片段 invokeMethod 方法的实际实现
/**
* 遍历当前类以及父类去查找方法,例子,写的比较简单 (ListView自动向上移动方法中调用)
* @param object
* @param methodName
* @param params
* @param paramTypes
* @return
*/
public static Object invokeMethod(Object object, String methodName, Object[] params, Class[] paramTypes) {
Object returnObj = null;
if (object == null) {
return null;
}
Class cls = object.getClass();
Method method = null;
for (; cls != Object.class; cls = cls.getSuperclass()) { //因为取的是父类的默认修饰符的方法,所以需要循环找到该方法
try {
method = cls.getDeclaredMethod(methodName, paramTypes);
break;
} catch (NoSuchMethodException e) {
// e.printStackTrace();
} catch (SecurityException e) {
// e.printStackTrace();
}
}
if (method != null) {
method.setAccessible(true);
try {
returnObj = method.invoke(object, params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return returnObj;
}
5)ListView上下滚动距离的计算 知道如何控制ListView上下滚动之后,我们就可以开始计算ListView在输入法弹出时应当滚动多少距离了。核心思路如下:
(1)借助评论按钮的点击事件获取评论按钮在屏幕上的大致位置
(2)通过ListView的条目布局获得评论按钮距离条目底部的像素值,并反推出当前条目底部在手机屏幕上的位置
(3)把当前条目底部在手机屏幕上的位置和系统输入法的位置坐对比,计算出差值,此差值就是ListView需要移动的距离
以下为关键代码:
// 注意,以下代码在 ListView 的 Adapter 下的 getView() 方法中!
// 当回复按钮按下的时候
viewHolder.ll_replay_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 首先获取这个点击事件发生的位置
int[] location = new int[2];
v.getLocationOnScreen(location);
int onClickY = location[1];
// onClickY就是这个点击事件在Y轴的发生位置,也就是被点击的评论按钮在屏幕Y轴的大体位置
/**
* 根据自己ListView条目的布局来确定评论按钮距离当前Item底部的距离
* 为方便阅读,我统一这个距离为10dp
*/
// 根据不同的手机分辨率算出 10dp 所对应的像素值
float density = context.getResources().getDisplayMetrics().density;
int px = (int) (10 * density + .5); // 获得 10dp 所对应的像素值
// 按钮的Y轴位置 + 评论按钮距离条目底部的像素长度 = 条目底部的Y轴位置
int itemButtonY = onClickY + px;
// 调用方法显示系统输入法(第一步已经有关键代码了,省略)
// 调用方法获得键盘的高度(第二步已经有关键代码了,省略)
// 开始计算ListView需要上下滚动的距离
WindowManager windowManager = (WindowManager) context.getSystemService(context.WINDOW_SERVICE);
int screenHeight = windowManager.getDefaultDisplay().getHeight(); // 得到手机屏幕整体高度
// listView需要上下滚动的距离 = 被评论条目底部的Y轴位置 - (屏幕高度 - 输入法高度 - 自定义评论输入布局高度)
int listViewScrollDistance = itemButtonY - (screenHeight - keyBoardHeight - replyInputLayoutHeight);
// 调用方法让ListView滚动到指定的位置(第三步已经有关键代码了,省略)
}
});
注意事项:
(1)假如当前用户评论的是ListView中最后一个条目,则无需再计算它的滚动位置了,直接调用 scrollToPosition(int position) 方法将条目滚动到最后即可。
(2)此外,我们还需要在 ListView 的最后加上一个固定的 footerView,这个 view 的高度必须和评论输入布局的高度一样,如果不加这个 footerView 最后一个 item 在被评论的时候会被评论输入布局遮挡住部分内容
6)代码模拟点击事件使EditText获取焦点
由于在部分手机上发现EditText并不会自动获取焦点,因此我使用了这个简单暴力的方式 ╮(╯▽╰)╭
使用很简单,输入想要被点击的 View,然后随意输入这个View的相对坐标,然后它就被点击了,很好用的方法
上关键代码:
/**
* 模拟控件点击事件
* @param view 控件(这里指的就是评论的EditText)
* @param x 控件相对x坐标
* @param y 控件相对y坐标
*/
public static void setSimulateClick(View view, float x, float y) {
long downTime = SystemClock.uptimeMillis();
final MotionEvent downEvent = MotionEvent.obtain(downTime, downTime,
MotionEvent.ACTION_DOWN, x, y, 0);
downTime += 1000;
final MotionEvent upEvent = MotionEvent.obtain(downTime, downTime,
MotionEvent.ACTION_UP, x, y, 0);
view.onTouchEvent(downEvent);
view.onTouchEvent(upEvent);
downEvent.recycle();
upEvent.recycle();
}
注意事项:为了保证输入框在获得焦点的时候,光标就在所有文字的后面,建议最好把评论EditText的宽度和高度写死。这样,我们就可以获取到评论EditText最右下角的坐标了,点击这个坐标就可以确保光标永远在所有文字之后。
到此为止,所有的主要功能就算完成了。隐藏系统输入法的逻辑各位可以写在 onBackPress() 方法中,以及复写的ListView 的 onTouchListener 方法中。
由于公司代码不便公开,暂时还不能提供Demo,等有时间整理代码的时候我会公布给大家。