触摸屏做出动作已经成为主流手机的操作规范,但是你想过怎么开发触摸屏手机的手势操作吗?本文将向各位介绍Android触摸屏手机开发的textview及listview对比验证。
View可以通过onTouchEvent收到触摸屏幕事件
我们可以通过View.setOnTouchListener()设置事件监听器
或者override onTouchEvent()来拦截这些事件
在拦截函数中判断触摸点的轨迹及运动速度就可以判断出是什么手势。
Android系统提供了GestureDetector来方便手势的判断,即:在拦截函数中每次touchevent都把事件作为参数调用GestureDetector.onTouchEvent(),当有手势被识别出后,就会通知调用者。
为了能通知到调用者,GestureDetector在构造时要求传入一个实现了OnGestureListener接口的对象,通过此对象就可以接收到各种手势通知了。
首先,用textview来实验:
用一个ViewFlipper放置两个Textview,当手指在屏幕上左右移动时进行进行切换。
由于textview本身不会处理touchevent,而是会将其继续上传,所以textview布局时layout_width及layout_height没有影响。
如果touch事件发生在textview上会继续上传到viewflipper;如果发生在viewflipper上那么就直接处理。
所以我们应该拦截发生在ViewFlipper上的touchEvent,并进行处理。
代码如下,在onCreate中完成界面布局及事件拦截函数设置:
Java代码
- //创建一个ViewFlipper
- mVf = new ViewFlipper(this);
- //ViewFlipper打开长点击支持。如果不打开,无法收到长时间点击,那么手势判断就无从进行。
- mVf.setLongClickable(true);
- //拦截ViewFlipper的touch事件,并使用GestureDetector.onTouchEvent来处理
- mVf.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return mVfDetector.onTouchEvent(event);
- }
- });
- //添加一个textview。textview不能setLongClickable(true),
- //如果设置了那么发生在textview上的touch事件就无法传给viewflipper,
- //无法被手势识别对象处理了。
- TextView tv = new TextView(this);
- tv.setText("TextView 1");
- tv.setBackgroundColor(0xffffffff); //设置一个白色背景,方便看到textview区域
- mVf.addView(tv,new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
- //再添加一个textview
- TextView tv2 = new TextView(this);
- tv2.setText("TextView 2");
- tv2.setBackgroundColor(0xffffffff);
- mVf.addView(tv2,new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
- //将viewFlipper作为Activity的主view显示
- setContentView(mVf);
在监听器中用到了GestureDetector对象,其是一个类成员对象,用如下代码创建:
Java代码
- private GestureDetector mVfDetector = new GestureDetector(new OnGestureListener() {
- //手指在屏幕上移动距离小于此值不会被认为是手势
- private static final int SWIPE_MIN_DISTANCE = 120;
- //手指在屏幕上移动速度小于此值不会被认为手势
- private static final int SWIPE_THRESHOLD_VELOCITY = 200;
- //手势识别函数,到此函数被系统回调时说明系统认为发生了手势事件,
- //我们可以做进一步判定。
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- //如果第1个坐标点大于第二个坐标点,说明是向左滑动
- //滑动距离以及滑动速度是额外判断,可根据实际情况修改。
- if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE &&
- Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
- //left
- Log.i("GestureDemo", "ViewFlipper left");
- mVf.showNext();
- return true;
- }else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE &&
- Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
- //right
- Log.i("GestureDemo", "ViewFlipper right");
- mVf.showPrevious();
- return true;
- }
- return false;
- }
- ... ...
- ... ...
- });
- }
#p#
我们将前文中添加第二个textview的改为添加Listview,代码如下:
Java代码
- ListView lv = new ListView(this);
- lv.setBackgroundColor(0xff808080);
- final String[] items = {"one", "two", "three"};
- lv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items));
- mVf.addView(lv,new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
执行ap,滑动屏幕切换到第二屏,可以看到第二屏被换成了一个ListView,并且没有填充完整个屏幕,这时如果在底部非ListView区域向左滑动,仍然可以切换到***屏,但是在Listview区域滑动就没有效果了,因为touch事件被Listview处理了,ViewFlipper无法收到touch事件也就无法进行手势判断。
我们给ListView也增加一个touch事件监听器,代码如下:
Java代码
- lv.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return mVfDetector.onTouchEvent(event);
- }
- });
此时在ListView上滑动也能实现屏幕切换了。
我们再给Listview增加响应点击事件的处理,代码如下:
Java代码
- lv.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> arg0, View arg1,
- int arg2, long arg3) {
- new AlertDialog.Builder(MainActivity.this)
- .setMessage(items[arg2])
- .create()
- .show();
- }
- });
当点击Listview的条目的时候,就会弹出窗口显示点击了哪一项,此时的代码可参见附件1。
截止到此时,似乎Listview支持左右滑动的操作完成了,但实际上仍有两个问题:
首先就是滑动时,Listview有时会有条目被高亮,这个问题倒还不是太严重。
再次就是ContextMenu每次滑动都会被激活,我们可以通过代码验证,创建ListView的代码稍作修改:
Java代码
- //使Listview长一些
- final String[] items = {"one", "two", "three", "four", "five", "six", "sevent", "eight", "nine"};
- registerForContextMenu(lv);
另外Activity的代码增加:
Java代码
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- menu.add("Menu 1");
- menu.add("Menu 2");
- menu.add("Menu 3");
- super.onCreateContextMenu(menu, v, menuInfo);
- }
当我们在ListView的某一项上长按时就会弹出菜单,但是当滑动时,即使屏幕切换到了***屏,此菜单仍然会弹出。
为了解决上面的问题,我尝试了以下办法:
1.在ListView的touch事件监听函数中始终返回true,吃掉所有事件,这样的修改导致不能相应点击,ListView不能上下滑动,此路不通。
2.在GestureDetector的fling函数中向ListView发送一个MotionEvent.ACTION_CANCEL事件,总是空指针异常,怀疑是因为touch事件没有被ListView处理过,其内部成员状态异常,于是我从ListView继承实现了一个ListView,在onTouchEvent中呼叫super.onTouchEvent,但是发送MotionEvent.ACTION_CANCEL事件时仍然是空指针异常,再次失败。
3.GestureDetector的onDown函数返回true,吃掉down事件,此时点击时没有高亮项了,切换时contextmenu也不被trigger了,但是长按也无法弹出contextmenu了。
为了能弹出contextMenu,那么就要在GestureDetector的onLongPress函数中调用ListView.showContextMenuForChild()来弹出菜单。
那么GestureDetector就与ViewFlipper的不通用了。
所以我重新由ListView继承实现了一个类,这个类自身绑定了一个GestureDetector:
Java代码
- @Override
- public boolean onDown(MotionEvent e) {
- return true; //吃掉Down事件
- }
- @Override
- public void onLongPress(MotionEvent e) {
- System.out.println("Listview long press");
- int position = pointToPosition((int)e.getX(), (int)e.getY());
- if( position != ListView.INVALID_POSITION) {
- View child = getChildAt(position - getFirstVisiblePosition());
- if(child != null) GestureListView.this.showContextMenuForChild(child);
- }
- }
另外为了能够显示contextmenu时在哪一项上激活的,在Activity增加函数:
Java代码
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
- System.out.println("View " + info.position + " context menu activited.");
- return super.onContextItemSelected(item);
- }
从LogCat即可看到打印输出。
此时此ListView可以响应手势、可以响应点击、可以弹出菜单,基本的功能已经满足,另外再微调一下ondown函数,当点击时可以高亮一下。
【编辑推荐】