zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

Android左右滑出菜单实例分析

Android实例 分析 菜单 左右
2023-06-13 09:15:00 时间
现在的Android应用,只要有一个什么新的创意,过不了多久,几乎所有的应用都带这个创意。这不,咱们公司最近的一个持续性的项目,想在首页加个从左滑动出来的菜单,我查阅网上资料,并自己摸索,实现了左、右两边都能滑出菜单,并且,左、右菜单中,都可以加ListView等这类需要解决GestureDetector冲突的问题(如在首页面中,含有ListView,上下滚动时,左右不动,相反,左右滑动菜单时,上下不动,听着头就大了吧!)

先上几张图,给大家瞧瞧,对整体有个了解:
 
一、首页布局:
复制代码代码如下:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--主布局-->
<RelativeLayout
android:id="@+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
android:padding="5dip">
<ImageView
android:id="@+id/ivMore"
android:src="@drawable/nav_more_normal"
android:contentDescription="@string/img_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="10dip"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/title"
android:textSize="20sp"
android:textColor="#000000"/>
<ImageView
android:id="@+id/ivSettings"
android:src="@drawable/nav_setting_normal"
android:contentDescription="@string/img_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_marginRight="10dip"/>
</RelativeLayout>

<ImageView
android:src="@drawable/picture"
android:contentDescription="@string/img_desc"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/titlebar"/>
</RelativeLayout>
<!--左侧菜单导航-->
<RelativeLayout
android:id="@+id/leftLayout"
android:layout_width="140dip"
android:layout_height="match_parent"
android:background="#000000">
<RelativeLayout
android:id="@+id/leftTitlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/grey21"
android:padding="5dip">
<TextView
android:layout_marginLeft="5dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="@string/leftNav"
android:textSize="20sp"
android:textColor="#ffffff"/>
</RelativeLayout>
<com.chris.lr.slidemenu.LayoutRelative
android:id="@+id/layoutSlideMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/leftTitlebar">
<ListView
android:id="@+id/listMore"
android:cacheColorHint="#00000000"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.chris.lr.slidemenu.LayoutRelative>
</RelativeLayout>
<!--右侧菜单导航-->
<RelativeLayout
android:id="@+id/rightLayout"
android:layout_width="140dip"
android:layout_height="match_parent"
android:background="#000000">
<RelativeLayout
android:id="@+id/rightTitlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gold"
android:padding="5dip">
<TextView
android:layout_marginLeft="5dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="@string/right_title"
android:textSize="20sp"
android:textColor="#ffffff"/>
</RelativeLayout>

<TextView
android:text="@string/rightNav"
android:textColor="#ff00ff"
android:textSize="18sp"
android:layout_width="match_parent"
android:layout_height="30dip"
android:layout_below="@id/rightTitlebar"
android:background="#000000"/>
</RelativeLayout>
</RelativeLayout>

布局很简单,我个人比较推荐用RelativeLayout,因为这个是几个Layout中,性能最好的,而LinearLayout则不好,原因在于,某个子视图的宽高变动,会引起这个布局中其它地方也需要重新调整。

布局中,有com.chris.lr.slidemenu.LayoutRelative这个自定义控件是继承RelativeLayout的,里面只是加了些手势的处理,它的作用实际上就是最开始讲到的,如果含有ListView这类需要判断手势的,则就用到它,先由它来判断,然后在视情况是否拦截由自己来处理。

二、自定义控件:
复制代码代码如下:

packagecom.chris.lr.slidemenu;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.GestureDetector;
importandroid.view.GestureDetector.SimpleOnGestureListener;
importandroid.view.MotionEvent;
importandroid.widget.RelativeLayout;
publicclassLayoutRelativeextendsRelativeLayout{
privatestaticfinalStringTAG="ChrisSlideMenu";
privateGestureDetectormGestureDetector;
privatebooleanbLockScrollX=false;
privatebooleanbTouchIntercept=false;

privateOnScrollListenermOnScrollListenerCallback=null;

publicLayoutRelative(Contextcontext){
this(context,null);
}

publicLayoutRelative(Contextcontext,AttributeSetattrs){
this(context,attrs,0);
}

publicLayoutRelative(Contextcontext,AttributeSetattrs,intdefStyle){
super(context,attrs,defStyle);

mGestureDetector=newGestureDetector(newLayoutGestureListener());
}

/**
*设置滚动监听接口
*@paraml
*/
publicvoidsetOnScrollListener(OnScrollListenerl){
mOnScrollListenerCallback=l;
}

/*
*Progress:
*1.重载dispatchTouchEvent,将消息传递给GestureDetector;
*2.重载手势中onDown和onScroll两个函数;
*3.在onDown中,默认对水平滚动方向加锁;
*4.在onScroll中,判断e1与e2的水平方向与垂直方向距离:
*a.如果垂直方向大,则表明是上下滚动,且返回false表明当前手势不用拦截;
*b.如果水平方向大,则表明是左右滚动,且返回true表明当前手势需要拦截;
*5.重载onInterceptTouchEvent,如果手势返回为true,则onInterceptTouchEvent也返回true;
*6.如果要拦截手势消息,则需要重载onTouchEvent,或子视图中重载这个函数,来处理这条消息;
*7.如果自己处理,则对水平方向滚动去锁(表明当前用户想左右滚动);
*
*----------
*---------------------------->|onDown|
*|||----------
*|dispatchTouchEvent|<----------false:上下滚动
*|||------------/
*---------------------------->|onScroll|----
*|------------\
*|------true:左右滚动
*|intercept=true----------------
*|----------------------|onTouchEvent|
*|----------------
*-------------------------
*||
*|onInterceptTouchEvent|
*||
*-------------------------
*
*/
@Override
publicbooleandispatchTouchEvent(MotionEventev){
bTouchIntercept=mGestureDetector.onTouchEvent(ev);

if(MotionEvent.ACTION_UP==ev.getAction()&&!bLockScrollX){
if(mOnScrollListenerCallback!=null){
mOnScrollListenerCallback.doOnRelease();
}
}

returnsuper.dispatchTouchEvent(ev);
}

//viewgroup.onInterceptTouchEvent
@Override
publicbooleanonInterceptTouchEvent(MotionEventev){
super.onInterceptTouchEvent(ev);
returnbTouchIntercept;
}
//view.onTouchEvent
@Override
publicbooleanonTouchEvent(MotionEventevent){
bLockScrollX=false;
returnsuper.onTouchEvent(event);
}
/**
*@authorcheng.yang
*
*自定义手势监听
*/
publicclassLayoutGestureListenerextendsSimpleOnGestureListener{
@Override
publicbooleanonDown(MotionEvente){
bLockScrollX=true;
returnsuper.onDown(e);
}
@Override
publicbooleanonScroll(MotionEvente1,MotionEvente2,
floatdistanceX,floatdistanceY){
if(!bLockScrollX){
if(mOnScrollListenerCallback!=null){
mOnScrollListenerCallback.doOnScroll(e1,e2,distanceX,distanceY);
}
}

if(Math.abs(e1.getY()-e2.getY())>Math.abs(e1.getX()-e2.getX())){
returnfalse;
}else{
returntrue;
}
}
}

publicinterfaceOnScrollListener{
voiddoOnScroll(MotionEvente1,MotionEvente2,
floatdistanceX,floatdistanceY);
voiddoOnRelease();
}
}

这个控件中,含有一个接口,当用户手势为左右时,则需要将滚动数据回传到主视图中去处理,而自己拦截不往下传递消息。里面有个消息流程图,讲的比较详细了,大家可以先看看,有什么问题,可以问我。
三、MainActivity的实现:
这个需要着讲解,毕竟,左、右滑动的实现都在这里。
复制代码代码如下:
/**
*
*@authorcheng.yang
*
*左、右菜单滑出
*
*params[0]:滑动距离
*params[1]:滑动速度,带方向
*/
publicclassSlideMenuextendsAsyncTask<Integer,Integer,Void>{
@Override
protectedVoiddoInBackground(Integer...params){
if(params.length!=2){
Log.e(TAG,"error,paramsmusthave2!");
}
inttimes=params[0]/Math.abs(params[1]);
if(params[0]%Math.abs(params[1])!=0){
times++;
}

for(inti=0;i<times;i++){
this.publishProgress(params[0],params[1],i+1);
}

returnnull;
}
@Override
protectedvoidonProgressUpdate(Integer...values){
if(values.length!=3){
Log.e(TAG,"error,valuesmusthave3!");
}
intdistance=Math.abs(values[1])*values[2];
intdelta=values[0]-distance;
intleftMargin=0;
if(values[1]<0){//左移
leftMargin=(delta>0?values[1]:-(Math.abs(values[1])-Math.abs(delta)));
}else{
leftMargin=(delta>0?values[1]:(Math.abs(values[1])-Math.abs(delta)));
}

rollLayout(leftMargin);
}

首先,自定义一个继承于AsyncTask的类的线程,这个自定义类,就是用来实现动画效果,重在“滑动”,而不是硬生生的挤出来。关于AsyncTask的用法,大家可以看我的博客中关于它的讲解:
http://blog.csdn.net/qingye_love/article/details/8777508
自定义类的使用需要两个参数,一个是滑动的距离,一个是每次滑动多少且向哪个方向滑动:
1.滑动距离:若是左侧菜单滑出来,距离就是左侧菜单的宽度;同理,右侧滑出就是右侧菜单的宽度;
2.滑动速度:即动画滑动时,向哪个方向,且每次滑动多少像素;
在doInBackground中,计算需要滑动多少次,然后用for循环调用publishProgress,实际上就是调用的onProgressUpdate,在onProgressUpdate中,根据方向,以及当前mainlayout的leftMargin来计算是滑动指定的距离(速度),还是当终点距离小于滑动速度时,速度就为终点距离,最终,调用rollLayout,来修改leftLayout,mainLayout,rightLayout三者的布局,达到滑动的效果。
rollLayout的实现:
复制代码代码如下:
privatevoidrollLayout(intmargin){
RelativeLayout.LayoutParamslp=(LayoutParams)mainLayout.getLayoutParams();
lp.leftMargin+=margin;
lp.rightMargin-=margin;
mainLayout.setLayoutParams(lp);
lp=(LayoutParams)leftLayout.getLayoutParams();
lp.leftMargin+=margin;
leftLayout.setLayoutParams(lp);
lp=(LayoutParams)rightLayout.getLayoutParams();
lp.leftMargin+=margin;
lp.rightMargin-=margin;
rightLayout.setLayoutParams(lp);
}

这个就是修改三个layout的leftMargin和rightMargin。
初始化布局控件:
复制代码代码如下:
privatevoidinitView(){
mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
leftLayout=(RelativeLayout)findViewById(R.id.leftLayout);
rightLayout=(RelativeLayout)findViewById(R.id.rightLayout);
mainLayout.setOnTouchListener(this);
leftLayout.setOnTouchListener(this);
rightLayout.setOnTouchListener(this);

layoutSlideMenu=(LayoutRelative)findViewById(R.id.layoutSlideMenu);
layoutSlideMenu.setOnScrollListener(newOnScrollListener(){
@Override
publicvoiddoOnScroll(MotionEvente1,MotionEvente2,
floatdistanceX,floatdistanceY){
onScroll(distanceX);
}

@Override
publicvoiddoOnRelease(){
onRelease();
}
});

ivMore=(ImageView)findViewById(R.id.ivMore);
ivSettings=(ImageView)findViewById(R.id.ivSettings);
ivMore.setOnTouchListener(this);
ivSettings.setOnTouchListener(this);

mListMore=(ListView)findViewById(R.id.listMore);
mListMore.setAdapter(newArrayAdapter<String>(this,R.layout.item,R.id.tv_item,title));
mListMore.setOnItemClickListener(this);

mGestureDetector=newGestureDetector(this);
mGestureDetector.setIsLongpressEnabled(false);

resizeLayout();
}

调整三个layout,将leftLayout移动到屏幕最左边之外,现时将rightLayout移动到屏幕最右边之外:
复制代码代码如下:
/*
*使用leftMargin及rightMargin防止layout被挤压变形
*Math.abs(leftMargin-rightMargin)=layout.width
*/
privatevoidresizeLayout(){
DisplayMetricsdm=getResources().getDisplayMetrics();

//固定mainlayout,防止被左、右挤压变形
RelativeLayout.LayoutParamslp=(LayoutParams)mainLayout.getLayoutParams();
lp.width=dm.widthPixels;
mainLayout.setLayoutParams(lp);

//将左layout调整至mainlayout左边
lp=(LayoutParams)leftLayout.getLayoutParams();
lp.leftMargin=-lp.width;
leftLayout.setLayoutParams(lp);
Log.d(TAG,"leftl.margin="+lp.leftMargin);

//将左layout调整至mainlayout右边
lp=(LayoutParams)rightLayout.getLayoutParams();
lp.leftMargin=dm.widthPixels;
lp.rightMargin=-lp.width;
rightLayout.setLayoutParams(lp);
Log.d(TAG,"rightl.margin="+lp.leftMargin);
}

重载onTouch,处理消息:
复制代码代码如下:
//////////////////////////////onTouch///////////////////////////////
@Override
publicbooleanonTouch(Viewv,MotionEventevent){
mClickedView=v;

if(MotionEvent.ACTION_UP==event.getAction()&&bIsScrolling){
onRelease();
}

returnmGestureDetector.onTouchEvent(event);
}

记录选择的view,并将消息传给GestureDetector;
重载GestureDetector的onDown,onScroll,onSingleTapUp这三个是主要的,其它的重载不做任何修改;
复制代码代码如下:
///////////////////GestureDetectorOverrideBegin///////////////////
@Override
publicbooleanonDown(MotionEvente){

bIsScrolling=false;
mScroll=0;
iLimited=0;
RelativeLayout.LayoutParamslp=(LayoutParams)mainLayout.getLayoutParams();
if(lp.leftMargin>0){
iLimited=1;
}elseif(lp.leftMargin<0){
iLimited=-1;
}

returntrue;
}

在onDown中,判断当前的mainLayout的leftMargin的值,并用iLimited记录下来,原因:
如果当前显示的是左侧菜单,则用户滚动屏幕时,最多只是将左侧菜单滑出到屏幕外,而不会继续滑动,将右侧菜单显示出来;同理,当前已经显示了右侧菜单。
复制代码代码如下:
@Override
publicbooleanonSingleTapUp(MotionEvente){
/*
*正常情况下,mainLayout的leftMargin为0,
*当左/右菜单为打开中,此时就不为0,需要判断
*/
if(mClickedView!=null){
RelativeLayout.LayoutParamslp=(LayoutParams)mainLayout.getLayoutParams();

if(mClickedView==ivMore){
Log.d(TAG,"[onSingleTapUp]ivMoreclicked!leftMargin="+lp.leftMargin);

if(lp.leftMargin==0){
newSlideMenu().execute(leftLayout.getLayoutParams().width,SPEED);
}else{
newSlideMenu().execute(leftLayout.getLayoutParams().width,-SPEED);
}
}elseif(mClickedView==ivSettings){
Log.d(TAG,"[onSingleTapUp]ivSettingsclicked!leftMargin="+lp.leftMargin);

if(lp.leftMargin==0){
newSlideMenu().execute(rightLayout.getLayoutParams().width,-SPEED);
}else{
newSlideMenu().execute(rightLayout.getLayoutParams().width,SPEED);
}
}elseif(mClickedView==mainLayout){
Log.d(TAG,"[onSingleTapUp]mainLayoutclicked!");
}
}
returntrue;
}

这个函数中,处理标题栏左、右两个按钮,点击一次,显示侧导航菜单,再点击一次,则关闭。
复制代码代码如下:
@Override
publicbooleanonScroll(MotionEvente1,MotionEvente2,floatdistanceX,
floatdistanceY){
onScroll(distanceX);
returnfalse;
}

滚动处理,直接将本次的水平滚动距离传给onScroll函数来处理,因为,侧边导航菜单的水平滚动也将通过OnScrollListener.doOnScroll来回调,所以,写个通用函数。
复制代码代码如下:
privatevoidonScroll(floatdistanceX){
bIsScrolling=true;
mScroll+=distanceX;//向左为正
Log.d(TAG,"mScroll="+mScroll+",distanceX="+distanceX);

RelativeLayout.LayoutParamslp=(LayoutParams)mainLayout.getLayoutParams();
RelativeLayout.LayoutParamslpLeft=(LayoutParams)leftLayout.getLayoutParams();
RelativeLayout.LayoutParamslpRight=(LayoutParams)rightLayout.getLayoutParams();
Log.d(TAG,"lp.leftMargin="+lp.leftMargin);

intdistance=0;
if(mScroll>0){//向左移动
if(lp.leftMargin<=0){//打开右导航菜单
if(iLimited>0){
return;
}
distance=lpRight.width-Math.abs(lp.leftMargin);
}elseif(lp.leftMargin>0){//关闭左导航菜单
distance=lp.leftMargin;
}
if(mScroll>=distance){
mScroll=distance;
}
}elseif(mScroll<0){//向右移动
if(lp.leftMargin>=0){//打开左导航菜单
if(iLimited<0){
return;
}
distance=lpLeft.width-Math.abs(lp.leftMargin);
}elseif(lp.leftMargin<0){//关闭右导航菜单
distance=Math.abs(lp.leftMargin);
}
if(mScroll<=-distance){
mScroll=-distance;
}
}
Log.d(TAG,"mScroll="+mScroll);
if(mScroll!=0){
rollLayout(-mScroll);
}
}

接下来,我们看看当侧边导航菜单,水平滚动且用户松开手指时,回调OnScrollListener.doOnRelease,我们会调用一个onRelease来负责处理收尾工作:
复制代码代码如下:
privatevoidonRelease(){
RelativeLayout.LayoutParamslp=(LayoutParams)mainLayout.getLayoutParams();
if(lp.leftMargin<0){//左移
/*
*左移大于右导航宽度一半,则自动展开,否则自动缩回去
*/
if(Math.abs(lp.leftMargin)>rightLayout.getLayoutParams().width/2){
newSlideMenu().execute(rightLayout.getLayoutParams().width-Math.abs(lp.leftMargin),-SPEED);
}else{
newSlideMenu().execute(Math.abs(lp.leftMargin),SPEED);
}
}elseif(lp.leftMargin>0){
/*
*右移大于左导航宽度一半,则自动展开,否则自动缩回去
*/
if(Math.abs(lp.leftMargin)>leftLayout.getLayoutParams().width/2){
newSlideMenu().execute(leftLayout.getLayoutParams().width-Math.abs(lp.leftMargin),SPEED);
}else{
newSlideMenu().execute(Math.abs(lp.leftMargin),-SPEED);
}
}
}

主要的代码块已经讲解完了,下面是完整的代码:
复制代码代码如下:
packagecom.chris.lr.slidemenu;
importcom.chris.lr.slidemenu.LayoutRelative.OnScrollListener;
importandroid.os.AsyncTask;
importandroid.os.Bundle;
importandroid.util.DisplayMetrics;
importandroid.util.Log;
importandroid.view.GestureDetector;
importandroid.view.GestureDetector.OnGestureListener;
importandroid.view.KeyEvent;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.View.OnTouchListener;
importandroid.view.Window;
importandroid.widget.AdapterView;
importandroid.widget.AdapterView.OnItemClickListener;
importandroid.widget.ArrayAdapter;
importandroid.widget.ImageView;
importandroid.widget.ListView;
importandroid.widget.RelativeLayout;
importandroid.widget.Toast;
importandroid.widget.RelativeLayout.LayoutParams;
importandroid.app.Activity;
publicclassMainActivityextendsActivityimplementsOnGestureListener,
OnTouchListener,OnItemClickListener{
privatestaticfinalStringTAG="ChrisSlideMenu";
privateRelativeLayoutmainLayout;
privateRelativeLayoutleftLayout;
privateRelativeLayoutrightLayout;
privateLayoutRelativelayoutSlideMenu;
privateListViewmListMore;

privateImageViewivMore;
privateImageViewivSettings;
privateGestureDetectormGestureDetector;

privatestaticfinalintSPEED=30;
privatebooleanbIsScrolling=false;
privateintiLimited=0;
privateintmScroll=0;
privateViewmClickedView=null;

privateStringtitle[]={"待发送队列",
"同步分享设置",
"编辑我的资料",
"找朋友",
"告诉朋友",
"节省流量",
"推送设置",
"版本更新",
"意见反馈",
"积分兑换",
"精品应用",
"常见问题",
"退出当前帐号",
"退出1",
"退出2",
"退出3",
"退出4"};

@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initView();
}

privatevoidinitView(){
mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
leftLayout=(RelativeLayout)findViewById(R.id.leftLayout);
rightLayout=(RelativeLayout)findViewById(R.id.rightLayout);
mainLayout.setOnTouchListener(this);
leftLayout.setOnTouchListener(this);
rightLayout.setOnTouchListener(this);

layoutSlideMenu=(LayoutRelative)findViewById(R.id.layoutSlideMenu);
layoutSlideMenu.setOnScrollListener(newOnScrollListener(){
@Override
publicvoiddoOnScroll(MotionEvente1,MotionEvente2,
floatdistanceX,floatdistanceY){
onScroll(distanceX);
}

@Override
publicvoiddoOnRelease(){
onRelease();
}
});

ivMore=(ImageView)findViewById(R.id.ivMore);
ivSettings=(ImageView)findViewById(R.id.ivSettings);
ivMore.setOnTouchListener(this);
ivSettings.setOnTouchListener(this);

mListMore=(ListView)findViewById(R.id.listMore);
mListMore.setAdapter(newArrayAdapter<String>(
this,R.layout.item,R.id.tv_item,title));
mListMore.setOnItemClickListener(this);

mGestureDetector=newGestureDetector(this);
mGestureDetector.setIsLongpressEnabled(false);

resizeLayout();
}

/*
*使用leftMargin及rightMargin防止layout被挤压变形
*Math.abs(leftMargin-rightMargin)=layout.width
*/
privatevoidresizeLayout(){
DisplayMetricsdm=getResources().getDisplayMetrics();

//固定mainlayout,防止被左、右挤压变形
RelativeLayout.LayoutParamslp=
(LayoutParams)mainLayout.getLayoutParams();
lp.width=dm.widthPixels;
mainLayout.setLayoutParams(lp);

//将左layout调整至mainlayout左边
lp=(LayoutParams)leftLayout.getLayoutParams();
lp.leftMargin=-lp.width;
leftLayout.setLayoutParams(lp);
Log.d(TAG,"leftl.margin="+lp.leftMargin);

//将左layout调整至mainlayout右边
lp=(LayoutParams)rightLayout.getLayoutParams();
lp.leftMargin=dm.widthPixels;
lp.rightMargin=-lp.width;
rightLayout.setLayoutParams(lp);
Log.d(TAG,"rightl.margin="+lp.leftMargin);
}

@Override
publicbooleanonKeyDown(intkeyCode,KeyEventevent){
if(keyCode==KeyEvent.KEYCODE_BACK&&event.getRepeatCount()==0){
RelativeLayout.LayoutParamslp=
(LayoutParams)mainLayout.getLayoutParams();

if(lp.leftMargin!=0){
if(lp.leftMargin>0){
newSlideMenu().execute(
leftLayout.getLayoutParams().width,-SPEED);
}elseif(lp.leftMargin<0){
newSlideMenu().execute(
rightLayout.getLayoutParams().width,SPEED);
}
returntrue;
}
}
returnsuper.onKeyDown(keyCode,event);
}

privatevoidrollLayout(intmargin){
RelativeLayout.LayoutParamslp=
(LayoutParams)mainLayout.getLayoutParams();
lp.leftMargin+=margin;
lp.rightMargin-=margin;
mainLayout.setLayoutParams(lp);
lp=(LayoutParams)leftLayout.getLayoutParams();
lp.leftMargin+=margin;
leftLayout.setLayoutParams(lp);
lp=(LayoutParams)rightLayout.getLayoutParams();
lp.leftMargin+=margin;
lp.rightMargin-=margin;
rightLayout.setLayoutParams(lp);
}
privatevoidonScroll(floatdistanceX){
bIsScrolling=true;
mScroll+=distanceX;//向左为正
Log.d(TAG,"mScroll="+mScroll+",distanceX="+distanceX);

RelativeLayout.LayoutParamslp=
(LayoutParams)mainLayout.getLayoutParams();
RelativeLayout.LayoutParamslpLeft=
(LayoutParams)leftLayout.getLayoutParams();
RelativeLayout.LayoutParamslpRight=
(LayoutParams)rightLayout.getLayoutParams();
Log.d(TAG,"lp.leftMargin="+lp.leftMargin);

intdistance=0;
if(mScroll>0){//向左移动
if(lp.leftMargin<=0){//打开右导航菜单
if(iLimited>0){
return;
}
distance=lpRight.width-Math.abs(lp.leftMargin);
}elseif(lp.leftMargin>0){//关闭左导航菜单
distance=lp.leftMargin;
}
if(mScroll>=distance){
mScroll=distance;
}
}elseif(mScroll<0){//向右移动
if(lp.leftMargin>=0){//打开左导航菜单
if(iLimited<0){
return;
}
distance=lpLeft.width-Math.abs(lp.leftMargin);
}elseif(lp.leftMargin<0){//关闭右导航菜单
distance=Math.abs(lp.leftMargin);
}
if(mScroll<=-distance){
mScroll=-distance;
}
}
Log.d(TAG,"mScroll="+mScroll);
if(mScroll!=0){
rollLayout(-mScroll);
}
}

privatevoidonRelease(){
RelativeLayout.LayoutParamslp=
(LayoutParams)mainLayout.getLayoutParams();
if(lp.leftMargin<0){//左移
/*
*左移大于右导航宽度一半,则自动展开,否则自动缩回去
*/
if(Math.abs(lp.leftMargin)>rightLayout.getLayoutParams().width/2){
newSlideMenu().execute(rightLayout.getLayoutParams().width-
Math.abs(lp.leftMargin),-SPEED);
}else{
newSlideMenu().execute(Math.abs(lp.leftMargin),SPEED);
}
}elseif(lp.leftMargin>0){
/*
*右移大于左导航宽度一半,则自动展开,否则自动缩回去
*/
if(Math.abs(lp.leftMargin)>leftLayout.getLayoutParams().width/2){
newSlideMenu().execute(leftLayout.getLayoutParams().width-
Math.abs(lp.leftMargin),SPEED);
}else{
newSlideMenu().execute(Math.abs(lp.leftMargin),-SPEED);
}
}
}
/////////////////////ListView.onItemClick///////////////////////
@Override
publicvoidonItemClick(AdapterView<?>arg0,Viewarg1,intarg2,longarg3){
Toast.makeText(this,title[arg2],Toast.LENGTH_SHORT).show();
}

//////////////////////////////onTouch///////////////////////////////
@Override
publicbooleanonTouch(Viewv,MotionEventevent){
mClickedView=v;

if(MotionEvent.ACTION_UP==event.getAction()&&bIsScrolling){
onRelease();
}

returnmGestureDetector.onTouchEvent(event);
}

///////////////////GestureDetectorOverrideBegin///////////////////
@Override
publicbooleanonDown(MotionEvente){

bIsScrolling=false;
mScroll=0;
iLimited=0;
RelativeLayout.LayoutParamslp=
(LayoutParams)mainLayout.getLayoutParams();
if(lp.leftMargin>0){
iLimited=1;
}elseif(lp.leftMargin<0){
iLimited=-1;
}

returntrue;
}
@Override
publicbooleanonFling(MotionEventarg0,MotionEventarg1,floatarg2,
floatarg3){
returnfalse;
}
@Override
publicvoidonLongPress(MotionEvente){

}
@Override
publicbooleanonScroll(MotionEvente1,MotionEvente2,floatdistanceX,
floatdistanceY){
onScroll(distanceX);
returnfalse;
}
@Override
publicvoidonShowPress(MotionEventarg0){

}
@Override
publicbooleanonSingleTapUp(MotionEvente){
/*
*正常情况下,mainLayout的leftMargin为0,
*当左/右菜单为打开中,此时就不为0,需要判断
*/
if(mClickedView!=null){
RelativeLayout.LayoutParamslp=
(LayoutParams)mainLayout.getLayoutParams();

if(mClickedView==ivMore){
Log.d(TAG,"[onSingleTapUp]ivMoreclicked!leftMargin="
+lp.leftMargin);

if(lp.leftMargin==0){
newSlideMenu().execute(
leftLayout.getLayoutParams().width,SPEED);
}else{
newSlideMenu().execute(
leftLayout.getLayoutParams().width,-SPEED);
}
}elseif(mClickedView==ivSettings){
Log.d(TAG,"[onSingleTapUp]ivSettingsclicked!leftMargin="
+lp.leftMargin);

if(lp.leftMargin==0){
newSlideMenu().execute(
rightLayout.getLayoutParams().width,-SPEED);
}else{
newSlideMenu().execute(
rightLayout.getLayoutParams().width,SPEED);
}
}elseif(mClickedView==mainLayout){
Log.d(TAG,"[onSingleTapUp]mainLayoutclicked!");
}
}
returntrue;
}
///////////////////GestureDetectorOverrideEnd///////////////////

/**
*
*@authorcheng.yang
*
*左、右菜单滑出
*
*params[0]:滑动距离
*params[1]:滑动速度,带方向
*/
publicclassSlideMenuextendsAsyncTask<Integer,Integer,Void>{
@Override
protectedVoiddoInBackground(Integer...params){
if(params.length!=2){
Log.e(TAG,"error,paramsmusthave2!");
}
inttimes=params[0]/Math.abs(params[1]);
if(params[0]%Math.abs(params[1])!=0){
times++;
}

for(inti=0;i<times;i++){
this.publishProgress(params[0],params[1],i+1);
}

returnnull;
}
@Override
protectedvoidonProgressUpdate(Integer...values){
if(values.length!=3){
Log.e(TAG,"error,valuesmusthave3!");
}
intdistance=Math.abs(values[1])*values[2];
intdelta=values[0]-distance;
intleftMargin=0;
if(values[1]<0){//左移
leftMargin=(delta>0?values[1]:
-(Math.abs(values[1])-Math.abs(delta)));
}else{
leftMargin=(delta>0?values[1]:
(Math.abs(values[1])-Math.abs(delta)));
}

rollLayout(leftMargin);
}
}
}

总结:
本文左,右滑出菜单的原理,就是用到了leftMargin和rightMargin两个属性,并配合几个GestureDetector来完全手势判断的逻辑处理,其中,自定义的那个控件布局LayoutRelative,可以用在任何子视图中需要处理上下,左右滚动冲突的地方,良好的解决了冲突问题。
完整的代码下载地址:
http://download.csdn.net/detail/qingye_love/5237799
希望大家多来聊聊,同时,大家可以在我的基础上,实现onFling的方法,欢迎交流。