实现原理
在一个Activity的布局中需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局。两个布局横向排列,菜单布局在左,内容布局在右。初始化的时候将菜单布局向左偏移,以至于能够完全隐藏,这样内容布局就会完全显示在Activity中。然后通过监听手指滑动事件,来改变菜单布局的左偏移距离,从而控制菜单布局的显示和隐藏。
布局
<LinearLayout 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" android:orientation="horizontal" tools:context=".MyActivity"> <LinearLayout android:id="@+id/menu" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ff00ff00" android:orientation="vertical"></LinearLayout> <LinearLayout android:id="@+id/content" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ffff0000" android:orientation="vertical"></LinearLayout> </LinearLayout>
这个布局文件的最外层布局是一个LinearLayout,排列方向是水平方向排列。这个LinearLayout下面嵌套了两个子LinearLayout,分别就是菜单的布局和内容的布局。这里为了要让布局尽量简单,菜单布局和内容布局里面没有加入任何控件。
Code
public class MyActivity extends Activity implements View.OnTouchListener { /** * 手指每单位时间滑动200个像素 */ private static final int SPEED = 200; /** * 屏幕宽度 */ private int mScreenWidth; /** * menu的layout */ private LinearLayout mMenuLayout; /** * content的layout */ private LinearLayout mContentLayout; /** * menu的layout的Paramters */ private LinearLayout.LayoutParams mMenuParams; /** * menu完全显示的时候给content的宽度值 */ private int mMenuPadding = 80; /** * menu最多滑到左边缘,值由menu布局的宽度决定,marginLeft到达此值之后,不能在减少 */ private int mLeftEdge; /** * 测速度的对象 */ private VelocityTracker mVelocityTracker; /** * 手指按下的X坐标 */ private float mXDown; /** * 手指移动时候的X坐标 */ private float mXMove; /** * 手指抬起的X坐标 */ private float mXUp; /** * menu是否再显示 */ private boolean mIsMenuVisible = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); initViewsAndValues(); mContentLayout.setOnTouchListener(this); } /** * 初始化menu和content并且设置他们的位置 */ private void initViewsAndValues() { //得到windowManager WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE); //得到屏幕宽度 mScreenWidth = window.getDefaultDisplay().getWidth(); //找到控件 mMenuLayout = (LinearLayout) findViewById(R.id.menu); mContentLayout = (LinearLayout) findViewById(R.id.content); //得到menu的paramter mMenuParams = (LinearLayout.LayoutParams) mMenuLayout.getLayoutParams(); //将menu的宽度设置为屏幕宽度减去mMenuPading mMenuParams.width = mScreenWidth - mMenuPadding; //左边缘的值复制为menu宽度的负数,这样的话就可以将menu隐藏 mLeftEdge = -mMenuParams.width; //将margin设置为mLeftEdge mMenuParams.leftMargin = mLeftEdge; //将content显示再屏幕上 mContentLayout.getLayoutParams().width = mScreenWidth; } @Override public boolean onTouch(View view, MotionEvent event) { createVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //记录X坐标 mXDown = event.getRawX(); break; case MotionEvent.ACTION_MOVE: mXMove = event.getRawX(); int distanceX = (int) (mXMove - mXDown); if (mIsMenuVisible) { mMenuParams.leftMargin = distanceX; } else { mMenuParams.leftMargin = mLeftEdge + distanceX; } if (mMenuParams.leftMargin < mLeftEdge) { //因为leftEdge是负数,就是menu已经隐藏完毕了,不能再往左隐藏了 mMenuParams.leftMargin = mLeftEdge; } else if (mMenuParams.leftMargin > 0) { //menu显示完全了,不能再往右移动了 mMenuParams.leftMargin = 0; } mMenuLayout.setLayoutParams(mMenuParams); break; case MotionEvent.ACTION_UP: mXUp = event.getRawX(); if (wantToShowMenu()) { if (shouldScrollToMenu()) { scrollToMenu(); } else { //条件不满足,把menu继续隐藏掉 scrollToContent(); } } else if (wantToShowContent()) { if (shouldScrollToContent()) { scrollToContent(); } else { scrollToMenu(); } } break; } recycleVelocityTracker(); return true; } /** * 创建VelocityTracker对象,针对于content的界面的滑动事件 * * @param event */ private void createVelocityTracker(MotionEvent event) { if (null == mVelocityTracker) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } /** * 判断手势是不是想要显示Content && menu处于显示状态 * * @return */ private boolean wantToShowContent() { return mXUp - mXDown < 0 && mIsMenuVisible; } /** * 是不是要显示menu && menu处于隐藏状态 * * @return */ private boolean wantToShowMenu() { return mXUp - mXDown > 0 && !mIsMenuVisible; } /** * 是否应该滑动出menu * * @return */ private boolean shouldScrollToMenu() { return mXUp - mXDown > mScreenWidth / 2 || getScrollVelocity() > SPEED; } /** * 是否应该让content全部显示出来 * * @return */ private boolean shouldScrollToContent() { return mXDown - mXUp < mScreenWidth / 2 || getScrollVelocity() > SPEED; } /** * 显示出menu */ private void scrollToMenu() { new ScrollAsyncTask().execute(30); } /** * 隐藏掉menu */ private void scrollToContent() { new ScrollAsyncTask().execute(-30); } /** * 得到手指滑动速度,每秒移动多少单位像素 * * @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 ScrollAsyncTask extends AsyncTask<Integer, Integer, Integer> { @Override protected Integer doInBackground(Integer[] speed) { //得到当前margin int leftMargin = mMenuParams.leftMargin; //不断更改margin的值 while (true) { leftMargin = leftMargin + speed[0]; if (leftMargin > 0) { leftMargin = 0; break; } if (leftMargin < mLeftEdge) { leftMargin = mLeftEdge; break; } publishProgress(leftMargin); sleep(); } if (speed[0] > 0) { mIsMenuVisible = true; } else { mIsMenuVisible = false; } return leftMargin; } @Override protected void onPostExecute(Integer integer) { mMenuParams.leftMargin = integer; mMenuLayout.setLayoutParams(mMenuParams); } @Override protected void onProgressUpdate(Integer... values) { mMenuParams.leftMargin = values[0]; mMenuLayout.setLayoutParams(mMenuParams); } } /** * sleep线程睡眠一下 */ private void sleep() { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }
首先初始化的时候调用initViewsAndValues方法,在这里面将内容布局的宽度设定为屏幕的宽度,菜单布局的宽度设定为屏幕的宽度减去menuPadding值,这样可以保证在菜单布局展示的时候,仍有一部分内容布局可以看到。如果不在初始化的时候重定义两个布局宽度,就会按照layout文件里面声明的一样,两个布局都是fill_parent,这样就无法实现滑动菜单的效果了。然后将菜单布局的左偏移量设置为负的菜单布局的宽度,这样菜单布局就会被完全隐藏,只有内容布局会显示在界面上。
之后给内容布局注册监听事件,这样当手指在内容布局上滑动的时候就会触发onTouch事件。在onTouch事件里面,根据手指滑动的距离会改变菜单布局的左偏移量,从而控制菜单布局的显示和隐藏。当手指离开屏幕的时候,会判断应该滑动到菜单布局还是内容布局,判断依据是根据手指滑动的距离或者滑动的速度,细节可以看代码中的注释。