精华内容
参与话题
问答
  • 滑动菜单

    2019-10-13 17:59:20
    滑动菜单指的是:菜单项在初始状态下是隐藏的,用户可以通过滑动的方式将菜单显示出来。 DrawerLayout布局 <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:...

    滑动菜单指的是:菜单项在初始状态下是隐藏的,用户可以通过滑动的方式将菜单显示出来。

    DrawerLayout布局

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                 />
        </FrameLayout>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:background="#ffffcc"
            android:text="This is menu"
            android:textSize="30sp" />
    </android.support.v4.widget.DrawerLayout>
    

    我们在 DrawerLayout 中放置了两个子控件:

    • FrameLayout 放置主屏幕需要显示的内容(这里定义了一个工具栏)。
    • TextView 放置滑动菜单中的显示内容。这里的 android:layout_gravity 用于设定滑动菜单的显示位置

    left :显示在左侧。
    right :显示在右侧。
    start :根据系统语言的自然阅读顺序来决定。如果从左到右(比如汉语),那么就显示在左侧;如果从右到左(比如阿拉伯语),那么就显示在右侧。

    public class MainActivity extends AppCompatActivity {
    
        private DrawerLayout drawerLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
    
            drawerLayout = (DrawerLayout) findViewById(R.id.drawer);
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null) {
                //显示导航按钮
                actionBar.setDisplayHomeAsUpEnabled(true);
                actionBar.setHomeAsUpIndicator(R.drawable.menu);
            }
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.toolbar, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.add:
                    Toast.makeText(this, "点击了新增按钮", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.set:
                    Toast.makeText(this, "点击了设置按钮", Toast.LENGTH_SHORT).show();
                    break;
                case android.R.id.home:
                    drawerLayout.openDrawer(GravityCompat.START);
                    break;
            }
            return true;
        }
    }
    
    

    我们通过 getSupportActionBar() 方法获取 ActionBar 实例。
    调用 setDisplayHomeAsUpEnabled() 显示 ToolBar 最左侧按钮。这个按钮名为 HomeAsUp,默认是一个“左箭头”图标,表示返回上一个活动。在此,我们定制了这个按钮的图标与功能。
    在 onOptionsItemSelected() 中,绑定了 HomeAsUp 按钮(android.R.id.home)的点击事件。
    调用 openDrawer() 显示滑动菜单。

    NavigationView

    • 引入依赖库
     implementation 'de.hdodenhof:circleimageview:3.0.1'
     implementation 'com.android.support:design:29.0.2'
    

    引入 circleimageview 库,它可以实现图形圆角化。

    • 菜单内容
      准备一些菜单按钮,放在drawable中
      在 menu 中新建 Menu resource file,名为 nav_menu
    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <group android:checkableBehavior="single">
            <item
                android:id="@+id/profile"
                android:icon="@drawable/profile"
                android:title="Call"/>
            <item
                android:id="@+id/network"
                android:icon="@drawable/network"
                android:title="Friends"/>
            <item
                android:id="@+id/pin"
                android:icon="@drawable/pin"
                android:title="Location"/>
            <item
                android:id="@+id/calendar"
                android:icon="@drawable/calendar"
                android:title="Mail"/>
            <item
                android:id="@+id/settings"
                android:icon="@drawable/settings"
                android:title="Task"/>
        </group>
    </menu>
    
    • 定义了 group 标签,然后把 android:checkableBehavior 设定为 single,表示菜单项只能单选。

    • 每个 item 标签表示菜单项,其中的 icon 表示图标,title 表示显示的文字。

    • 在 layout 中新建 Layout file resource,名为 nav_header.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" 
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:padding="10dp"
        android:background="?attr/colorPrimary"
        >
    
        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/icon"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:src="@drawable/nav_icon"
            android:layout_centerInParent="true"
            />
    
        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:text="Tony Green" 
            android:textColor="#FFF"
            android:textSize="16sp"
            />
    </RelativeLayout>
    
    • 使用 CircleImageView 将指定的图片圆角化,并居中显示。
      修改布局文件:
    
    <com.google.android.material.navigation.NavigationView
    
    android:id="@+id/navigation_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    material:headerLayout="@layout/nav_header"
    material:menu="@menu/nav_menu" />
    
    • 绑定菜单事件
    NavigationView navigationView=(NavigationView) findViewById(R.id.navigation_view);
    //设置 profile 为默认选中
    navigationView.setCheckedItem(R.id.profile);
    //绑定菜单项点击事件
    navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            //关闭滑动菜单
            drawerLayout.closeDrawers();
            return true;
        }
    });
    
    • 调用 setNavigationItemSelectedListener() 来绑定菜单项点击事件。
    • 当发生点击事件后,就会回调 onNavigationItemSelected() 方法。
    展开全文
  • 之前我向大家介绍了史上最简单的滑动菜单的实现方式,相信大家都还记得。如果忘记了其中的实现原理或者还没看过的朋友,请先去看一遍之前的文章Android仿人人客户端滑动菜单的侧滑特效实现代码,史上最简单的侧滑...
  • 之前我向大家介绍了史上最简单的滑动菜单的实现方式,相信大家都还记得。如果忘记了其中的实现原理或者还没看过的朋友,请先去看一遍之前的文章 Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑...

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/8744400


    之前我向大家介绍了史上最简单的滑动菜单的实现方式,相信大家都还记得。如果忘记了其中的实现原理或者还没看过的朋友,请先去看一遍之前的文章 Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现 ,因为我们今天要实现的滑动菜单框架也是基于同样的原理的。


    之前的文章中在最后也提到了,如果是你的应用程序中有很多个Activity都需要加入滑动菜单的功能,那么每个Activity都要写上百行的代码才能实现效果,再简单的滑动菜单实现方案也没用。因此我们今天要实现一个滑动菜单的框架,然后在任何Activity中都可以一分钟引入滑动菜单功能。


    首先还是讲一下实现原理。说是滑动菜单的框架,其实说白了也很简单,就是我们自定义一个布局,在这个自定义布局中实现好滑动菜单的功能,然后只要在Activity的布局文件里面引入我们自定义的布局,这个Activity就拥有了滑动菜单的功能了。原理讲完了,是不是很简单?下面我们来动手实现吧。


    在Eclipse中新建一个Android项目,项目名就叫做RenRenSlidingLayout。


    新建一个类,名叫SlidingLayout,这个类是继承自LinearLayout的,并且实现了OnTouchListener接口,具体代码如下:

    public class SlidingLayout extends LinearLayout implements OnTouchListener {
    
    	/**
    	 * 滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
    	 */
    	public static final int SNAP_VELOCITY = 200;
    
    	/**
    	 * 屏幕宽度值。
    	 */
    	private int screenWidth;
    
    	/**
    	 * 左侧布局最多可以滑动到的左边缘。值由左侧布局的宽度来定,marginLeft到达此值之后,不能再减少。
    	 */
    	private int leftEdge;
    
    	/**
    	 * 左侧布局最多可以滑动到的右边缘。值恒为0,即marginLeft到达0之后,不能增加。
    	 */
    	private int rightEdge = 0;
    
    	/**
    	 * 左侧布局完全显示时,留给右侧布局的宽度值。
    	 */
    	private int leftLayoutPadding = 80;
    
    	/**
    	 * 记录手指按下时的横坐标。
    	 */
    	private float xDown;
    
    	/**
    	 * 记录手指移动时的横坐标。
    	 */
    	private float xMove;
    
    	/**
    	 * 记录手机抬起时的横坐标。
    	 */
    	private float xUp;
    
    	/**
    	 * 左侧布局当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
    	 */
    	private boolean isLeftLayoutVisible;
    
    	/**
    	 * 左侧布局对象。
    	 */
    	private View leftLayout;
    
    	/**
    	 * 右侧布局对象。
    	 */
    	private View rightLayout;
    
    	/**
    	 * 用于监听侧滑事件的View。
    	 */
    	private View mBindView;
    
    	/**
    	 * 左侧布局的参数,通过此参数来重新确定左侧布局的宽度,以及更改leftMargin的值。
    	 */
    	private MarginLayoutParams leftLayoutParams;
    
    	/**
    	 * 右侧布局的参数,通过此参数来重新确定右侧布局的宽度。
    	 */
    	private MarginLayoutParams rightLayoutParams;
    
    	/**
    	 * 用于计算手指滑动的速度。
    	 */
    	private VelocityTracker mVelocityTracker;
    
    	/**
    	 * 重写SlidingLayout的构造函数,其中获取了屏幕的宽度。
    	 * 
    	 * @param context
    	 * @param attrs
    	 */
    	public SlidingLayout(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    		screenWidth = wm.getDefaultDisplay().getWidth();
    	}
    
    	/**
    	 * 绑定监听侧滑事件的View,即在绑定的View进行滑动才可以显示和隐藏左侧布局。
    	 * 
    	 * @param bindView
    	 *            需要绑定的View对象。
    	 */
    	public void setScrollEvent(View bindView) {
    		mBindView = bindView;
    		mBindView.setOnTouchListener(this);
    	}
    
    	/**
    	 * 将屏幕滚动到左侧布局界面,滚动速度设定为30.
    	 */
    	public void scrollToLeftLayout() {
    		new ScrollTask().execute(30);
    	}
    
    	/**
    	 * 将屏幕滚动到右侧布局界面,滚动速度设定为-30.
    	 */
    	public void scrollToRightLayout() {
    		new ScrollTask().execute(-30);
    	}
    
    	/**
    	 * 左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效。
    	 * 
    	 * @return 左侧布局完全显示返回true,完全隐藏返回false。
    	 */
    	public boolean isLeftLayoutVisible() {
    		return isLeftLayoutVisible;
    	}
    
    	/**
    	 * 在onLayout中重新设定左侧布局和右侧布局的参数。
    	 */
    	@Override
    	protected void onLayout(boolean changed, int l, int t, int r, int b) {
    		super.onLayout(changed, l, t, r, b);
    		if (changed) {
    			// 获取左侧布局对象
    			leftLayout = getChildAt(0);
    			leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();
    			// 重置左侧布局对象的宽度为屏幕宽度减去leftLayoutPadding
    			leftLayoutParams.width = screenWidth - leftLayoutPadding;
    			// 设置最左边距为负的左侧布局的宽度
    			leftEdge = -leftLayoutParams.width;
    			leftLayoutParams.leftMargin = leftEdge;
    			leftLayout.setLayoutParams(leftLayoutParams);
    			// 获取右侧布局对象
    			rightLayout = getChildAt(1);
    			rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();
    			rightLayoutParams.width = screenWidth;
    			rightLayout.setLayoutParams(rightLayoutParams);
    		}
    	}
    
    	@Override
    	public boolean onTouch(View v, MotionEvent event) {
    		createVelocityTracker(event);
    		switch (event.getAction()) {
    		case MotionEvent.ACTION_DOWN:
    			// 手指按下时,记录按下时的横坐标
    			xDown = event.getRawX();
    			break;
    		case MotionEvent.ACTION_MOVE:
    			// 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整左侧布局的leftMargin值,从而显示和隐藏左侧布局
    			xMove = event.getRawX();
    			int distanceX = (int) (xMove - xDown);
    			if (isLeftLayoutVisible) {
    				leftLayoutParams.leftMargin = distanceX;
    			} else {
    				leftLayoutParams.leftMargin = leftEdge + distanceX;
    			}
    			if (leftLayoutParams.leftMargin < leftEdge) {
    				leftLayoutParams.leftMargin = leftEdge;
    			} else if (leftLayoutParams.leftMargin > rightEdge) {
    				leftLayoutParams.leftMargin = rightEdge;
    			}
    			leftLayout.setLayoutParams(leftLayoutParams);
    			break;
    		case MotionEvent.ACTION_UP:
    			// 手指抬起时,进行判断当前手势的意图,从而决定是滚动到左侧布局,还是滚动到右侧布局
    			xUp = event.getRawX();
    			if (wantToShowLeftLayout()) {
    				if (shouldScrollToLeftLayout()) {
    					scrollToLeftLayout();
    				} else {
    					scrollToRightLayout();
    				}
    			} else if (wantToShowRightLayout()) {
    				if (shouldScrollToContent()) {
    					scrollToRightLayout();
    				} else {
    					scrollToLeftLayout();
    				}
    			}
    			recycleVelocityTracker();
    			break;
    		}
    		return isBindBasicLayout();
    	}
    
    	/**
    	 * 判断当前手势的意图是不是想显示右侧布局。如果手指移动的距离是负数,且当前左侧布局是可见的,则认为当前手势是想要显示右侧布局。
    	 * 
    	 * @return 当前手势想显示右侧布局返回true,否则返回false。
    	 */
    	private boolean wantToShowRightLayout() {
    		return xUp - xDown < 0 && isLeftLayoutVisible;
    	}
    
    	/**
    	 * 判断当前手势的意图是不是想显示左侧布局。如果手指移动的距离是正数,且当前左侧布局是不可见的,则认为当前手势是想要显示左侧布局。
    	 * 
    	 * @return 当前手势想显示左侧布局返回true,否则返回false。
    	 */
    	private boolean wantToShowLeftLayout() {
    		return xUp - xDown > 0 && !isLeftLayoutVisible;
    	}
    
    	/**
    	 * 判断是否应该滚动将左侧布局展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,
    	 * 就认为应该滚动将左侧布局展示出来。
    	 * 
    	 * @return 如果应该滚动将左侧布局展示出来返回true,否则返回false。
    	 */
    	private boolean shouldScrollToLeftLayout() {
    		return xUp - xDown > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
    	}
    
    	/**
    	 * 判断是否应该滚动将右侧布局展示出来。如果手指移动距离加上leftLayoutPadding大于屏幕的1/2,
    	 * 或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将右侧布局展示出来。
    	 * 
    	 * @return 如果应该滚动将右侧布局展示出来返回true,否则返回false。
    	 */
    	private boolean shouldScrollToContent() {
    		return xDown - xUp + leftLayoutPadding > screenWidth / 2
    				|| getScrollVelocity() > SNAP_VELOCITY;
    	}
    
    	/**
    	 * 判断绑定滑动事件的View是不是一个基础layout,不支持自定义layout,只支持四种基本layout,
    	 * AbsoluteLayout已被弃用。
    	 * 
    	 * @return 如果绑定滑动事件的View是LinearLayout,RelativeLayout,FrameLayout,
    	 *         TableLayout之一就返回true,否则返回false。
    	 */
    	private boolean isBindBasicLayout() {
    		if (mBindView == null) {
    			return false;
    		}
    		String viewName = mBindView.getClass().getName();
    		return viewName.equals(LinearLayout.class.getName())
    				|| viewName.equals(RelativeLayout.class.getName())
    				|| viewName.equals(FrameLayout.class.getName())
    				|| viewName.equals(TableLayout.class.getName());
    	}
    
    	/**
    	 * 创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
    	 * 
    	 * @param event
    	 *            右侧布局监听控件的滑动事件
    	 */
    	private void createVelocityTracker(MotionEvent event) {
    		if (mVelocityTracker == null) {
    			mVelocityTracker = VelocityTracker.obtain();
    		}
    		mVelocityTracker.addMovement(event);
    	}
    
    	/**
    	 * 获取手指在右侧布局的监听View上的滑动速度。
    	 * 
    	 * @return 滑动速度,以每秒钟移动了多少像素值为单位。
    	 */
    	private int getScrollVelocity() {
    		mVelocityTracker.computeCurrentVelocity(1000);
    		int velocity = (int) mVelocityTracker.getXVelocity();
    		return Math.abs(velocity);
    	}
    
    	/**
    	 * 回收VelocityTracker对象。
    	 */
    	private void recycleVelocityTracker() {
    		mVelocityTracker.recycle();
    		mVelocityTracker = null;
    	}
    
    	class ScrollTask extends AsyncTask<Integer, Integer, Integer> {
    
    		@Override
    		protected Integer doInBackground(Integer... speed) {
    			int leftMargin = leftLayoutParams.leftMargin;
    			// 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
    			while (true) {
    				leftMargin = leftMargin + speed[0];
    				if (leftMargin > rightEdge) {
    					leftMargin = rightEdge;
    					break;
    				}
    				if (leftMargin < leftEdge) {
    					leftMargin = leftEdge;
    					break;
    				}
    				publishProgress(leftMargin);
    				// 为了要有滚动效果产生,每次循环使线程睡眠20毫秒,这样肉眼才能够看到滚动动画。
    				sleep(20);
    			}
    			if (speed[0] > 0) {
    				isLeftLayoutVisible = true;
    			} else {
    				isLeftLayoutVisible = false;
    			}
    			return leftMargin;
    		}
    
    		@Override
    		protected void onProgressUpdate(Integer... leftMargin) {
    			leftLayoutParams.leftMargin = leftMargin[0];
    			leftLayout.setLayoutParams(leftLayoutParams);
    		}
    
    		@Override
    		protected void onPostExecute(Integer leftMargin) {
    			leftLayoutParams.leftMargin = leftMargin;
    			leftLayout.setLayoutParams(leftLayoutParams);
    		}
    	}
    
    	/**
    	 * 使当前线程睡眠指定的毫秒数。
    	 * 
    	 * @param millis
    	 *            指定当前线程睡眠多久,以毫秒为单位
    	 */
    	private void sleep(long millis) {
    		try {
    			Thread.sleep(millis);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }

    看到这里,我相信大家一定会觉得这些代码非常熟悉。没错,基本上这些代码和之前那篇文章的代码大同小异,只不过以前这些代码是写在Activity里的,而现在我们移动到了自定义的View当中。


    接着我来说明一下和以前不同的部分。我们可以看到,这里将onLayout方法进行了重写,使用getChildAt(0)获取到的布局作为左边布局,使用getChildAt(1)获取到的布局作为右边布局。并将左边布局的宽度重定义为屏幕宽度减去leftLayoutPadding,将右侧布局的宽度重定义为屏幕宽度。然后让左边布局偏移出屏幕,这样能看到的就只有右边布局了。因此在这里我们也可以看出,使用SlidingLayout这个布局的前提条件,必须为这个布局提供两个子元素,第一个元素会作为左边布局偏移出屏幕,第二个元素会作为右边布局显示在屏幕上。


    然后我们看一下setScrollEvent方法,这个方法接收一个View作为参数,然后为这个View绑定了一个touch事件。这是什么意思呢?让我们来想象一个场景,如果右侧布局是一个LinearLayout,我可以通过监听LinearLayout上的touch事件来控制左侧布局的显示和隐藏。但是如果右侧布局的LinearLayout里面加入了一个ListView,而这个ListView又充满了整个LinearLayout,这个时候LinearLayout将不可能再被touch到了,这个时候我们就需要将touch事件注册到ListView上。setScrollEvent方法也就是提供了一个注册接口,touch事件将会注册到传入的View上。


    最后还有一个陌生的方法,isBindBasicLayout。这个方法就是判断了一下注册touch事件的View是不是四个基本布局之一,如果是就返回true,否则返回false。这个方法在整个SlidingLayout中起着非常重要的作用,主要用于控制onTouch事件是返回true还是false,这将影响到布局当中的View的功能是否可用。由于里面牵扯到了Android的事件转发机制,内容比较多,就不在这里详细解释了,我会考虑以后专门写一篇文章来介绍Android的事件机制。这里就先简单记住如果是基本布局就返回true,否则就返回false。


    好了,我们的SlidingLayout写完了,接下来就是见证奇迹的时刻,让我们一起看看如何一分钟在Activity中引入滑动菜单功能。


    创建或打开layout目录下的activity_main.xml文件,加入如下代码:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal"
        tools:context=".MainActivity" >
    
        <!-- 使用自定义的侧滑布局,orientation必须为水平方向 -->
    
        <com.example.slide.SlidingLayout
            android:id="@+id/slidingLayout"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="horizontal" >
    
            <!--
    	        侧滑布局的根节点下,有且只能有两个子元素,这两个子元素必须是四种基本布局之一,
    	        即LinearLayout, RelativeLayout, FrameLayout或TableLayout。
    	        第一个子元素将做为左侧布局,初始化后被隐藏。第二个子元素将做为右侧布局,
    	        也就是当前Activity的主布局,将主要的数据放在里面。
            -->
    
            <RelativeLayout
                android:id="@+id/menu"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:background="#00ccff" >
    
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:text="This is menu"
                    android:textColor="#000000"
                    android:textSize="28sp" />
            </RelativeLayout>
    
            <LinearLayout
                android:id="@+id/content"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical" >
    
                <Button
                    android:id="@+id/menuButton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Menu" />
    
                <ListView
                    android:id="@+id/contentList"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent" >
                </ListView>
            </LinearLayout>
        </com.example.slide.SlidingLayout>
    
    </LinearLayout>

    我们可以看到,在根布局的下面,我们引入了自定义布局com.example.slide.SlidingLayout,然后在它里面加入了两个子元素,一个RelativeLayout和一个LinearLayout。RelativeLayout中比较简单,就加入了一个TextView。LinearLayout里面我们加入了一个按钮和一个ListView。


    然后创建或打开MainActivity作为程序的主Activity,加入代码:

    public class MainActivity extends Activity {
    
    	/**
    	 * 侧滑布局对象,用于通过手指滑动将左侧的菜单布局进行显示或隐藏。
    	 */
    	private SlidingLayout slidingLayout;
    
    	/**
    	 * menu按钮,点击按钮展示左侧布局,再点击一次隐藏左侧布局。
    	 */
    	private Button menuButton;
    
    	/**
    	 * 放在content布局中的ListView。
    	 */
    	private ListView contentListView;
    
    	/**
    	 * 作用于contentListView的适配器。
    	 */
    	private ArrayAdapter<String> contentListAdapter;
    
    	/**
    	 * 用于填充contentListAdapter的数据源。
    	 */
    	private String[] contentItems = { "Content Item 1", "Content Item 2", "Content Item 3",
    			"Content Item 4", "Content Item 5", "Content Item 6", "Content Item 7",
    			"Content Item 8", "Content Item 9", "Content Item 10", "Content Item 11",
    			"Content Item 12", "Content Item 13", "Content Item 14", "Content Item 15",
    			"Content Item 16" };
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		slidingLayout = (SlidingLayout) findViewById(R.id.slidingLayout);
    		menuButton = (Button) findViewById(R.id.menuButton);
    		contentListView = (ListView) findViewById(R.id.contentList);
    		contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
    				contentItems);
    		contentListView.setAdapter(contentListAdapter);
    		// 将监听滑动事件绑定在contentListView上
    		slidingLayout.setScrollEvent(contentListView);
    		menuButton.setOnClickListener(new OnClickListener() {
    			@Override
    			public void onClick(View v) {
    				// 实现点击一下menu展示左侧布局,再点击一下隐藏左侧布局的功能
    				if (slidingLayout.isLeftLayoutVisible()) {
    					slidingLayout.scrollToRightLayout();
    				} else {
    					slidingLayout.scrollToLeftLayout();
    				}
    			}
    		});
    	}
    
    }

    上述代码重点是调用SlidingLayout的setScrollEvent方法,为ListView注册touch事件。同时给按钮添加了一个点击事件,实现了点击一下显示左边布局,再点击一下隐藏左边布局的功能。


    最后还是老规矩,给出AndroidManifest.xml的代码:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.slide"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="8" />
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.NoTitleBar" >
            <activity
                android:name="com.example.slide.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    好了,现在让我们运行一下吧。首先是程序打开的时候,显示的是右边布局。用手指在界面上向右滑动,可以看到左边布局出现。


                    


    而当左边布局完全显示的时候,效果图如下:


                               


    除此之外,点击Menu按钮也可以控制左边布局的显示和隐藏,大家可以自己试一下。


    使用自定义布局的话,就可以用简单的方式在任意Activity中加入滑动菜单功能,即使你有再多的Activity也不用怕了,一分钟引入滑动菜单妥妥的。


    再总结一下吧,向Activity中加入滑动菜单功能只需要两步:

    1. 在Acitivty的layout中引入我们自定义的布局,并且给这个布局要加入两个直接子元素。

    2. 在Activity中通过setScrollEvent方法,给一个View注册touch事件。


    好了,今天的讲解到此结束,有疑问的朋友请在下面留言。


    源码下载,请点击这里


    补充:

    由于这段文章写的比较早了,那时写的滑动菜单还存在着不少问题,我之后又将上面的代码做了不少改动,编写了一个修正版的滑动菜单,这个版本主要修正了以下内容:


    1.将滑动方式改成了覆盖型。

    2.ListView上下滚动时不会轻易滑出菜单。

    3.正在滑动时屏蔽掉内容布局上的事件。

    4.当菜单布局展示时,点击一下右侧的内容布局,可以将菜单隐藏。

    5.修复刚打开程序时,菜单可能会短暂显示一下,然后瞬间消失的bug。


    修正版源码下载,请点击这里


    另外,有对双向滑动菜单感兴趣的朋友请转阅  Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效


    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

            

    展开全文
  • 左侧滑动菜单

    2017-03-27 15:51:28
    左侧滑动菜单
  • android 滑动菜单SlidingMenu的实现

    万次阅读 多人点赞 2012-10-24 12:09:00
    首先我们看下面视图:   这种效果大家都不陌生,网上好多都说是仿人人网的,估计人家牛逼出来的早吧,我也参考了一一些例子,实现起来有三种方法,我下面简单介绍下: 方法一:其实就是对GestureDetector手势的...

    首先我们看下面视图:

          

    这种效果大家都不陌生,网上好多都说是仿人人网的,估计人家牛逼出来的早吧,我也参考了一一些例子,实现起来有三种方法,我下面简单介绍下:

    方法一:其实就是对GestureDetector手势的应用及布局文件的设计.

    布局文件main.xml    采用RelativeLayout布局.

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
    
        <LinearLayout
            android:id="@+id/layout_right"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_marginLeft="50dp"
            android:orientation="vertical" >
    
            <AbsoluteLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@color/grey21"
                android:padding="10dp" >
    
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="设置"
                    android:textColor="@android:color/background_light"
                    android:textSize="20sp" />
            </AbsoluteLayout>
    
            <ListView
                android:id="@+id/lv_set"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:layout_weight="1" >
            </ListView>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/layout_left"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="@color/white"
            android:orientation="vertical" >
    
            <RelativeLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/nav_bg" >
    
                <ImageView
                    android:id="@+id/iv_set"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_alignParentTop="true"
                    android:src="@drawable/nav_setting" />
    
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:text="我"
                    android:textColor="@android:color/background_light"
                    android:textSize="20sp" />
            </RelativeLayout>
    
            <ImageView
                android:id="@+id/iv_set"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:scaleType="fitXY"
                android:src="@drawable/bg_guide_5" />
        </LinearLayout>
    
    </RelativeLayout>

    layout_right:这个大布局文件,layout_left:距离左边50dp像素.(我们要移动的是layout_left).

    看到这个图我想大家都很清晰了吧,其实:我们就是把layout_left这个布局控件整理向左移动,至于移动多少,就要看layout_right有多宽了。layout_left移动到距离左边的边距就是layout_right的宽及-MAX_WIDTH.相信大家都理解.

    布局文件就介绍到这里,下面看代码.

    /***
    	 * 初始化view
    	 */
    	void InitView() {
    		layout_left = (LinearLayout) findViewById(R.id.layout_left);
    		layout_right = (LinearLayout) findViewById(R.id.layout_right);
    		iv_set = (ImageView) findViewById(R.id.iv_set);
    		lv_set = (ListView) findViewById(R.id.lv_set);
    		lv_set.setAdapter(new ArrayAdapter<String>(this, R.layout.item,
    				R.id.tv_item, title));
    		lv_set.setOnItemClickListener(new OnItemClickListener() {
    
    			@Override
    			public void onItemClick(AdapterView<?> parent, View view,
    					int position, long id) {
    				Toast.makeText(MainActivity.this, title[position], 1).show();
    			}
    		});
    		layout_left.setOnTouchListener(this);
    		iv_set.setOnTouchListener(this);
    		mGestureDetector = new GestureDetector(this);
    		// 禁用长按监听
    		mGestureDetector.setIsLongpressEnabled(false);
    		getMAX_WIDTH();
    	}
    这里要对手势进行监听,我想大家都知道怎么做,在这里我要说明一个方法:

    /***
    	 * 获取移动距离 移动的距离其实就是layout_left的宽度
    	 */
    	void getMAX_WIDTH() {
    		ViewTreeObserver viewTreeObserver = layout_left.getViewTreeObserver();
    		// 获取控件宽度
    		viewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() {
    			@Override
    			public boolean onPreDraw() {
    				if (!hasMeasured) {
    					window_width = getWindowManager().getDefaultDisplay()
    							.getWidth();
    					RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left
    							.getLayoutParams();
    					layoutParams.width = window_width;
    					layout_left.setLayoutParams(layoutParams);
    					MAX_WIDTH = layout_right.getWidth();
    					Log.v(TAG, "MAX_WIDTH=" + MAX_WIDTH + "width="
    							+ window_width);
    					hasMeasured = true;
    				}
    				return true;
    			}
    		});
    
    	}

    在这里我们要获取屏幕的宽度,并将屏幕宽度设置给layout_left这个控件,为什么要这么做呢因为如果不把该控件宽度写死的话,那么系统将认为layout_left会根据不同环境宽度自动适应,也就是说我们通过layout_left.getLayoutParams动态移动该控件的时候,该控件会伸缩而不是移动。描述的有点模糊,大家请看下面示意图就明白了.

    我们不为layout_left定义死宽度效果:

         

    getLayoutParams可以很清楚看到,layout_left被向左拉伸了,并不是我们要的效果.

    还有一种解决办法就是我们在配置文件中直接把layout_left宽度写死,不过这样不利于开发,因为分辨率的问题.因此就用ViewTreeObserver进行对layout_left设置宽度.

    ViewTreeObserver,这个类主要用于对布局文件的监听.强烈建议同学们参考这篇文章 android ViewTreeObserver详细讲解,相信让你对ViewTreeObserver有更一步的了解.

    其他的就是对GestureDetector手势的应用,下面我把代码贴出来:

    package com.jj.slidingmenu;
    
    import android.app.Activity;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.KeyEvent;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.view.ViewTreeObserver.OnPreDrawListener;
    import android.view.Window;
    import android.view.View.OnTouchListener;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.ArrayAdapter;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.ListView;
    import android.widget.RelativeLayout;
    import android.widget.Toast;
    import android.widget.LinearLayout.LayoutParams;
    
    /***
     * 滑动菜单
     * 
     * @author jjhappyforever...
     * 
     */
    public class MainActivity extends Activity implements OnTouchListener,
    		GestureDetector.OnGestureListener {
    	private boolean hasMeasured = false;// 是否Measured.
    	private LinearLayout layout_left;
    	private LinearLayout layout_right;
    	private ImageView iv_set;
    	private ListView lv_set;
    
    	/** 每次自动展开/收缩的范围 */
    	private int MAX_WIDTH = 0;
    	/** 每次自动展开/收缩的速度 */
    	private final static int SPEED = 30;
    
    	private GestureDetector mGestureDetector;// 手势
    	private boolean isScrolling = false;
    	private float mScrollX; // 滑块滑动距离
    	private int window_width;// 屏幕的宽度
    
    	private String TAG = "jj";
    
    	private String title[] = { "待发送队列", "同步分享设置", "编辑我的资料", "找朋友", "告诉朋友",
    			"节省流量", "推送设置", "版本更新", "意见反馈", "积分兑换", "精品应用", "常见问题", "退出当前帐号" };
    
    	/***
    	 * 初始化view
    	 */
    	void InitView() {
    		layout_left = (LinearLayout) findViewById(R.id.layout_left);
    		layout_right = (LinearLayout) findViewById(R.id.layout_right);
    		iv_set = (ImageView) findViewById(R.id.iv_set);
    		lv_set = (ListView) findViewById(R.id.lv_set);
    		lv_set.setAdapter(new ArrayAdapter<String>(this, R.layout.item,
    				R.id.tv_item, title));
    		lv_set.setOnItemClickListener(new OnItemClickListener() {
    
    			@Override
    			public void onItemClick(AdapterView<?> parent, View view,
    					int position, long id) {
    				Toast.makeText(MainActivity.this, title[position], 1).show();
    			}
    		});
    		layout_left.setOnTouchListener(this);
    		iv_set.setOnTouchListener(this);
    		mGestureDetector = new GestureDetector(this);
    		// 禁用长按监听
    		mGestureDetector.setIsLongpressEnabled(false);
    		getMAX_WIDTH();
    	}
    
    	/***
    	 * 获取移动距离 移动的距离其实就是layout_left的宽度
    	 */
    	void getMAX_WIDTH() {
    		ViewTreeObserver viewTreeObserver = layout_left.getViewTreeObserver();
    		// 获取控件宽度
    		viewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() {
    			@Override
    			public boolean onPreDraw() {
    				if (!hasMeasured) {
    					window_width = getWindowManager().getDefaultDisplay()
    							.getWidth();
    					RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left
    							.getLayoutParams();
    					// layoutParams.width = window_width;
    					layout_left.setLayoutParams(layoutParams);
    					MAX_WIDTH = layout_right.getWidth();
    					Log.v(TAG, "MAX_WIDTH=" + MAX_WIDTH + "width="
    							+ window_width);
    					hasMeasured = true;
    				}
    				return true;
    			}
    		});
    
    	}
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		requestWindowFeature(Window.FEATURE_NO_TITLE);
    		setContentView(R.layout.main);
    		InitView();
    
    	}
    
    	// 返回键
    	@Override
    	public boolean onKeyDown(int keyCode, KeyEvent event) {
    		if (KeyEvent.KEYCODE_BACK == keyCode && event.getRepeatCount() == 0) {
    			RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left
    					.getLayoutParams();
    			if (layoutParams.leftMargin < 0) {
    				new AsynMove().execute(SPEED);
    				return false;
    			}
    		}
    
    		return super.onKeyDown(keyCode, event);
    	}
    
    	@Override
    	public boolean onTouch(View v, MotionEvent event) {
    		// 松开的时候要判断,如果不到半屏幕位子则缩回去,
    		if (MotionEvent.ACTION_UP == event.getAction() && isScrolling == true) {
    			RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left
    					.getLayoutParams();
    			// 缩回去
    			if (layoutParams.leftMargin < -window_width / 2) {
    				new AsynMove().execute(-SPEED);
    			} else {
    				new AsynMove().execute(SPEED);
    			}
    		}
    
    		return mGestureDetector.onTouchEvent(event);
    	}
    
    	@Override
    	public boolean onDown(MotionEvent e) {
    		mScrollX = 0;
    		isScrolling = false;
    		// 将之改为true,不然事件不会向下传递.
    		return true;
    	}
    
    	@Override
    	public void onShowPress(MotionEvent e) {
    
    	}
    
    	/***
    	 * 点击松开执行
    	 */
    	@Override
    	public boolean onSingleTapUp(MotionEvent e) {
    		RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left
    				.getLayoutParams();
    		// 左移动
    		if (layoutParams.leftMargin >= 0) {
    			new AsynMove().execute(-SPEED);
    		} else {
    			// 右移动
    			new AsynMove().execute(SPEED);
    		}
    
    		return true;
    	}
    
    	/***
    	 * e1 是起点,e2是终点,如果distanceX=e1.x-e2.x>0说明向左滑动。反之亦如此.
    	 */
    	@Override
    	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
    			float distanceY) {
    		isScrolling = true;
    		mScrollX += distanceX;// distanceX:向左为正,右为负
    		RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left
    				.getLayoutParams();
    		layoutParams.leftMargin -= mScrollX;
    		if (layoutParams.leftMargin >= 0) {
    			isScrolling = false;// 拖过头了不需要再执行AsynMove了
    			layoutParams.leftMargin = 0;
    
    		} else if (layoutParams.leftMargin <= -MAX_WIDTH) {
    			// 拖过头了不需要再执行AsynMove了
    			isScrolling = false;
    			layoutParams.leftMargin = -MAX_WIDTH;
    		}
    		layout_left.setLayoutParams(layoutParams);
    		return false;
    	}
    
    	@Override
    	public void onLongPress(MotionEvent e) {
    
    	}
    
    	@Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    			float velocityY) {
    		return false;
    	}
    
    	class AsynMove extends AsyncTask<Integer, Integer, Void> {
    
    		@Override
    		protected Void doInBackground(Integer... params) {
    			int times = 0;
    			if (MAX_WIDTH % Math.abs(params[0]) == 0)// 整除
    				times = MAX_WIDTH / Math.abs(params[0]);
    			else
    				times = MAX_WIDTH / Math.abs(params[0]) + 1;// 有余数
    
    			for (int i = 0; i < times; i++) {
    				publishProgress(params[0]);
    				try {
    					Thread.sleep(Math.abs(params[0]));
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			}
    
    			return null;
    		}
    
    		/**
    		 * update UI
    		 */
    		@Override
    		protected void onProgressUpdate(Integer... values) {
    			RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left
    					.getLayoutParams();
    			// 右移动
    			if (values[0] > 0) {
    				layoutParams.leftMargin = Math.min(layoutParams.leftMargin
    						+ values[0], 0);
    				Log.v(TAG, "移动右" + layoutParams.rightMargin);
    			} else {
    				// 左移动
    				layoutParams.leftMargin = Math.max(layoutParams.leftMargin
    						+ values[0], -MAX_WIDTH);
    				Log.v(TAG, "移动左" + layoutParams.rightMargin);
    			}
    			layout_left.setLayoutParams(layoutParams);
    
    		}
    
    	}
    
    }
    上面代码注释已经很明确,相信大家都看的明白,我就不过多解释了。

    效果图:截屏出来有点卡,不过在手机虚拟机上是不卡的.



    源码下载


    怎么样,看着还行吧,我们在看下面一个示例:



    简单说明一下,当你滑动的时候左边会跟着右边一起滑动,这个效果比上面那个酷吧,上面那个有点死板,其实实现起来也比较容易,只需要把我们上面那个稍微修改下,对layout_right也进行时时更新,这样就实现了这个效果了,如果上面那个理解了,这个很轻松就解决了,在这里我又遇到一个问题:此时的listview的item监听不到手势,意思就是我左右滑动listview他没有进行滑动。

    本人对touch众多事件监听拦截等熟悉度不够,因此这里我用到自己写的方法,也许比较麻烦,如果有更好的解决办法,请大家一定要分享哦,再次 thanks for you 了.

    具体解决办法:我们重写listview,对此listview进行手势监听,我们自定义一个接口来实现,具体代码如下:

    package com.jj.slidingmenu;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.MotionEvent;
    import android.view.GestureDetector.OnGestureListener;
    import android.view.View;
    import android.widget.ListView;
    import android.widget.Toast;
    
    public class MyListView extends ListView implements OnGestureListener {
    
    	private GestureDetector gd;
    	// 事件状态
    	public static final char FLING_CLICK = 0;
    	public static final char FLING_LEFT = 1;
    	public static final char FLING_RIGHT = 2;
    	public static char flingState = FLING_CLICK;
    
    	private float distanceX;// 水平滑动的距离
    
    	private MyListViewFling myListViewFling;
    
    	public static boolean isClick = false;// 是否可以点击
    
    	public void setMyListViewFling(MyListViewFling myListViewFling) {
    		this.myListViewFling = myListViewFling;
    	}
    
    	public float getDistanceX() {
    		return distanceX;
    	}
    
    	public char getFlingState() {
    		return flingState;
    	}
    
    	private Context context;
    
    	public MyListView(Context context) {
    		super(context);
    
    	}
    
    	public MyListView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		this.context = context;
    		gd = new GestureDetector(this);
    	}
    
    	/**
    	 * 覆写此方法,以解决ListView滑动被屏蔽问题
    	 */
    	@Override
    	public boolean dispatchTouchEvent(MotionEvent event) {
    		myListViewFling.doFlingOver(event);// 回调执行完毕.
    		this.gd.onTouchEvent(event);
    
    		return super.dispatchTouchEvent(event);
    	}
    
    	@Override
    	public boolean onTouchEvent(MotionEvent ev) {
    		/***
    		 * 当移动的时候
    		 */
    		if (ev.getAction() == MotionEvent.ACTION_DOWN)
    			isClick = true;
    		if (ev.getAction() == MotionEvent.ACTION_MOVE)
    			isClick = false;
    		return super.onTouchEvent(ev);
    	}
    
    	@Override
    	public boolean onDown(MotionEvent e) {
    		int position = pointToPosition((int) e.getX(), (int) e.getY());
    		if (position != ListView.INVALID_POSITION) {
    			View child = getChildAt(position - getFirstVisiblePosition());
    		}
    		return true;
    	}
    
    	@Override
    	public void onShowPress(MotionEvent e) {
    
    	}
    
    	@Override
    	public boolean onSingleTapUp(MotionEvent e) {
    
    		return false;
    	}
    
    	@Override
    	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
    			float distanceY) {
    		// 左滑动
    		if (distanceX > 0) {
    			flingState = FLING_RIGHT;
    			Log.v("jj", "左distanceX=" + distanceX);
    			myListViewFling.doFlingLeft(distanceX);// 回调
    			// 右滑动.
    		} else if (distanceX < 0) {
    			flingState = FLING_LEFT;
    			Log.v("jj", "右distanceX=" + distanceX);
    			myListViewFling.doFlingRight(distanceX);// 回调
    		}
    
    		return false;
    	}
    
    	/***
    	 * 上下文菜单
    	 */
    	@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) {
    		// showContextMenuForChild(child);
    		// this.requestFocusFromTouch();
    		// }
    		//
    		// }
    	}
    
    	@Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    			float velocityY) {
    
    		return false;
    	}
    
    	/***
    	 * 回调接口
    	 * 
    	 * @author jjhappyforever...
    	 * 
    	 */
    	interface MyListViewFling {
    		void doFlingLeft(float distanceX);// 左滑动执行
    
    		void doFlingRight(float distanceX);// 右滑动执行
    
    		void doFlingOver(MotionEvent event);// 拖拽松开时执行
    
    	}
    
    }
    

    而在MainActivity.java里面实现该接口,我这么一说,我想有的同学们都明白了,具体实现起来代码有点多,我把代码上传到网上,大家可以下载后用心看,我想大家都能够明白的.(在这里我鄙视一下自己,肯定通过对手势监听拦截实现对listview的左右滑动,但是自己学业不经,再次再说一下,如有好的解决方案,请一定要分享我一下哦.)

    另外有一个问题:当listivew超出一屏的时候,此时的listview滑动的时候可以上下左右一起滑动,在此没有解决这个问题,如有解决请分享我哦.

    效果图:



    源码下载


    补充说明上面这个例子有点小BUG,就是右边菜单过长的话,我不仅可以上下滑动,同时也可以左右滑动,这点肯定不是我们想要的效果,其实下面已经解决了这个问题,就是我们自定义一个布局文件,在布局文件中进行对Touch事件监听.效果比上面好的多,至于网上别的样式,我想大家都应该可以效仿实现,这里就不一一讲解了,关键:大家要明白原理,遇到问题知道怎么处理,话费时间长没关系,只要可以搞定.(网上有的朋友说这个有重影,有的布局会变形,其实和我们的布局有关,因为我们用的是AbsoluteLayout布局,但是只要你懂得怎么用,那些问题都不是问题.)


    更正后源码下载



    由于篇符较长,先说到这里,其实android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu也可以实现.具体参考下一篇文章:android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu


    /*********************************************************************************************/


    下面介绍下

    android 滑动菜单SlidingMenu之拓展(解决ListView滑动冲突)

                    

    百度新闻客户端可以手势左划右划,而操作的对象是一个ListView,大家都知道SlidingMenu里的ListView加手势GestureDetector就是蛋疼的操作,但是百度人家就这么搞,而且做的相当棒,其他的应用我很少见到如此的,不得不说,牛逼有牛逼的道理.

    网上我搜查了,没有找到类似的案例,只能自己琢磨了,功夫不负有心人啊,终于实现了,方法比较笨戳,下面我简单讲解下:

    实现原理:Touch事件的拦截与分发.

    在项目中,由于点击不同的菜单要显示不同的内容,所以右边最好弄成活动布局,就是添加一个Linerlayout,动态添加相应布局,这样扩展比较容易.但是这个Linerlayout我们要自己定义,因为我们要拦截一些Touch事件.(实现:当我们上下滑动,ListView上下滑动,当我们左右滑动ListView禁止上下滑动,进行左右滑动)

    package com.hytrip.ui.custom;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.GestureDetector.SimpleOnGestureListener;
    import android.widget.LinearLayout;
    
    /***
     * 行程详情的自定义布局
     * 
     * @author zhangjia
     * 
     */
    public class JourneyLinearLayout extends LinearLayout {
    	private GestureDetector mGestureDetector;
    	View.OnTouchListener mGestureListener;
    
    	private boolean isLock = true;
    
    	private OnScrollListener onScrollListener;// 自定义接口
    
    	private boolean b;
    
    	public JourneyLinearLayout(Context context) {
    		super(context);
    	}
    
    	public void setOnScrollListener(OnScrollListener onScrollListener) {
    		this.onScrollListener = onScrollListener;
    	}
    
    	public JourneyLinearLayout(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		mGestureDetector = new GestureDetector(new MySimpleGesture());
    	}
    
    	@Override
    	public boolean dispatchTouchEvent(MotionEvent ev) {
    		Log.e("jj", "dispatchTouchEvent...");
    		// 获取手势返回值
    		b = mGestureDetector.onTouchEvent(ev);
    		// 松开手要执行一些操作。(关闭 or 打开)
    		if (ev.getAction() == MotionEvent.ACTION_UP) {
    			onScrollListener.doLoosen();
    		}
    		return super.dispatchTouchEvent(ev);
    	}
    
    	@Override
    	public boolean onInterceptTouchEvent(MotionEvent ev) {
    		Log.e("jj", "onInterceptTouchEvent...");
    		super.onInterceptTouchEvent(ev);
    		return b;
    	}
        /***
         * 在这里我简单说明一下
         */
    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    		Log.e("jj", "onTouchEvent...");
    		isLock = false;
    		return super.onTouchEvent(event);
    	}
    
    	/***
    	 * 自定义手势执行
    	 * 
    	 * @author zhangjia
    	 * 
    	 */
    	class MySimpleGesture extends SimpleOnGestureListener {
    
    		@Override
    		public boolean onDown(MotionEvent e) {
    			Log.e("jj", "onDown...");
    			isLock = true;
    			return super.onDown(e);
    		}
    
    		@Override
    		public boolean onScroll(MotionEvent e1, MotionEvent e2,
    				float distanceX, float distanceY) {
    
    			if (!isLock)
    				onScrollListener.doScroll(distanceX);
    
    			// 垂直大于水平
    			if (Math.abs(distanceY) > Math.abs(distanceX)) {
    				// Log.e("jjj", "ll...垂直...");
    				return false;
    			} else {
    				// Log.e("jjj", "ll...水平...");
    				// Log.e("jj", "distanceX===" + distanceX);
    				return true;
    			}
    
    		}
    	}
    
    	/***
    	 * 自定义接口 实现滑动...
    	 * 
    	 * @author zhangjia
    	 * 
    	 */
    	public interface OnScrollListener {
    		void doScroll(float distanceX);// 滑动...
    
    		void doLoosen();// 手指松开后执行...
    	}
    
    }
    

    说明1:顺序:dispatchTouchEvent》GestureDetector》onInterceptTouchEvent》onTouchEvent.

    说明2:onInterceptTouchEvent 返回true,则拦截孩子touch事件,执行当前OnTouch事件,而返回false,则不执行OnTouch事件,事件传递给孩子执行。。。

    因为onInterceptTouchEvent 是用于拦截Touch的,不适用于执行一些操作,所以把注入手势操作方法分发事件dispatchTouchEvent中.

    下面是自定义的一个接口(方法1:滑动中。。。方法2:松开自动合拢。。。),用于实现手势移动操作,在SlidingMenuActivity.java中实现其接口.

    (写的比较凌乱,但是如果你仔细看的话一定会明白的,弄懂事件的传递对你自定义想实现一些牛叉View会有帮助的.鄙人正在研究中...)

        

     因为是模型,所以样子很丑,不过重要的是实现方法.

    弄懂上面那个,下面我们在深入看一下:最上面最后一张图片,我们在滑动中间图片的时候ListView肯定是不要进行左划或者右划,是不是有点头大了,其实分析好了,也不难,我们只要对上面那个自定义类稍微修调一下:我们在滑动左右滑动判断一下,如果是ListView的HeadView,那么我们就不进行手势操作,这样ViewPager就可以左右滑动,而ListView就不会左右滑动了,如果不是HeadView还照常就Ok了,简单吧。

    下面是示例图:

                         


       左右拖拽图片局域(ListView未受影响)                左右拖拽(非图片)局域


    就说到这里,如有疑问请留言。

    对你有帮助的话,记得赞一个,不要钱的.

    Thanks for you !



    展开全文
  • 滑动菜单 AMSlideMenu.zip

    2019-09-17 18:38:40
    滑动菜单 AMSlideMenu ,AMSlideMenu iOS 的滑动菜单(允许左右滑动的菜单)。
  • 记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个朋友在评论中留言,希望我可以帮他将这个滑动菜单改成双向滑动的方式。当时也没想花太多时间,简单修改了一下就发给了他,结果没想到后来却有一...

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609


    记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个朋友在评论中留言,希望我可以帮他将这个滑动菜单改成双向滑动的方式。当时也没想花太多时间,简单修改了一下就发给了他,结果没想到后来却有一大批的朋友都来问我要这份双向滑动菜单的代码。由于这份代码写得很不用心,我发了部分朋友之后实在不忍心继续发下去了,于是决定专门写一篇文章来介绍更好的Android双向滑动菜单的实现方法。


    在开始动手之前先来讲一下实现原理,在一个Activity的布局中需要有三部分,一个是左侧菜单的布局,一个是右侧菜单的布局,一个是内容布局。左侧菜单居屏幕左边缘对齐,右侧菜单居屏幕右边缘对齐,然后内容布局占满整个屏幕,并压在了左侧菜单和右侧菜单的上面。当用户手指向右滑动时,将右侧菜单隐藏,左侧菜单显示,然后通过偏移内容布局的位置,就可以让左侧菜单展现出来。同样的道理,当用户手指向左滑动时,将左侧菜单隐藏,右侧菜单显示,也是通过偏移内容布局的位置,就可以让右侧菜单展现出来。原理示意图所下所示:




    介绍完了原理,我们就开始动手实现吧。新建一个Android项目,项目名就叫做BidirSlidingLayout。然后新建我们最主要的BidirSlidingLayout类,这个类就是实现双向滑动菜单功能的核心类,代码如下所示:

    public class BidirSlidingLayout extends RelativeLayout implements OnTouchListener {
    
    	/**
    	 * 滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
    	 */
    	public static final int SNAP_VELOCITY = 200;
    
    	/**
    	 * 滑动状态的一种,表示未进行任何滑动。
    	 */
    	public static final int DO_NOTHING = 0;
    
    	/**
    	 * 滑动状态的一种,表示正在滑出左侧菜单。
    	 */
    	public static final int SHOW_LEFT_MENU = 1;
    
    	/**
    	 * 滑动状态的一种,表示正在滑出右侧菜单。
    	 */
    	public static final int SHOW_RIGHT_MENU = 2;
    
    	/**
    	 * 滑动状态的一种,表示正在隐藏左侧菜单。
    	 */
    	public static final int HIDE_LEFT_MENU = 3;
    
    	/**
    	 * 滑动状态的一种,表示正在隐藏右侧菜单。
    	 */
    	public static final int HIDE_RIGHT_MENU = 4;
    
    	/**
    	 * 记录当前的滑动状态
    	 */
    	private int slideState;
    
    	/**
    	 * 屏幕宽度值。
    	 */
    	private int screenWidth;
    
    	/**
    	 * 在被判定为滚动之前用户手指可以移动的最大值。
    	 */
    	private int touchSlop;
    
    	/**
    	 * 记录手指按下时的横坐标。
    	 */
    	private float xDown;
    
    	/**
    	 * 记录手指按下时的纵坐标。
    	 */
    	private float yDown;
    
    	/**
    	 * 记录手指移动时的横坐标。
    	 */
    	private float xMove;
    
    	/**
    	 * 记录手指移动时的纵坐标。
    	 */
    	private float yMove;
    
    	/**
    	 * 记录手机抬起时的横坐标。
    	 */
    	private float xUp;
    
    	/**
    	 * 左侧菜单当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
    	 */
    	private boolean isLeftMenuVisible;
    
    	/**
    	 * 右侧菜单当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
    	 */
    	private boolean isRightMenuVisible;
    
    	/**
    	 * 是否正在滑动。
    	 */
    	private boolean isSliding;
    
    	/**
    	 * 左侧菜单布局对象。
    	 */
    	private View leftMenuLayout;
    
    	/**
    	 * 右侧菜单布局对象。
    	 */
    	private View rightMenuLayout;
    
    	/**
    	 * 内容布局对象。
    	 */
    	private View contentLayout;
    
    	/**
    	 * 用于监听滑动事件的View。
    	 */
    	private View mBindView;
    
    	/**
    	 * 左侧菜单布局的参数。
    	 */
    	private MarginLayoutParams leftMenuLayoutParams;
    
    	/**
    	 * 右侧菜单布局的参数。
    	 */
    	private MarginLayoutParams rightMenuLayoutParams;
    
    	/**
    	 * 内容布局的参数。
    	 */
    	private RelativeLayout.LayoutParams contentLayoutParams;
    
    	/**
    	 * 用于计算手指滑动的速度。
    	 */
    	private VelocityTracker mVelocityTracker;
    
    	/**
    	 * 重写BidirSlidingLayout的构造函数,其中获取了屏幕的宽度和touchSlop的值。
    	 * 
    	 * @param context
    	 * @param attrs
    	 */
    	public BidirSlidingLayout(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    		screenWidth = wm.getDefaultDisplay().getWidth();
    		touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    	}
    
    	/**
    	 * 绑定监听滑动事件的View。
    	 * 
    	 * @param bindView
    	 *            需要绑定的View对象。
    	 */
    	public void setScrollEvent(View bindView) {
    		mBindView = bindView;
    		mBindView.setOnTouchListener(this);
    	}
    
    	/**
    	 * 将界面滚动到左侧菜单界面,滚动速度设定为-30.
    	 */
    	public void scrollToLeftMenu() {
    		new LeftMenuScrollTask().execute(-30);
    	}
    
    	/**
    	 * 将界面滚动到右侧菜单界面,滚动速度设定为-30.
    	 */
    	public void scrollToRightMenu() {
    		new RightMenuScrollTask().execute(-30);
    	}
    
    	/**
    	 * 将界面从左侧菜单滚动到内容界面,滚动速度设定为30.
    	 */
    	public void scrollToContentFromLeftMenu() {
    		new LeftMenuScrollTask().execute(30);
    	}
    
    	/**
    	 * 将界面从右侧菜单滚动到内容界面,滚动速度设定为30.
    	 */
    	public void scrollToContentFromRightMenu() {
    		new RightMenuScrollTask().execute(30);
    	}
    
    	/**
    	 * 左侧菜单是否完全显示出来,滑动过程中此值无效。
    	 * 
    	 * @return 左侧菜单完全显示返回true,否则返回false。
    	 */
    	public boolean isLeftLayoutVisible() {
    		return isLeftMenuVisible;
    	}
    
    	/**
    	 * 右侧菜单是否完全显示出来,滑动过程中此值无效。
    	 * 
    	 * @return 右侧菜单完全显示返回true,否则返回false。
    	 */
    	public boolean isRightLayoutVisible() {
    		return isRightMenuVisible;
    	}
    
    	/**
    	 * 在onLayout中重新设定左侧菜单、右侧菜单、以及内容布局的参数。
    	 */
    	@Override
    	protected void onLayout(boolean changed, int l, int t, int r, int b) {
    		super.onLayout(changed, l, t, r, b);
    		if (changed) {
    			// 获取左侧菜单布局对象
    			leftMenuLayout = getChildAt(0);
    			leftMenuLayoutParams = (MarginLayoutParams) leftMenuLayout.getLayoutParams();
    			// 获取右侧菜单布局对象
    			rightMenuLayout = getChildAt(1);
    			rightMenuLayoutParams = (MarginLayoutParams) rightMenuLayout.getLayoutParams();
    			// 获取内容布局对象
    			contentLayout = getChildAt(2);
    			contentLayoutParams = (RelativeLayout.LayoutParams) contentLayout.getLayoutParams();
    			contentLayoutParams.width = screenWidth;
    			contentLayout.setLayoutParams(contentLayoutParams);
    		}
    	}
    
    	@Override
    	public boolean onTouch(View v, MotionEvent event) {
    		createVelocityTracker(event);
    		switch (event.getAction()) {
    		case MotionEvent.ACTION_DOWN:
    			// 手指按下时,记录按下时的坐标
    			xDown = event.getRawX();
    			yDown = event.getRawY();
    			// 将滑动状态初始化为DO_NOTHING
    			slideState = DO_NOTHING;
    			break;
    		case MotionEvent.ACTION_MOVE:
    			xMove = event.getRawX();
    			yMove = event.getRawY();
    			// 手指移动时,对比按下时的坐标,计算出移动的距离。
    			int moveDistanceX = (int) (xMove - xDown);
    			int moveDistanceY = (int) (yMove - yDown);
    			// 检查当前的滑动状态
    			checkSlideState(moveDistanceX, moveDistanceY);
    			// 根据当前滑动状态决定如何偏移内容布局
    			switch (slideState) {
    			case SHOW_LEFT_MENU:
    				contentLayoutParams.rightMargin = -moveDistanceX;
    				checkLeftMenuBorder();
    				contentLayout.setLayoutParams(contentLayoutParams);
    				break;
    			case HIDE_LEFT_MENU:
    				contentLayoutParams.rightMargin = -leftMenuLayoutParams.width - moveDistanceX;
    				checkLeftMenuBorder();
    				contentLayout.setLayoutParams(contentLayoutParams);
    			case SHOW_RIGHT_MENU:
    				contentLayoutParams.leftMargin = moveDistanceX;
    				checkRightMenuBorder();
    				contentLayout.setLayoutParams(contentLayoutParams);
    				break;
    			case HIDE_RIGHT_MENU:
    				contentLayoutParams.leftMargin = -rightMenuLayoutParams.width + moveDistanceX;
    				checkRightMenuBorder();
    				contentLayout.setLayoutParams(contentLayoutParams);
    			default:
    				break;
    			}
    			break;
    		case MotionEvent.ACTION_UP:
    			xUp = event.getRawX();
    			int upDistanceX = (int) (xUp - xDown);
    			if (isSliding) {
    				// 手指抬起时,进行判断当前手势的意图
    				switch (slideState) {
    				case SHOW_LEFT_MENU:
    					if (shouldScrollToLeftMenu()) {
    						scrollToLeftMenu();
    					} else {
    						scrollToContentFromLeftMenu();
    					}
    					break;
    				case HIDE_LEFT_MENU:
    					if (shouldScrollToContentFromLeftMenu()) {
    						scrollToContentFromLeftMenu();
    					} else {
    						scrollToLeftMenu();
    					}
    					break;
    				case SHOW_RIGHT_MENU:
    					if (shouldScrollToRightMenu()) {
    						scrollToRightMenu();
    					} else {
    						scrollToContentFromRightMenu();
    					}
    					break;
    				case HIDE_RIGHT_MENU:
    					if (shouldScrollToContentFromRightMenu()) {
    						scrollToContentFromRightMenu();
    					} else {
    						scrollToRightMenu();
    					}
    					break;
    				default:
    					break;
    				}
    			} else if (upDistanceX < touchSlop && isLeftMenuVisible) {
    				// 当左侧菜单显示时,如果用户点击一下内容部分,则直接滚动到内容界面
    				scrollToContentFromLeftMenu();
    			} else if (upDistanceX < touchSlop && isRightMenuVisible) {
    				// 当右侧菜单显示时,如果用户点击一下内容部分,则直接滚动到内容界面
    				scrollToContentFromRightMenu();
    			}
    			recycleVelocityTracker();
    			break;
    		}
    		if (v.isEnabled()) {
    			if (isSliding) {
    				// 正在滑动时让控件得不到焦点
    				unFocusBindView();
    				return true;
    			}
    			if (isLeftMenuVisible || isRightMenuVisible) {
    				// 当左侧或右侧布局显示时,将绑定控件的事件屏蔽掉
    				return true;
    			}
    			return false;
    		}
    		return true;
    	}
    
    	/**
    	 * 根据手指移动的距离,判断当前用户的滑动意图,然后给slideState赋值成相应的滑动状态值。
    	 * 
    	 * @param moveDistanceX
    	 *            横向移动的距离
    	 * @param moveDistanceY
    	 *            纵向移动的距离
    	 */
    	private void checkSlideState(int moveDistanceX, int moveDistanceY) {
    		if (isLeftMenuVisible) {
    			if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX < 0) {
    				isSliding = true;
    				slideState = HIDE_LEFT_MENU;
    			}
    		} else if (isRightMenuVisible) {
    			if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX > 0) {
    				isSliding = true;
    				slideState = HIDE_RIGHT_MENU;
    			}
    		} else {
    			if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX > 0
    					&& Math.abs(moveDistanceY) < touchSlop) {
    				isSliding = true;
    				slideState = SHOW_LEFT_MENU;
    				contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
    				contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
    				contentLayout.setLayoutParams(contentLayoutParams);
    				// 如果用户想要滑动左侧菜单,将左侧菜单显示,右侧菜单隐藏
    				leftMenuLayout.setVisibility(View.VISIBLE);
    				rightMenuLayout.setVisibility(View.GONE);
    			} else if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX < 0
    					&& Math.abs(moveDistanceY) < touchSlop) {
    				isSliding = true;
    				slideState = SHOW_RIGHT_MENU;
    				contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0);
    				contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
    				contentLayout.setLayoutParams(contentLayoutParams);
    				// 如果用户想要滑动右侧菜单,将右侧菜单显示,左侧菜单隐藏
    				rightMenuLayout.setVisibility(View.VISIBLE);
    				leftMenuLayout.setVisibility(View.GONE);
    			}
    		}
    	}
    
    	/**
    	 * 在滑动过程中检查左侧菜单的边界值,防止绑定布局滑出屏幕。
    	 */
    	private void checkLeftMenuBorder() {
    		if (contentLayoutParams.rightMargin > 0) {
    			contentLayoutParams.rightMargin = 0;
    		} else if (contentLayoutParams.rightMargin < -leftMenuLayoutParams.width) {
    			contentLayoutParams.rightMargin = -leftMenuLayoutParams.width;
    		}
    	}
    
    	/**
    	 * 在滑动过程中检查右侧菜单的边界值,防止绑定布局滑出屏幕。
    	 */
    	private void checkRightMenuBorder() {
    		if (contentLayoutParams.leftMargin > 0) {
    			contentLayoutParams.leftMargin = 0;
    		} else if (contentLayoutParams.leftMargin < -rightMenuLayoutParams.width) {
    			contentLayoutParams.leftMargin = -rightMenuLayoutParams.width;
    		}
    	}
    
    	/**
    	 * 判断是否应该滚动将左侧菜单展示出来。如果手指移动距离大于左侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
    	 * 就认为应该滚动将左侧菜单展示出来。
    	 * 
    	 * @return 如果应该将左侧菜单展示出来返回true,否则返回false。
    	 */
    	private boolean shouldScrollToLeftMenu() {
    		return xUp - xDown > leftMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    	}
    
    	/**
    	 * 判断是否应该滚动将右侧菜单展示出来。如果手指移动距离大于右侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
    	 * 就认为应该滚动将右侧菜单展示出来。
    	 * 
    	 * @return 如果应该将右侧菜单展示出来返回true,否则返回false。
    	 */
    	private boolean shouldScrollToRightMenu() {
    		return xDown - xUp > rightMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    	}
    
    	/**
    	 * 判断是否应该从左侧菜单滚动到内容布局,如果手指移动距离大于左侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
    	 * 就认为应该从左侧菜单滚动到内容布局。
    	 * 
    	 * @return 如果应该从左侧菜单滚动到内容布局返回true,否则返回false。
    	 */
    	private boolean shouldScrollToContentFromLeftMenu() {
    		return xDown - xUp > leftMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    	}
    
    	/**
    	 * 判断是否应该从右侧菜单滚动到内容布局,如果手指移动距离大于右侧菜单宽度的1/2,或者手指移动速度大于SNAP_VELOCITY,
    	 * 就认为应该从右侧菜单滚动到内容布局。
    	 * 
    	 * @return 如果应该从右侧菜单滚动到内容布局返回true,否则返回false。
    	 */
    	private boolean shouldScrollToContentFromRightMenu() {
    		return xUp - xDown > rightMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    	}
    
    	/**
    	 * 创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
    	 * 
    	 * @param event
    	 *            右侧布局监听控件的滑动事件
    	 */
    	private void createVelocityTracker(MotionEvent event) {
    		if (mVelocityTracker == null) {
    			mVelocityTracker = VelocityTracker.obtain();
    		}
    		mVelocityTracker.addMovement(event);
    	}
    
    	/**
    	 * 获取手指在绑定布局上的滑动速度。
    	 * 
    	 * @return 滑动速度,以每秒钟移动了多少像素值为单位。
    	 */
    	private int getScrollVelocity() {
    		mVelocityTracker.computeCurrentVelocity(1000);
    		int velocity = (int) mVelocityTracker.getXVelocity();
    		return Math.abs(velocity);
    	}
    
    	/**
    	 * 回收VelocityTracker对象。
    	 */
    	private void recycleVelocityTracker() {
    		mVelocityTracker.recycle();
    		mVelocityTracker = null;
    	}
    
    	/**
    	 * 使用可以获得焦点的控件在滑动的时候失去焦点。
    	 */
    	private void unFocusBindView() {
    		if (mBindView != null) {
    			mBindView.setPressed(false);
    			mBindView.setFocusable(false);
    			mBindView.setFocusableInTouchMode(false);
    		}
    	}
    
    	class LeftMenuScrollTask extends AsyncTask<Integer, Integer, Integer> {
    
    		@Override
    		protected Integer doInBackground(Integer... speed) {
    			int rightMargin = contentLayoutParams.rightMargin;
    			// 根据传入的速度来滚动界面,当滚动到达边界值时,跳出循环。
    			while (true) {
    				rightMargin = rightMargin + speed[0];
    				if (rightMargin < -leftMenuLayoutParams.width) {
    					rightMargin = -leftMenuLayoutParams.width;
    					break;
    				}
    				if (rightMargin > 0) {
    					rightMargin = 0;
    					break;
    				}
    				publishProgress(rightMargin);
    				// 为了要有滚动效果产生,每次循环使线程睡眠一段时间,这样肉眼才能够看到滚动动画。
    				sleep(15);
    			}
    			if (speed[0] > 0) {
    				isLeftMenuVisible = false;
    			} else {
    				isLeftMenuVisible = true;
    			}
    			isSliding = false;
    			return rightMargin;
    		}
    
    		@Override
    		protected void onProgressUpdate(Integer... rightMargin) {
    			contentLayoutParams.rightMargin = rightMargin[0];
    			contentLayout.setLayoutParams(contentLayoutParams);
    			unFocusBindView();
    		}
    
    		@Override
    		protected void onPostExecute(Integer rightMargin) {
    			contentLayoutParams.rightMargin = rightMargin;
    			contentLayout.setLayoutParams(contentLayoutParams);
    		}
    	}
    
    	class RightMenuScrollTask extends AsyncTask<Integer, Integer, Integer> {
    
    		@Override
    		protected Integer doInBackground(Integer... speed) {
    			int leftMargin = contentLayoutParams.leftMargin;
    			// 根据传入的速度来滚动界面,当滚动到达边界值时,跳出循环。
    			while (true) {
    				leftMargin = leftMargin + speed[0];
    				if (leftMargin < -rightMenuLayoutParams.width) {
    					leftMargin = -rightMenuLayoutParams.width;
    					break;
    				}
    				if (leftMargin > 0) {
    					leftMargin = 0;
    					break;
    				}
    				publishProgress(leftMargin);
    				// 为了要有滚动效果产生,每次循环使线程睡眠一段时间,这样肉眼才能够看到滚动动画。
    				sleep(15);
    			}
    			if (speed[0] > 0) {
    				isRightMenuVisible = false;
    			} else {
    				isRightMenuVisible = true;
    			}
    			isSliding = false;
    			return leftMargin;
    		}
    
    		@Override
    		protected void onProgressUpdate(Integer... leftMargin) {
    			contentLayoutParams.leftMargin = leftMargin[0];
    			contentLayout.setLayoutParams(contentLayoutParams);
    			unFocusBindView();
    		}
    
    		@Override
    		protected void onPostExecute(Integer leftMargin) {
    			contentLayoutParams.leftMargin = leftMargin;
    			contentLayout.setLayoutParams(contentLayoutParams);
    		}
    	}
    
    	/**
    	 * 使当前线程睡眠指定的毫秒数。
    	 * 
    	 * @param millis
    	 *            指定当前线程睡眠多久,以毫秒为单位
    	 */
    	private void sleep(long millis) {
    		try {
    			Thread.sleep(millis);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }

    以上代码注释已经写得非常详细,我再来简单解释一下。首先在onLayout()方法中分别获取到左侧菜单、右侧菜单和内容布局的参数,并将内容布局的宽度重定义成屏幕的宽度,这样就可以保证内容布局既能覆盖住下面的菜单布局,还能偏移出屏幕。然后在onTouch()方法中监听触屏事件,以判断用户手势的意图。这里事先定义好了几种滑动状态,DO_NOTHING表示没有进行任何滑动,SHOW_LEFT_MENU表示用户想要滑出左侧菜单,SHOW_RIGHT_MENU表示用户想要滑出右侧菜单,HIDE_LEFT_MENU表示用户想要隐藏左侧菜单,HIDE_RIGHT_MENU表示用户想要隐藏右侧菜单,在checkSlideState()方法中判断出用户到底是想进行哪一种滑动操作,并给slideState变量赋值,然后根据slideState的值决定如何偏移内容布局。接着当用户手指离开屏幕时,会根据当前的滑动距离,决定后续的滚动方向,通过LeftMenuScrollTask和RightMenuScrollTask来完成完整的滑动过程。另外在滑动的过程,内容布局上的事件会被屏蔽掉,主要是通过一系列的return操作实现的,对这一部分不理解的朋友,请参阅 Android事件分发机制完全解析,带你从源码的角度彻底理解 。


    然后我们看一下setScrollEvent方法,这个方法接收一个View作为参数,然后为这个View绑定了一个touch事件。这是什么意思呢?让我们来想象一个场景,如果内容布局是一个LinearLayout,我可以通过监听LinearLayout上的touch事件来控制它的偏移。但是如果内容布局的LinearLayout里面加入了一个ListView,而这个ListView又充满了整个LinearLayout,这个时候LinearLayout将不可能再被touch到了,这个时候我们就需要将touch事件注册到ListView上。setScrollEvent方法也就是提供了一个注册接口,touch事件将会注册到传入的View上。


    接下来打开或新建activity_main.xml文件,加入如下代码:

    <com.example.bidirslidinglayout.BidirSlidingLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/bidir_sliding_layout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    
        <RelativeLayout
            android:id="@+id/left_menu"
            android:layout_width="270dip"
            android:layout_height="fill_parent"
            android:layout_alignParentLeft="true"
            android:background="#00ccff"
            android:visibility="invisible" >
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="This is left menu"
                android:textColor="#000000"
                android:textSize="28sp" />
        </RelativeLayout>
    
        <RelativeLayout
            android:id="@+id/right_menu"
            android:layout_width="270dip"
            android:layout_height="fill_parent"
            android:layout_alignParentRight="true"
            android:background="#00ffcc"
            android:visibility="invisible" >
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="This is right menu"
                android:textColor="#000000"
                android:textSize="28sp" />
        </RelativeLayout>
    
        <LinearLayout
            android:id="@+id/content"
            android:layout_width="320dip"
            android:layout_height="fill_parent"
            android:background="#e9e9e9" >
    
            <ListView
                android:id="@+id/contentList"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:scrollbars="none"
                android:cacheColorHint="#00000000" >
            </ListView>
        </LinearLayout>
    
    </com.example.bidirslidinglayout.BidirSlidingLayout>

    可以看到,我们使用了自定义的BidirSlidingLayout作为根布局,然后依次加入了三个子布局分别作为左侧菜单、右侧菜单和内容的布局。左侧菜单和右侧菜单中都只是简单地放入了一个TextView用于显示一段文字,内容布局中放入了一个ListView。注意要让左侧菜单和父布局左边缘对齐,右侧菜单和父布局右边缘对齐。


    最后打开或者创建MainActivity作为程序的主Activity,代码如下所示:

    public class MainActivity extends Activity {
    
    	/**
    	 * 双向滑动菜单布局
    	 */
    	private BidirSlidingLayout bidirSldingLayout;
    
    	/**
    	 * 在内容布局上显示的ListView
    	 */
    	private ListView contentList;
    
    	/**
    	 * ListView的适配器
    	 */
    	private ArrayAdapter<String> contentListAdapter;
    
    	/**
    	 * 用于填充contentListAdapter的数据源。
    	 */
    	private String[] contentItems = { "Content Item 1", "Content Item 2", "Content Item 3",
    			"Content Item 4", "Content Item 5", "Content Item 6", "Content Item 7",
    			"Content Item 8", "Content Item 9", "Content Item 10", "Content Item 11",
    			"Content Item 12", "Content Item 13", "Content Item 14", "Content Item 15",
    			"Content Item 16" };
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		bidirSldingLayout = (BidirSlidingLayout) findViewById(R.id.bidir_sliding_layout);
    		contentList = (ListView) findViewById(R.id.contentList);
    		contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
    				contentItems);
    		contentList.setAdapter(contentListAdapter);
    		bidirSldingLayout.setScrollEvent(contentList);
    	}
    
    }

    这里我们给ListView填充了几条数据,又通过findViewById()方法获取到了BidirSlidingLayout对象,然后调用它的setScrollEvent()方法,将ListView进行绑定,这样就可以通过左右滑动ListView来展示左侧菜单和右侧菜单了。


    好了,全部编码工作都已完成,现在让我们运行一下程序吧,效果如下图所示:




    看起来还是挺不错的吧!并且更重要的是,以后我们在项目的任何地方都可以轻松加入双向滑动菜单功能,只需要以下两步即可:


    1. 在Acitivty的layout中引入我们自定义的BidirSlidingLayout布局,并且给这个布局要加入三个直接子元素。

    2. 在Activity中通过setScrollEvent方法,给一个View注册touch事件。


    如此一来,一分钟实现双向滑动菜单功能妥妥的。

    好了,今天的讲解到此结束,有疑问的朋友请在下面留言。


    源码下载,请点击这里

    带按钮的版本下载,请点击这里


    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

            

    展开全文
  • 响应式滑动菜单by Prashant Yadav 通过Prashant Yadav 如何创建响应式滑动菜单 (How to create a responsive sliding menu) I run a blog named learnersbucket.com where I write about ES6, Data structures, ...
  • 平台滑动菜单.zip

    2019-07-03 21:46:28
    平台滑动菜单是一款在标题打开左侧菜单中点击“菜单”按钮无穷的滑动菜单,点击好友按钮的标题,打开第二个菜单。
  • 滑动菜单 图文菜单 手风琴图文菜单大全代码,都是比较个性的菜单,都带有动画效果,有的像是图片特效,不像菜单,第1款菜单,点击图片旁边的文字会切换图片,每一条文字对应一幅图片。  第2款是带文字标签的滑动...
  • 滑动菜单unity插件

    2018-08-24 18:10:47
    滑动菜单unity插件资源,可以用,功能还不错,代码比较多
  • 主要介绍了JS+CSS实现简单滑动门(滑动菜单)效果,通过JavaScript响应鼠标事件动态设置页面元素样式实现滑动菜单功能,需要的朋友可以参考下
  • CSS动态滑动菜单.rar

    2019-07-04 00:15:47
    CSS动态滑动菜单
  • 幻影菜单,js滑动菜单,支持两级子菜单项,鼠标快速划过菜单主项,会看到二级菜单滑出效果。横排和竖排的菜单,菜单从这里开始, 注意要把class设置成和css里相同的, 还要为它设一个id,在上面这个table结束的地方...
  • Android顶部滑动菜单

    2017-09-14 14:24:01
    类似今日头条的顶部的滑动菜单,这边是图片滑动,原理一样,适合初学者,亲测可用,代码简单,清晰,很实用。
  • iOS 滑动菜单 NewsDemo ,NewsDemo 是模仿网易新闻客户端首页,实现滑动选择菜单。
  • qq滑动菜单

    热门讨论 2012-07-13 14:16:28
    qq滑动菜单 手势翻页,动画切屏,下划线联动,相当美观实用一款特效
  • 本篇文章分享了基于jQuery实现背景滑动菜单的实例代码。感兴趣的朋友可以下载实例运行,下面就跟小编一起来看看吧
  • 滑动菜单 SlideNavigationController ,SlideNavigationController 是一个 iOS 路径或者是像 Facebook ...

空空如也

1 2 3 4 5 ... 20
收藏数 5,620
精华内容 2,248
关键字:

滑动菜单