Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android系統教程 >> Android開發教程 >> Android雙向滑動菜單完全解析,教你如何一分鐘實現雙向滑動特效

Android雙向滑動菜單完全解析,教你如何一分鐘實現雙向滑動特效

編輯:Android開發教程

記得在很早之前,我寫了一篇關於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();  
        }  
    }  
}

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved