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