Android滑動菜單特效實現,側滑效果,史上最簡單的側滑實現

轉自http://blog.csdn.net/guolin_blog/article/details/8714621

人人客戶端有一個特效還是挺吸引人的,在主界面手指向右滑動,就可以將菜單展示出來,而主界面會被隱藏大部分,但是仍有左側的一小部分同菜單一起展示。

據說人人客戶端的這個特效是從facebook客戶端模仿來的,至於facebook是不是又從其它地方模仿來的就不得而知了。好,今天我們就一起來實現這個效果,總之我第一次看到這個特效是在人人客戶端看到的,我也就主觀性地認爲我是在模仿人人客戶端的特效了。

雖然現在網上類似這種效果的實現也非常多,可是我發現實現方案大都非常複雜,並不容易理解。但其實這種效果並不難實現,因此我今天給大家帶來的也是史上最簡單的滑動菜單實現方案。

首先還是講一下實現原理。在一個Activity的佈局中需要有兩部分,一個是菜單(menu)的佈局,一個是內容(content)的佈局。兩個佈局橫向排列,菜單佈局在左,內容佈局在右。初始化的時候將菜單佈局向左偏移,以至於能夠完全隱藏,這樣內容佈局就會完全顯示在Activity中。然後通過監聽手指滑動事件,來改變菜單佈局的左偏移距離,從而控制菜單佈局的顯示和隱藏。原理圖如下:


將菜單佈局的左偏移值改成0時,效果圖如下:


好,我們開始用代碼來實現。首先在Eclipse中新建一個Android項目,項目名就叫做RenRenSlideMenuDemo。然後寫一下佈局文件,創建或打開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" >  
  
    <LinearLayout  
        android:id="@+id/menu"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:background="@drawable/menu" >  
    </LinearLayout>  
  
    <LinearLayout  
        android:id="@+id/content"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:background="@drawable/content" >  
    </LinearLayout>  
  
</LinearLayout>  
這個佈局文件的最外層佈局是一個LinearLayout,排列方向是水平方向排列。這個LinearLayout下面嵌套了兩個子LinearLayout,分別就是菜單的佈局和內容的佈局。這裏爲了要讓佈局儘量簡單,菜單佈局和內容佈局裏面沒有加入任何控件,只是給這兩個佈局各添加了一張背景圖片,這兩張背景圖片是我從人人客戶端上截下來的圖。這樣我們可以把注意力都集中在如何實現滑動菜單的效果上面,不用關心裏面各種複雜的佈局了。

創建或打開MainActivity,這個類仍然是程序的主Activity,也是這次demo唯一的Activity,在裏面加入如下代碼:

public class MainActivity extends Activity implements OnTouchListener {  
  
    /** 
     * 滾動顯示和隱藏menu時,手指滑動需要達到的速度。 
     */  
    public static final int SNAP_VELOCITY = 200;  
  
    /** 
     * 屏幕寬度值。 
     */  
    private int screenWidth;  
  
    /** 
     * menu最多可以滑動到的左邊緣。值由menu佈局的寬度來定,marginLeft到達此值之後,不能再減少。 
     */  
    private int leftEdge;  
  
    /** 
     * menu最多可以滑動到的右邊緣。值恆爲0,即marginLeft到達0之後,不能增加。 
     */  
    private int rightEdge = 0;  
  
    /** 
     * menu完全顯示時,留給content的寬度值。 
     */  
    private int menuPadding = 80;  
  
    /** 
     * 主內容的佈局。 
     */  
    private View content;  
  
    /** 
     * menu的佈局。 
     */  
    private View menu;  
  
    /** 
     * menu佈局的參數,通過此參數來更改leftMargin的值。 
     */  
    private LinearLayout.LayoutParams menuParams;  
  
    /** 
     * 記錄手指按下時的橫座標。 
     */  
    private float xDown;  
  
    /** 
     * 記錄手指移動時的橫座標。 
     */  
    private float xMove;  
  
    /** 
     * 記錄手機擡起時的橫座標。 
     */  
    private float xUp;  
  
    /** 
     * menu當前是顯示還是隱藏。只有完全顯示或隱藏menu時纔會更改此值,滑動過程中此值無效。 
     */  
    private boolean isMenuVisible;  
  
    /** 
     * 用於計算手指滑動的速度。 
     */  
    private VelocityTracker mVelocityTracker;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        initValues();  
        content.setOnTouchListener(this);  
    }  
  
    /** 
     * 初始化一些關鍵性數據。包括獲取屏幕的寬度,給content佈局重新設置寬度,給menu佈局重新設置寬度和偏移距離等。 
     */  
    private void initValues() {  
        WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);  
        screenWidth = window.getDefaultDisplay().getWidth();  
        content = findViewById(R.id.content);  
        menu = findViewById(R.id.menu);  
        menuParams = (LinearLayout.LayoutParams) menu.getLayoutParams();  
        // 將menu的寬度設置爲屏幕寬度減去menuPadding  
        menuParams.width = screenWidth - menuPadding;  
        // 左邊緣的值賦值爲menu寬度的負數  
        leftEdge = -menuParams.width;  
        // menu的leftMargin設置爲左邊緣的值,這樣初始化時menu就變爲不可見  
        menuParams.leftMargin = leftEdge;  
        // 將content的寬度設置爲屏幕寬度  
        content.getLayoutParams().width = screenWidth;  
    }  
  
    @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:  
            // 手指移動時,對比按下時的橫座標,計算出移動的距離,來調整menu的leftMargin值,從而顯示和隱藏menu  
            xMove = event.getRawX();  
            int distanceX = (int) (xMove - xDown);  
            if (isMenuVisible) {  
                menuParams.leftMargin = distanceX;  
            } else {  
                menuParams.leftMargin = leftEdge + distanceX;  
            }  
            if (menuParams.leftMargin < leftEdge) {  
                menuParams.leftMargin = leftEdge;  
            } else if (menuParams.leftMargin > rightEdge) {  
                menuParams.leftMargin = rightEdge;  
            }  
            menu.setLayoutParams(menuParams);  
            break;  
        case MotionEvent.ACTION_UP:  
            // 手指擡起時,進行判斷當前手勢的意圖,從而決定是滾動到menu界面,還是滾動到content界面  
            xUp = event.getRawX();  
            if (wantToShowMenu()) {  
                if (shouldScrollToMenu()) {  
                    scrollToMenu();  
                } else {  
                    scrollToContent();  
                }  
            } else if (wantToShowContent()) {  
                if (shouldScrollToContent()) {  
                    scrollToContent();  
                } else {  
                    scrollToMenu();  
                }  
            }  
            recycleVelocityTracker();  
            break;  
        }  
        return true;  
    }  
  
    /** 
     * 判斷當前手勢的意圖是不是想顯示content。如果手指移動的距離是負數,且當前menu是可見的,則認爲當前手勢是想要顯示content。 
     *  
     * @return 當前手勢想顯示content返回true,否則返回false。 
     */  
    private boolean wantToShowContent() {  
        return xUp - xDown < 0 && isMenuVisible;  
    }  
  
    /** 
     * 判斷當前手勢的意圖是不是想顯示menu。如果手指移動的距離是正數,且當前menu是不可見的,則認爲當前手勢是想要顯示menu。 
     *  
     * @return 當前手勢想顯示menu返回true,否則返回false。 
     */  
    private boolean wantToShowMenu() {  
        return xUp - xDown > 0 && !isMenuVisible;  
    }  
  
    /** 
     * 判斷是否應該滾動將menu展示出來。如果手指移動距離大於屏幕的1/2,或者手指移動速度大於SNAP_VELOCITY, 
     * 就認爲應該滾動將menu展示出來。 
     *  
     * @return 如果應該滾動將menu展示出來返回true,否則返回false。 
     */  
    private boolean shouldScrollToMenu() {  
        return xUp - xDown > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;  
    }  
  
    /** 
     * 判斷是否應該滾動將content展示出來。如果手指移動距離加上menuPadding大於屏幕的1/2, 
     * 或者手指移動速度大於SNAP_VELOCITY, 就認爲應該滾動將content展示出來。 
     *  
     * @return 如果應該滾動將content展示出來返回true,否則返回false。 
     */  
    private boolean shouldScrollToContent() {  
        return xDown - xUp + menuPadding > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;  
    }  
  
    /** 
     * 將屏幕滾動到menu界面,滾動速度設定爲30. 
     */  
    private void scrollToMenu() {  
        new ScrollTask().execute(30);  
    }  
  
    /** 
     * 將屏幕滾動到content界面,滾動速度設定爲-30. 
     */  
    private void scrollToContent() {  
        new ScrollTask().execute(-30);  
    }  
  
    /** 
     * 創建VelocityTracker對象,並將觸摸content界面的滑動事件加入到VelocityTracker當中。 
     *  
     * @param event 
     *            content界面的滑動事件 
     */  
    private void createVelocityTracker(MotionEvent event) {  
        if (mVelocityTracker == null) {  
            mVelocityTracker = VelocityTracker.obtain();  
        }  
        mVelocityTracker.addMovement(event);  
    }  
  
    /** 
     * 獲取手指在content界面滑動的速度。 
     *  
     * @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 = menuParams.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) {  
                isMenuVisible = true;  
            } else {  
                isMenuVisible = false;  
            }  
            return leftMargin;  
        }  
  
        @Override  
        protected void onProgressUpdate(Integer... leftMargin) {  
            menuParams.leftMargin = leftMargin[0];  
            menu.setLayoutParams(menuParams);  
        }  
  
        @Override  
        protected void onPostExecute(Integer leftMargin) {  
            menuParams.leftMargin = leftMargin;  
            menu.setLayoutParams(menuParams);  
        }  
    }  
  
    /** 
     * 使當前線程睡眠指定的毫秒數。 
     *  
     * @param millis 
     *            指定當前線程睡眠多久,以毫秒爲單位 
     */  
    private void sleep(long millis) {  
        try {  
            Thread.sleep(millis);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  

全部的代碼都在這裏了,我們可以看到,加上註釋總共才兩百多行的代碼就能實現滑動菜單的特效。下面我來對以上代碼解釋一下,首先初始化的時候調用initValues方法,在這裏面將內容佈局的寬度設定爲屏幕的寬度,菜單佈局的寬度設定爲屏幕的寬度減去menuPadding值,這樣可以保證在菜單佈局展示的時候,仍有一部分內容佈局可以看到。如果不在初始化的時候重定義兩個佈局寬度,就會按照layout文件裏面聲明的一樣,兩個佈局都是fill_parent,這樣就無法實現滑動菜單的效果了。然後將菜單佈局的左偏移量設置爲負的菜單佈局的寬度,這樣菜單佈局就會被完全隱藏,只有內容佈局會顯示在界面上。

之後給內容佈局註冊監聽事件,這樣當手指在內容佈局上滑動的時候就會觸發onTouch事件。在onTouch事件裏面,根據手指滑動的距離會改變菜單佈局的左偏移量,從而控制菜單佈局的顯示和隱藏。當手指離開屏幕的時候,會判斷應該滑動到菜單佈局還是內容佈局,判斷依據是根據手指滑動的距離或者滑動的速度,細節可以看代碼中的註釋。

最後還是給出AndroidManifest.xml的代碼,都是自動生成的,非常簡單:

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="com.example.renrenslidemenudemo"  
    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.renrenslidemenudemo.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>  
好了,現在我們運行一下,看一下效果吧,首先是程序剛打開的時候,顯示的是內容佈局。用手指在界面向右滑動,可以看到菜單佈局出現。

而當菜單佈局完全展示的時候,效果如下圖:


源碼下載,請點擊這裏

今天大家看到了史上最簡單的滑動菜單實現方案,確實是非常簡單。那麼有朋友也許會問了,在一個Activity當中這樣實現滑動菜單是很簡單,可是如果我的應用程序有好多個Activity都需要滑動菜單,每個Activity裏都這麼實現一遍,也變得複雜了。沒錯,當前的這個解決方案只適用於單個Activity中,如果是想在多個Activity中都實現滑動菜單的效果,請參考我的另一篇文章 Android滑動菜單框架完全解析,教你如何一分鐘實現滑動菜單特效

有對雙向滑動菜單感興趣的朋友請轉閱  Android雙向滑動菜單完全解析,教你如何一分鐘實現雙向滑動特效

好了,今天的講解到此結束,有疑問的朋友可以在下面留言。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章