接上一篇

<Android 开发实践 ViewGroup 实现左右滑出窗口(一)http://www.cnblogs.com/inkheart0124/p/3532862.html>

源码: http://files.cnblogs.com/inkheart0124/PushSlider.zip

ViewGroup中touch事件的处理,有三个函数

public boolean dispatchTouchEvent(MotionEvent ev);事件派发

public boolean onInterceptTouchEvent(MotionEvent ev);事件拦截

public boolean onTouchEvent(MotionEvent ev);事件处理

(废话一句,View中只有两个,dispatchTouchEvent和onTouchEvent,ViewGroup继承了View以后增加了onInterceptTouchEvent)

一次完整的touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE....->ACTION_UP

其中对ACTION_DOWN的处理返回值比较重要,如果dispatchTouchEvent时返回false,那表示这一次touch事件我都不需要,之后的move和up都不会再传给我的ViewGroup。

@Override
public boolean dispatchTouchEvent(MotionEvent ev){
int action = ev.getAction();
boolean gdHandled = mGDetector.onTouchEvent(ev);
if((gdHandled) && (action == MotionEvent.ACTION_UP)){
log("[dispatchTouchEvent] action = " + action + "return true");
return true;
}
log("[dispatchTouchEvent] action = " + action + "return false");
return super.dispatchTouchEvent(ev);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev){
int action = ev.getAction();
boolean gdHandled = mGDetector.onTouchEvent(ev);
if((gdHandled) && (action == MotionEvent.ACTION_UP)){
log("[onInterceptTouchEvent] action = " + action + "return true");
return true;
}
log("[onInterceptTouchEvent] action = " + action + "return false");
return super.onInterceptTouchEvent(ev);
} //@Override
public boolean onTouchEvent(MotionEvent ev){
int action = ev.getAction();
return true;
}

重写这三个函数,先把event直接丢给mGDetector处理,然后在调用系统的派发机制把消息传递到子View。

注意Down消息一定不能拦截,ViewGroup主要根据move和up的情况来区别手势,当收到down消息的时候,我们还不能确定用户是要滑动呢还是要操作子view中的控件。Down消息如果被拦截,意味着这一个消息组都不会再传给子view。

==========================================================

这里我单独处理了ACTION_UP,当手势处理已经消费了此消息(即mGDetector.onTouchEvent(ev)返回true)时,不在继续派发,而是直接返回true。

这是因为当用户点住屏幕慢慢拖动(scroll手势)到左边再拖回来,而点住的位置又恰好是一个可以onClick的控件时,当用户松开屏幕,up消息将导致onClick发生。

实际上我后来取消了scroll手势,只处理fling手势。因为一个朋友体验后告诉我,没人会慢慢拖出侧边菜单,而当用户犹豫的在界面上拖来拖去的时候,多半是在犹豫要在主界面上干点什么,如果划着划着突然出来个菜单,打断了思路会觉得很不爽。我觉得他说的很有道理。

==========================================================

onTouchEvent中什么都没做,直接返回了true,这个方法会在dispatchTouchEvent函数调用super.dispatchTouchEvent(ev)时跑到,它的返回值就是super.dispatchTouchEvent(ev)的返回值,这里至少在down消息的时候必须返回true,否则就像前面说的那样,整组消息都不会再传递给我的pushslider了。

手势:

PushSlider类实现了android.view.GestureDetector.OnGestureListener接口,在前一篇的构造函数中我们创建了一个手势对象,并注册监听。

mGDetector = new GestureDetector(mContext,this);

下面就要实现OnGestureListener中的方法

public boolean onDown(MotionEvent ev);处理down消息

public void onLongPress(MotionEvent arg0);长按

public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);拖动,收到move消息时会跑到

public boolean onFling(MotionEvent eDown, MotionEvent eMove, float velocityX,float velocityY);快速滑抛

public void onShowPress(MotionEvent arg0);发生down消息后,用来显示高亮等可视化的处理

public boolean onSingleTapUp(MotionEvent ev);处理一次轻触

详细的解释就不写了,android developer官网上解释的很详细。这里我只需要处理onFling和onSingleTapUp,onScroll的代码后来没用,屏蔽掉了。

onFling 是发生donw-move-move....-up时,最后up事件时会调用到,根据入参float velocityX和float velocityY这两个速度值,决定这是不是我们想要的一次滑抛手势

 public boolean onFling(MotionEvent eDown, MotionEvent eMove,float velocityX,float velocityY) {
log("onFling " + eDown.getX() + "," + eMove.getX() +"," + velocityX); //<<<
if((eDown.getX() - eMove.getX() > FLING_DISTANCE*mDensity) && (Math.abs(velocityX)> FLING_VELOCITY)){
if(isMoveAllow(MOVE_FLAG_ALLOW_LEFT)){
flingTo(MOVE_FLAG_ALLOW_LEFT,false);
log("[onFling] true");
return true;
}
} //>>>
if((eMove.getX() - eDown.getX() > FLING_DISTANCE*mDensity) && (Math.abs(velocityX)> FLING_VELOCITY)){
if(isMoveAllow(MOVE_FLAG_ALLOW_RIGHT)){
flingTo(MOVE_FLAG_ALLOW_RIGHT,false);
log("[onFling] true");
return true;
}
}
log("[onFling] false");
return false;
}

5~11行,手势是从右向左 <<<--

14~20行,手势是从左向右 -->>>

根据eMove和eDown的相对位移,及X方向的速度,判断是否滑到下一页。

onSingleTapUp 是发生down-up这样的一次点击(轻触)时调用

 @Override
public boolean onSingleTapUp(MotionEvent ev) {
Rect focusRect = new Rect();
mChild[focusedIndex].getHitRect(focusRect);
float x = ev.getX();
int left = focusRect.left-mScroller.getCurrX();
int right = focusRect.right-mScroller.getCurrX(); if((x < left) && isMoveAllow(MOVE_FLAG_ALLOW_RIGHT)){
flingTo(MOVE_FLAG_ALLOW_RIGHT,true);
log("[onSingleTapUp] true");
return true;
}else if((x > right) && isMoveAllow(MOVE_FLAG_ALLOW_LEFT)){
flingTo(MOVE_FLAG_ALLOW_LEFT,true);
log("[onSingleTapUp] true");
return true;
}
log("[onSingleTapUp] false");
return false;
} }

3,4行,取得当前focus的子view的区域;

6,7行,这里取到的HitRect是相对与viewgroup的0,0点的相对坐标。需要换算成实际坐标,mScroller.getCurrX()是viewgroup的x位置相对于屏幕原点的值。

由于我有一个滑出子view是半屏的,当这个半屏的设置菜单被唤出后,我希望轻触一下菜单以外的区域 就可以关闭菜单回到主界面。重写onSingleTapUp方法就是为了处理这种情况。

flingTo,关于实现滑动,其实有很多方法,最直观的就像我们在onLayout中做的那样,重新算好每个子View的位置,然后layout一遍。

但我觉得用view自己的Scroller类是最简单最省心的。

 private void flingTo(int direct,boolean fast){
if(mForbidden)
return; int startX = getScrollX();
int endX=;
int dx; if(direct == MOVE_FLAG_ALLOW_LEFT){
focusedIndex ++; if(focusedIndex == SLIDER_PAGE_MIDDLE)
endX = ;
else if(focusedIndex == SLIDER_PAGE_RIGHT)
endX = mRightPosX; dx = endX - startX;
mScroller.startScroll(startX, , dx, ,(fast?:));
}else{
focusedIndex --;
if(focusedIndex == SLIDER_PAGE_MIDDLE)
endX = ;
else if(focusedIndex == SLIDER_PAGE_LEFT)
endX = mLeftPosX; dx = endX - startX;
mScroller.startScroll(startX, , dx, ,(fast?:));
}
log("flingTo " + startX);
invalidate();
changeFocus();
}

5行,startx,得到当前ViewGroup的位置(相对于屏幕原点);

10~15行,切换焦点,根据新的焦点得到endX,及滑动后ViewGroup的位置;

18行,直接调用Scroller的startScroll方法,就开始滑动了,很简单吧!

startScroll原型:public void startScroll(int startX, int startY, int dx, int dy, int duration)

5个入参意义一目了然。

flingTo函数的两个入参 direct是滑动方向,还有一个boolean fast,表示滑动速度,用它来确定startScroll的duration,100和250是实测的经验值。

当滑抛手势唤出或关闭半屏菜单时,fast=false。让用户看到一个清晰的滑动过程。

当用户按menu或cancel键的时候,fast=true。快速唤出或关闭界面。

menu键和canel键的处理,我没有把它们定义在PushSlider的管理范畴内。这两个键值仍然是有activity处理,PushSlider只需要给出一个public的方法。

简单的说,PushSlider就管自己的3个子View,他们的显示,位置,滑动,焦点。具体功能上的东东全都是activity的活。

因此PushSlider除了让activity监听焦点变化外(见上一篇),还需要提供一些查询和操作接口,比如查询当前焦点,查询当前是否处于滑动中。

比较重要的操作有两个:

public void pushForbid(boolean forbid){
  mForbidden = forbid;
 }

某些情况下,activity可能想不让用户滑动切换焦点,就可以pushForbid。

另一个public void moveToPageById(int index),activity可直达某个子View,比如前面提到的 按menu键唤出设置菜单的子View。

具体代码不贴了,仍然是调用flingTo(fast)的模式。

PushSlider搞定了,看一下activity中怎么用它。

layout/main.xml

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
> <com.meflame.where.PushSlider android:id="@+id/push_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
>
<!-- left page -->
<include layout="@layout/menu" />
<!-- middle page -->
<include layout="@layout/record_list" />
<!-- right page <include layout="@layout/map" /> -->
</com.meflame.where.PushSlider> <ViewStub
android:id="@+id/waiting_stub"
android:layout="@layout/waiting_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</RelativeLayout>

6~15行,加入PushSlider,插入三个子View,分别是

<include layout="@layout/menu" />  左边菜单

<include layout="@layout/record_list" /> 中间主界面列表

<include layout="@layout/map" /> 右边最初设计是地图,后来没这么做,改成了一个独立的activity,因为地图本身就要获取各种各样的手势。

这三个子View的layout按照一般的layout文件写就ok,可以自己定义。

在activity.java中

 public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); /* push slider 初始化 start*/
mPushView = (PushSlider)findViewById(R.id.push_view);
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
mPushView.setPageWidth(PushSlider.SLIDER_PAGE_LEFT, screenWidth/2);
mPushView.setPageWidth(PushSlider.SLIDER_PAGE_RIGHT, screenWidth);
mPushView.setOnPageChangedListener(this);
/* push slider 初始化 end*/ /* 其他初始化 */ }

5行,把main.xml设为contect view;

7~13行,获得PushSlider对象句柄,设置左右两个view的宽度,设置焦点切换监听。

oh~~~ 全篇完~~

新年攒人品,稍后发源码,还不知道怎么上传源码呢

Android 开发实践 ViewGroup 实现左右滑出窗口(二)的更多相关文章

  1. Android 开发实践 ViewGroup 实现左右滑出窗口(一)

    利用假期把以前做的东西总结整理一下,先从简单的开始吧.实现的效果是这样的:   做了个截屏动画,比例有点不对了,凑合着看吧. 整个窗口有3部分组成,中间的主界面是个列表,左边的滑出界面是个菜单,右边的 ...

  2. Xamarin.Android开发实践(一)

    原文:Xamarin.Android开发实践(一) 一.准备工作 1.创建一个空的解决方案,并命名为Phoneword 2.右击解决方案 新建->新建项目 并命名为Phoneword_Droid ...

  3. Xamarin.Android开发实践(五)

    原文:Xamarin.Android开发实践(五) 一.服务的生命周期 服务与活动一样,在它的整个生命周期中存在着一些事件,下图可以很好解释整个过程以及涉及到的方法: 在真实的使用中,Service来 ...

  4. Xamarin.Android开发实践(四)

    原文:Xamarin.Android开发实践(四) Xamarin.Android下获取与解析JSON 一.新建项目 1.新建一个Android项目,并命名为为NetJsonList 2.右击引用,选 ...

  5. Xamarin.Android开发实践(三)

    原文:Xamarin.Android开发实践(三) 一.前言 用过Android手机的人一定会发现一种现象,当你把一个应用置于后台后,一段时间之后在打开就会发现应用重新打开了,但是之前的相关的数据却没 ...

  6. Xamarin.Android开发实践(二)

    原文:Xamarin.Android开发实践(二) 一.准备 开始学习本教程前必须先完成该教程http://www.cnblogs.com/yaozhenfa/p/xamarin_android_qu ...

  7. [Android Pro] Android开发实践:自定义ViewGroup的onLayout()分析

    reference to : http://www.linuxidc.com/Linux/2014-12/110165.htm 前一篇文章主要讲了自定义View为什么要重载onMeasure()方法( ...

  8. Android开发技巧——使用PopupWindow实现弹出菜单

    在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...

  9. Xamarin.Android开发实践(九)

    Xamarin.Android之ActionBar与菜单 一.选项卡 如今很多应用都会使用碎片以便在同一个活动中能够显示多个不同的视图.在 Android 3.0 以上的版本中,我们已经可以使用Act ...

随机推荐

  1. 教你如何监控 Apache?

    什么是 Apache? Apache 是一款 HTTP 服务器软件,现在更名为 "http",而 Apache 则成了一个(包含httpd的项目)巨大的基金组织,根据习惯后文都用 ...

  2. 关于 OneAPM Cloud Test DNS 监控的几个重要问题

    你注意到了吗?OneAPM Cloud Test 已经全面开启支持 DNS 监控了! CT 产品自上线以来一直致力于产品完善,希望能够尽可能全面地满足用户需求,为您提供完美的用户体验.目前 Cloud ...

  3. AJAX里调用AJAX,作定时进度刷新

    这个确实搞了一段时间,但成就感有啦... 哈哈,这个自动部署平吧,异步队列CELERY+REDIS,发布进度实时AJAX的技术点全部打通!!! 而获取实时进度,我用的是RESTFUL FRAMEWOR ...

  4. cisco上的RIP V2加上MD5认证配置测试成功

    R1: Router#show run Building configuration... Current configuration : bytes ! version 12.3 service t ...

  5. Lua table pair和ipair区别

    官方描述: ipairs (t) Returns three values: an iterator function, the table t, and 0, so that the constru ...

  6. 【转】 Android——eclipse共享library以及导出jar包

    原文网址:http://blog.csdn.net/jscese/article/details/36627195 android的apk在在eclipse上进行开发的时候,有时候需要import其它 ...

  7. Java I/O学习(一)

    写作目的 自学Java,Java中流的概念比较复杂,故专门作一整理.期望开始学习的童鞋,看完本文后对Java流有一个大致的认识.主要分三个小篇: 一.概述.输入/输出字节流 二.输入/输出字符流.装饰 ...

  8. Web性能测试基本性能指标

    Web性能测试的部分概况一般来说,一个Web请求的处理包括以下步骤: (1)客户发送请求 (2)web server接受到请求,进行处理: (3)web server向DB获取数据: (4)webse ...

  9. python进度1

    Python 错误和异常 异常参数: 3.4与2.7有些不同 3.4中 try: x except NameError as e: print(type(e)) print(e) 运行结果: < ...

  10. (转载)VC/MFC 工具栏上动态添加组合框等控件的方法

    引言 工具条作为大多数标准的Windows应用程序的 一个重要组成部分,使其成为促进人机界面友好的一个重要工具.通过工具条极大方便了用户对程序的操作,但是在由Microsoft Visual C++开 ...