Android 开发实践 ViewGroup 实现左右滑出窗口(二)
接上一篇
<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 实现左右滑出窗口(二)的更多相关文章
- Android 开发实践 ViewGroup 实现左右滑出窗口(一)
利用假期把以前做的东西总结整理一下,先从简单的开始吧.实现的效果是这样的: 做了个截屏动画,比例有点不对了,凑合着看吧. 整个窗口有3部分组成,中间的主界面是个列表,左边的滑出界面是个菜单,右边的 ...
- Xamarin.Android开发实践(一)
原文:Xamarin.Android开发实践(一) 一.准备工作 1.创建一个空的解决方案,并命名为Phoneword 2.右击解决方案 新建->新建项目 并命名为Phoneword_Droid ...
- Xamarin.Android开发实践(五)
原文:Xamarin.Android开发实践(五) 一.服务的生命周期 服务与活动一样,在它的整个生命周期中存在着一些事件,下图可以很好解释整个过程以及涉及到的方法: 在真实的使用中,Service来 ...
- Xamarin.Android开发实践(四)
原文:Xamarin.Android开发实践(四) Xamarin.Android下获取与解析JSON 一.新建项目 1.新建一个Android项目,并命名为为NetJsonList 2.右击引用,选 ...
- Xamarin.Android开发实践(三)
原文:Xamarin.Android开发实践(三) 一.前言 用过Android手机的人一定会发现一种现象,当你把一个应用置于后台后,一段时间之后在打开就会发现应用重新打开了,但是之前的相关的数据却没 ...
- Xamarin.Android开发实践(二)
原文:Xamarin.Android开发实践(二) 一.准备 开始学习本教程前必须先完成该教程http://www.cnblogs.com/yaozhenfa/p/xamarin_android_qu ...
- [Android Pro] Android开发实践:自定义ViewGroup的onLayout()分析
reference to : http://www.linuxidc.com/Linux/2014-12/110165.htm 前一篇文章主要讲了自定义View为什么要重载onMeasure()方法( ...
- Android开发技巧——使用PopupWindow实现弹出菜单
在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...
- Xamarin.Android开发实践(九)
Xamarin.Android之ActionBar与菜单 一.选项卡 如今很多应用都会使用碎片以便在同一个活动中能够显示多个不同的视图.在 Android 3.0 以上的版本中,我们已经可以使用Act ...
随机推荐
- 一周一话题之三(Windows服务、批处理项目实战)
-->目录导航 一. Windows服务 1. windows service介绍 2. 使用步骤 3. 项目实例--数据上传下载服务 二. 批处理运用 1. 批处理介绍 2. 基本语法 3. ...
- String 类型的相关转换
题目: what is the result of the expression 5.4+"3.2"? 答案: 当一个运算数为原始数据类型,另外一个为字符串时,则基本数据类型会转化 ...
- 【HDU 1542】Atlantis 矩形面积并(线段树,扫描法)
[题目] Atlantis Problem Description There are several ancient Greek texts that contain descriptions of ...
- 【网络流24题】 No.6 最长不减子序列问题 (最大流)[模型:最多不相交路径]
[题意] 给定正整数序列x1 ,x2 , x3... ( 1)计算其最长不减子序列的长度 s.( 2)计算从给定的序列中最多可取出多少个长度为 s 的不减子序列.( 3) 如果允许在取出的序列中多次使 ...
- apache整合tomcat部署集群
近日,由于公司项目需要,所以学习了apache整合tomcat以及集群的一些知识. 所以做下笔记日后回顾可以用到. apache只有处理静态事物的能力, 而tomcat的强项就是处理动态的请求,所以a ...
- ZOJ --- 3516 Tree of Three
Tree of Three Time Limit: 2 Seconds Memory Limit: 65536 KB Now we have a tree and some queries ...
- [转]NHibernate之旅(7):初探NHibernate中的并发控制
本节内容 什么是并发控制? 悲观并发控制(Pessimistic Concurrency) 乐观并发控制(Optimistic Concurrency) NHibernate支持乐观并发控制 实例分析 ...
- [CODEVS1014]装箱问题
题目描述 Description 有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数). 要求n个物品中,任取若 ...
- Javascript 母羊生小羊问题,递归
农场买了一只小羊,这种羊在第一年是小羊,第二年的年底会生一只小羊,第三年不生小羊,第四年的年底还会再生下一只小羊,第五年就死掉了. 要计算N年时农场里有几只羊. [凡是碰到“一生二.二生三.三生万物” ...
- 用 localhost 访问正常,替换成 IP ,部分 CSS 或 JS 就失效了
这应该是浏览器的兼容性问题. 经测试,只要不是360浏览器的兼容模式,将 localhost 替换成 IP 无影响. 来自为知笔记(Wiz)