Android -- SlidingMenu
實現原理 在一個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事件裡面,根據手指滑動的距離會改變菜單布局的左位移量,從而控制功能表布局的顯示和隱藏。當手指離開螢幕的時候,會判斷應該滑動到菜單布局還是內容布局,判斷依據是根據手指滑動的距離或者滑動的速度,細節可以看代碼中的注釋。