欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android左右滑出菜单实例分析

程序员文章站 2023-12-03 14:46:04
现在的android应用,只要有一个什么新的创意,过不了多久,几乎所有的应用都带这个创意。这不,咱们公司最近的一个持续性的项目,想在首页加个从左滑动出来的菜单,我查阅网上资...
现在的android应用,只要有一个什么新的创意,过不了多久,几乎所有的应用都带这个创意。这不,咱们公司最近的一个持续性的项目,想在首页加个从左滑动出来的菜单,我查阅网上资料,并自己摸索,实现了左、右两边都能滑出菜单,并且,左、右菜单中,都可以加listview等这类需要解决gesturedetector冲突的问题(如在首页面中,含有listview,上下滚动时,左右不动,相反,左右滑动菜单时,上下不动,听着头就大了吧!)

先上几张图,给大家瞧瞧,对整体有个了解:
Android左右滑出菜单实例分析 
一、首页布局:
复制代码 代码如下:

<relativelayout xmlns: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这类需要判断手势的,则就用到它,先由它来判断,然后在视情况是否拦截由自己来处理。

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

package com.chris.lr.slidemenu;
import android.content.context;
import android.util.attributeset;
import android.util.log;
import android.view.gesturedetector;
import android.view.gesturedetector.simpleongesturelistener;
import android.view.motionevent;
import android.widget.relativelayout;
public class layoutrelative extends relativelayout {
private static final string tag = "chrisslidemenu";
private gesturedetector mgesturedetector;
private boolean blockscrollx = false;
private boolean btouchintercept = false;

private onscrolllistener monscrolllistenercallback = null;

public layoutrelative(context context) {
this(context, null);
}

public layoutrelative(context context, attributeset attrs) {
this(context, attrs, 0);
}

public layoutrelative(context context, attributeset attrs, int defstyle) {
super(context, attrs, defstyle);

mgesturedetector = new gesturedetector(new layoutgesturelistener());
}

/**
* 设置滚动监听接口
* @param l
*/
public void setonscrolllistener(onscrolllistener l){
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
public boolean dispatchtouchevent(motionevent ev) {
btouchintercept = mgesturedetector.ontouchevent(ev);

if(motionevent.action_up == ev.getaction() && !blockscrollx){
if(monscrolllistenercallback != null){
monscrolllistenercallback.doonrelease();
}
}

return super.dispatchtouchevent(ev);
}

// viewgroup.onintercepttouchevent
@override
public boolean onintercepttouchevent(motionevent ev) {
super.onintercepttouchevent(ev);
return btouchintercept;
}
// view.ontouchevent
@override
public boolean ontouchevent(motionevent event) {
blockscrollx = false;
return super.ontouchevent(event);
}
/**
* @author cheng.yang
*
* 自定义手势监听
*/
public class layoutgesturelistener extends simpleongesturelistener {
@override
public boolean ondown(motionevent e) {
blockscrollx = true;
return super.ondown(e);
}
@override
public boolean onscroll(motionevent e1, motionevent e2,
float distancex, float distancey) {
if(!blockscrollx){
if(monscrolllistenercallback != null){
monscrolllistenercallback.doonscroll(e1, e2, distancex, distancey);
}
}

if(math.abs(e1.gety() - e2.gety()) > math.abs(e1.getx() - e2.getx())){
return false;
}else{
return true;
}
}
}

public interface onscrolllistener {
void doonscroll(motionevent e1, motionevent e2,
float distancex, float distancey);
void doonrelease();
}
}

这个控件中,含有一个接口,当用户手势为左右时,则需要将滚动数据回传到主视图中去处理,而自己拦截不往下传递消息。里面有个消息流程图,讲的比较详细了,大家可以先看看,有什么问题,可以问我。
三、mainactivity的实现:
这个需要着讲解,毕竟,左、右滑动的实现都在这里。
复制代码 代码如下:

/**
*
* @author cheng.yang
*
* 左、右菜单滑出
*
* params[0]: 滑动距离
* params[1]: 滑动速度,带方向
*/
public class slidemenu extends asynctask<integer, integer, void>{
@override
protected void doinbackground(integer... params) {
if(params.length != 2){
log.e(tag, "error, params must have 2!");
}
int times = params[0] / math.abs(params[1]);
if(params[0] % math.abs(params[1]) != 0){
times ++;
}

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

return null;
}
@override
protected void onprogressupdate(integer... values) {
if(values.length != 3){
log.e(tag, "error, values must have 3!");
}
int distance = math.abs(values[1]) * values[2];
int delta = values[0] - distance;
int leftmargin = 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中,根据方向,以及当前main layout的 leftmargin来计算是滑动指定的距离(速度),还是当终点距离小于滑动速度时,速度就为终点距离,最终,调用rolllayout,来修改 leftlayout, mainlayout, rightlayout三者的布局,达到滑动的效果。
rolllayout的实现:
复制代码 代码如下:

private void rolllayout(int margin){
relativelayout.layoutparams lp = (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。
初始化布局控件:
复制代码 代码如下:

private void initview(){
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(new onscrolllistener(){
@override
public void doonscroll(motionevent e1, motionevent e2,
float distancex, float distancey) {
onscroll(distancex);
}

@override
public void doonrelease(){
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(new arrayadapter<string>(this, r.layout.item, r.id.tv_item, title));
mlistmore.setonitemclicklistener(this);

mgesturedetector = new gesturedetector(this);
mgesturedetector.setislongpressenabled(false);

resizelayout();
}

调整三个layout,将leftlayout移动到屏幕最左边之外,现时将rightlayout移动到屏幕最右边之外:
复制代码 代码如下:

/*
* 使用leftmargin及rightmargin防止layout被挤压变形
* math.abs(leftmargin - rightmargin) = layout.width
*/
private void resizelayout(){
displaymetrics dm = getresources().getdisplaymetrics();

// 固定 main layout, 防止被左、右挤压变形
relativelayout.layoutparams lp = (layoutparams) mainlayout.getlayoutparams();
lp.width = dm.widthpixels;
mainlayout.setlayoutparams(lp);

// 将左layout调整至main layout左边
lp = (layoutparams) leftlayout.getlayoutparams();
lp.leftmargin = -lp.width;
leftlayout.setlayoutparams(lp);
log.d(tag, "left l.margin = " + lp.leftmargin);

// 将左layout调整至main layout右边
lp = (layoutparams) rightlayout.getlayoutparams();
lp.leftmargin = dm.widthpixels;
lp.rightmargin = -lp.width;
rightlayout.setlayoutparams(lp);
log.d(tag, "right l.margin = " + lp.leftmargin);
}

重载ontouch,处理消息:
复制代码 代码如下:

////////////////////////////// ontouch ///////////////////////////////
@override
public boolean ontouch(view v, motionevent event) {
mclickedview = v;

if(motionevent.action_up == event.getaction() && bisscrolling){
onrelease();
}

return mgesturedetector.ontouchevent(event);
}

记录选择的view,并将消息传给gesturedetector;
重载gesturedetector的ondown, onscroll, onsingletapup这三个是主要的,其它的重载不做任何修改;
复制代码 代码如下:

/////////////////// gesturedetector override begin ///////////////////
@override
public boolean ondown(motionevent e) {

bisscrolling = false;
mscroll = 0;
ilimited = 0;
relativelayout.layoutparams lp = (layoutparams) mainlayout.getlayoutparams();
if(lp.leftmargin > 0){
ilimited = 1;
}else if(lp.leftmargin < 0){
ilimited = -1;
}

return true;
}

在ondown中,判断当前的 mainlayout 的 leftmargin 的值,并用 ilimited 记录下来,原因:
如果当前显示的是左侧菜单,则用户滚动屏幕时,最多只是将左侧菜单滑出到屏幕外,而不会继续滑动,将右侧菜单显示出来;同理,当前已经显示了右侧菜单。
复制代码 代码如下:

@override
public boolean onsingletapup(motionevent e) {
/*
* 正常情况下,mainlayout的leftmargin为0,
* 当左/右菜单为打开中,此时就不为0,需要判断
*/
if(mclickedview != null){
relativelayout.layoutparams lp = (layoutparams) mainlayout.getlayoutparams();

if(mclickedview == ivmore){
log.d(tag, "[onsingletapup] ivmore clicked! leftmargin = " + lp.leftmargin);

if(lp.leftmargin == 0){
new slidemenu().execute(leftlayout.getlayoutparams().width, speed);
}else{
new slidemenu().execute(leftlayout.getlayoutparams().width, -speed);
}
}else if(mclickedview == ivsettings){
log.d(tag, "[onsingletapup] ivsettings clicked! leftmargin = " + lp.leftmargin);

if(lp.leftmargin == 0){
new slidemenu().execute(rightlayout.getlayoutparams().width, -speed);
}else{
new slidemenu().execute(rightlayout.getlayoutparams().width, speed);
}
}else if(mclickedview == mainlayout){
log.d(tag, "[onsingletapup] mainlayout clicked!");
}
}
return true;
}

这个函数中,处理标题栏左、右两个按钮,点击一次,显示侧导航菜单,再点击一次,则关闭。
复制代码 代码如下:

@override
public boolean onscroll(motionevent e1, motionevent e2, float distancex,
float distancey) {
onscroll(distancex);
return false;
}

滚动处理,直接将本次的水平滚动距离传给onscroll函数来处理,因为,侧边导航菜单的水平滚动也将通过onscrolllistener.doonscroll来回调,所以,写个通用函数。
复制代码 代码如下:

private void onscroll(float distancex){
bisscrolling = true;
mscroll += distancex; // 向左为正
log.d(tag, "mscroll = " + mscroll + ", distancex = " + distancex);

relativelayout.layoutparams lp = (layoutparams) mainlayout.getlayoutparams();
relativelayout.layoutparams lpleft = (layoutparams) leftlayout.getlayoutparams();
relativelayout.layoutparams lpright = (layoutparams) rightlayout.getlayoutparams();
log.d(tag, "lp.leftmargin = " + lp.leftmargin);

int distance = 0;
if(mscroll > 0){ // 向左移动
if(lp.leftmargin <= 0){ // 打开右导航菜单
if(ilimited > 0){
return;
}
distance = lpright.width - math.abs(lp.leftmargin);
}else if(lp.leftmargin > 0){ // 关闭左导航菜单
distance = lp.leftmargin;
}
if(mscroll >= distance){
mscroll = distance;
}
}else if(mscroll < 0){ // 向右移动
if(lp.leftmargin >= 0){ // 打开左导航菜单
if(ilimited < 0){
return;
}
distance = lpleft.width - math.abs(lp.leftmargin);
}else if(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来负责处理收尾工作:
复制代码 代码如下:

private void onrelease(){
relativelayout.layoutparams lp = (layoutparams) mainlayout.getlayoutparams();
if(lp.leftmargin < 0){ // 左移
/*
* 左移大于右导航宽度一半,则自动展开,否则自动缩回去
*/
if(math.abs(lp.leftmargin) > rightlayout.getlayoutparams().width/2){
new slidemenu().execute(rightlayout.getlayoutparams().width - math.abs(lp.leftmargin), -speed);
}else{
new slidemenu().execute(math.abs(lp.leftmargin), speed);
}
}else if(lp.leftmargin > 0){
/*
* 右移大于左导航宽度一半,则自动展开,否则自动缩回去
*/
if(math.abs(lp.leftmargin) > leftlayout.getlayoutparams().width/2){
new slidemenu().execute(leftlayout.getlayoutparams().width - math.abs(lp.leftmargin), speed);
}else{
new slidemenu().execute(math.abs(lp.leftmargin), -speed);
}
}
}

主要的代码块已经讲解完了,下面是完整的代码:
复制代码 代码如下:

package com.chris.lr.slidemenu;
import com.chris.lr.slidemenu.layoutrelative.onscrolllistener;
import android.os.asynctask;
import android.os.bundle;
import android.util.displaymetrics;
import android.util.log;
import android.view.gesturedetector;
import android.view.gesturedetector.ongesturelistener;
import android.view.keyevent;
import android.view.motionevent;
import android.view.view;
import android.view.view.ontouchlistener;
import android.view.window;
import android.widget.adapterview;
import android.widget.adapterview.onitemclicklistener;
import android.widget.arrayadapter;
import android.widget.imageview;
import android.widget.listview;
import android.widget.relativelayout;
import android.widget.toast;
import android.widget.relativelayout.layoutparams;
import android.app.activity;
public class mainactivity extends activity implements ongesturelistener,
ontouchlistener, onitemclicklistener {
private static final string tag = "chrisslidemenu";
private relativelayout mainlayout;
private relativelayout leftlayout;
private relativelayout rightlayout;
private layoutrelative layoutslidemenu;
private listview mlistmore;

private imageview ivmore;
private imageview ivsettings;
private gesturedetector mgesturedetector;

private static final int speed = 30;
private boolean bisscrolling = false;
private int ilimited = 0;
private int mscroll = 0;
private view mclickedview = null;

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

@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
this.requestwindowfeature(window.feature_no_title);
setcontentview(r.layout.activity_main);
initview();
}

private void initview(){
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(new onscrolllistener(){
@override
public void doonscroll(motionevent e1, motionevent e2,
float distancex, float distancey) {
onscroll(distancex);
}

@override
public void doonrelease(){
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(new arrayadapter<string>(
this, r.layout.item, r.id.tv_item, title));
mlistmore.setonitemclicklistener(this);

mgesturedetector = new gesturedetector(this);
mgesturedetector.setislongpressenabled(false);

resizelayout();
}

/*
* 使用leftmargin及rightmargin防止layout被挤压变形
* math.abs(leftmargin - rightmargin) = layout.width
*/
private void resizelayout(){
displaymetrics dm = getresources().getdisplaymetrics();

// 固定 main layout, 防止被左、右挤压变形
relativelayout.layoutparams lp =
(layoutparams) mainlayout.getlayoutparams();
lp.width = dm.widthpixels;
mainlayout.setlayoutparams(lp);

// 将左layout调整至main layout左边
lp = (layoutparams) leftlayout.getlayoutparams();
lp.leftmargin = -lp.width;
leftlayout.setlayoutparams(lp);
log.d(tag, "left l.margin = " + lp.leftmargin);

// 将左layout调整至main layout右边
lp = (layoutparams) rightlayout.getlayoutparams();
lp.leftmargin = dm.widthpixels;
lp.rightmargin = -lp.width;
rightlayout.setlayoutparams(lp);
log.d(tag, "right l.margin = " + lp.leftmargin);
}

@override
public boolean onkeydown(int keycode, keyevent event) {
if(keycode == keyevent.keycode_back && event.getrepeatcount() == 0){
relativelayout.layoutparams lp =
(layoutparams) mainlayout.getlayoutparams();

if(lp.leftmargin != 0){
if(lp.leftmargin > 0){
new slidemenu().execute(
leftlayout.getlayoutparams().width, -speed);
}else if(lp.leftmargin < 0){
new slidemenu().execute(
rightlayout.getlayoutparams().width, speed);
}
return true;
}
}
return super.onkeydown(keycode, event);
}

private void rolllayout(int margin){
relativelayout.layoutparams lp =
(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);
}
private void onscroll(float distancex){
bisscrolling = true;
mscroll += distancex; // 向左为正
log.d(tag, "mscroll = " + mscroll + ", distancex = " + distancex);

relativelayout.layoutparams lp =
(layoutparams) mainlayout.getlayoutparams();
relativelayout.layoutparams lpleft =
(layoutparams) leftlayout.getlayoutparams();
relativelayout.layoutparams lpright =
(layoutparams) rightlayout.getlayoutparams();
log.d(tag, "lp.leftmargin = " + lp.leftmargin);

int distance = 0;
if(mscroll > 0){ // 向左移动
if(lp.leftmargin <= 0){ // 打开右导航菜单
if(ilimited > 0){
return;
}
distance = lpright.width - math.abs(lp.leftmargin);
}else if(lp.leftmargin > 0){ // 关闭左导航菜单
distance = lp.leftmargin;
}
if(mscroll >= distance){
mscroll = distance;
}
}else if(mscroll < 0){ // 向右移动
if(lp.leftmargin >= 0){ // 打开左导航菜单
if(ilimited < 0){
return;
}
distance = lpleft.width - math.abs(lp.leftmargin);
}else if(lp.leftmargin < 0){ // 关闭右导航菜单
distance = math.abs(lp.leftmargin);
}
if(mscroll <= -distance){
mscroll = -distance;
}
}
log.d(tag, "mscroll = " + mscroll);
if(mscroll != 0){
rolllayout(-mscroll);
}
}

private void onrelease(){
relativelayout.layoutparams lp =
(layoutparams) mainlayout.getlayoutparams();
if(lp.leftmargin < 0){ // 左移
/*
* 左移大于右导航宽度一半,则自动展开,否则自动缩回去
*/
if(math.abs(lp.leftmargin) > rightlayout.getlayoutparams().width/2){
new slidemenu().execute(rightlayout.getlayoutparams().width -
math.abs(lp.leftmargin), -speed);
}else{
new slidemenu().execute(math.abs(lp.leftmargin), speed);
}
}else if(lp.leftmargin > 0){
/*
* 右移大于左导航宽度一半,则自动展开,否则自动缩回去
*/
if(math.abs(lp.leftmargin) > leftlayout.getlayoutparams().width/2){
new slidemenu().execute(leftlayout.getlayoutparams().width -
math.abs(lp.leftmargin), speed);
}else{
new slidemenu().execute(math.abs(lp.leftmargin), -speed);
}
}
}
///////////////////// listview.onitemclick ///////////////////////
@override
public void onitemclick(adapterview<?> arg0, view arg1, int arg2, long arg3) {
toast.maketext(this, title[arg2], toast.length_short).show();
}

////////////////////////////// ontouch ///////////////////////////////
@override
public boolean ontouch(view v, motionevent event) {
mclickedview = v;

if(motionevent.action_up == event.getaction() && bisscrolling){
onrelease();
}

return mgesturedetector.ontouchevent(event);
}

/////////////////// gesturedetector override begin ///////////////////
@override
public boolean ondown(motionevent e) {

bisscrolling = false;
mscroll = 0;
ilimited = 0;
relativelayout.layoutparams lp =
(layoutparams) mainlayout.getlayoutparams();
if(lp.leftmargin > 0){
ilimited = 1;
}else if(lp.leftmargin < 0){
ilimited = -1;
}

return true;
}
@override
public boolean onfling(motionevent arg0, motionevent arg1, float arg2,
float arg3) {
return false;
}
@override
public void onlongpress(motionevent e) {

}
@override
public boolean onscroll(motionevent e1, motionevent e2, float distancex,
float distancey) {
onscroll(distancex);
return false;
}
@override
public void onshowpress(motionevent arg0) {

}
@override
public boolean onsingletapup(motionevent e) {
/*
* 正常情况下,mainlayout的leftmargin为0,
* 当左/右菜单为打开中,此时就不为0,需要判断
*/
if(mclickedview != null){
relativelayout.layoutparams lp =
(layoutparams) mainlayout.getlayoutparams();

if(mclickedview == ivmore){
log.d(tag, "[onsingletapup] ivmore clicked! leftmargin = "
+ lp.leftmargin);

if(lp.leftmargin == 0){
new slidemenu().execute(
leftlayout.getlayoutparams().width, speed);
}else{
new slidemenu().execute(
leftlayout.getlayoutparams().width, -speed);
}
}else if(mclickedview == ivsettings){
log.d(tag, "[onsingletapup] ivsettings clicked! leftmargin = "
+ lp.leftmargin);

if(lp.leftmargin == 0){
new slidemenu().execute(
rightlayout.getlayoutparams().width, -speed);
}else{
new slidemenu().execute(
rightlayout.getlayoutparams().width, speed);
}
}else if(mclickedview == mainlayout){
log.d(tag, "[onsingletapup] mainlayout clicked!");
}
}
return true;
}
/////////////////// gesturedetector override end ///////////////////

/**
*
* @author cheng.yang
*
* 左、右菜单滑出
*
* params[0]: 滑动距离
* params[1]: 滑动速度,带方向
*/
public class slidemenu extends asynctask<integer, integer, void>{
@override
protected void doinbackground(integer... params) {
if(params.length != 2){
log.e(tag, "error, params must have 2!");
}
int times = params[0] / math.abs(params[1]);
if(params[0] % math.abs(params[1]) != 0){
times ++;
}

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

return null;
}
@override
protected void onprogressupdate(integer... values) {
if(values.length != 3){
log.e(tag, "error, values must have 3!");
}
int distance = math.abs(values[1]) * values[2];
int delta = values[0] - distance;
int leftmargin = 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的方法,欢迎交流。