android 滑動菜單SlidingMenu的實現

首先我們看下面視圖:
     
這種效果大傢都不陌生,網上好多都說是仿人人網的,估計人傢牛逼出來的早吧,我也參考瞭一一些例子,實現起來有三種方法,我下面簡單介紹下:
方法一:其實就是對GestureDetector手勢的應用及佈局文件的設計.
佈局文件main.xml    采用RelativeLayout佈局.
[java] 
<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:orientation="vertical" > 
 
    <LinearLayout 
        android:id="@+id/layout_right" 
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
        android:layout_marginLeft="50dp" 
        android:orientation="vertical" > 
 
        <AbsoluteLayout 
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:background="@color/grey21" 
            android:padding="10dp" > 
 
            <TextView 
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:text="設置" 
                android:textColor="@android:color/background_light" 
                android:textSize="20sp" /> 
        </AbsoluteLayout> 
 
        <ListView 
            android:id="@+id/lv_set" 
            android:layout_width="fill_parent" 
            android:layout_height="fill_parent" 
            android:layout_weight="1" > 
        </ListView> 
    </LinearLayout> 
 
    <LinearLayout 
        android:id="@+id/layout_left" 
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
        android:background="@color/white" 
        android:orientation="vertical" > 
 
        <RelativeLayout 
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:background="@drawable/nav_bg" > 
 
            <ImageView 
                android:id="@+id/iv_set" 
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:layout_alignParentRight="true" 
                android:layout_alignParentTop="true" 
                android:src="@drawable/nav_setting" /> 
 
            <TextView 
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content" 
                android:layout_centerInParent="true" 
                android:text="我" 
                android:textColor="@android:color/background_light" 
                android:textSize="20sp" /> 
        </RelativeLayout> 
 
        <ImageView 
            android:id="@+id/iv_set" 
            android:layout_width="fill_parent" 
            android:layout_height="fill_parent" 
            android:scaleType="fitXY" 
            android:src="@drawable/bg_guide_5" /> 
    </LinearLayout> 
 
</RelativeLayout> 

layout_right:這個大佈局文件,layout_left:距離左邊50dp像素.(我們要移動的是layout_left).
看到這個圖我想大傢都很清晰瞭吧,其實:我們就是把layout_left這個佈局控件整理向左移動,至於移動多少,就要看layout_right有多寬瞭。layout_left移動到距離左邊的邊距就是layout_right的寬及-MAX_WIDTH.相信大傢都理解.
佈局文件就介紹到這裡,下面看代碼.
[java]
/***
     * 初始化view
     */ 
    void InitView() { 
        layout_left = (LinearLayout) findViewById(R.id.layout_left); 
        layout_right = (LinearLayout) findViewById(R.id.layout_right); 
        iv_set = (ImageView) findViewById(R.id.iv_set); 
        lv_set = (ListView) findViewById(R.id.lv_set); 
        lv_set.setAdapter(new ArrayAdapter<String>(this, R.layout.item, 
                R.id.tv_item, title)); 
        lv_set.setOnItemClickListener(new OnItemClickListener() { 
 
            @Override 
            public void onItemClick(AdapterView<?> parent, View view, 
                    int position, long id) { 
                Toast.makeText(MainActivity.this, title[position], 1).show(); 
            } 
        }); 
        layout_left.setOnTouchListener(this); 
        iv_set.setOnTouchListener(this); 
        mGestureDetector = new GestureDetector(this); 
        // 禁用長按監聽 
        mGestureDetector.setIsLongpressEnabled(false); 
        getMAX_WIDTH(); 
    } 
這裡要對手勢進行監聽,我想大傢都知道怎麼做,在這裡我要說明一個方法:
[java]
/***
     * 獲取移動距離 移動的距離其實就是layout_left的寬度
     */ 
    void getMAX_WIDTH() { 
        ViewTreeObserver viewTreeObserver = layout_left.getViewTreeObserver(); 
        // 獲取控件寬度 
        viewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() { 
            @Override 
            public boolean onPreDraw() { 
                if (!hasMeasured) { 
                    window_width = getWindowManager().getDefaultDisplay() 
                            .getWidth(); 
                    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left 
                            .getLayoutParams(); 
                    layoutParams.width = window_width; 
                    layout_left.setLayoutParams(layoutParams); 
                    MAX_WIDTH = layout_right.getWidth(); 
                    Log.v(TAG, "MAX_WIDTH=" + MAX_WIDTH + "width=" 
                            + window_width); 
                    hasMeasured = true; 
                } 
                return true; 
            } 
        }); 
 
    } 
在這裡我們要獲取屏幕的寬度,並將屏幕寬度設置給layout_left這個控件,為什麼要這麼做呢,因為如果不把該控件寬度寫死的話,那麼系統將認為layout_left會根據不同環境寬度自動適應,也就是說我們通過layout_left.getLayoutParams動態移動該控件的時候,該控件會伸縮而不是移動。描述的有點模糊,大傢請看下面示意圖就明白瞭.
我們不為layout_left定義死寬度效果:
         
getLayoutParams可以很清楚看到,layout_left被向左拉伸瞭,並不是我們要的效果.
還有一種解決辦法就是我們在配置文件中直接把layout_left寬度寫死,不過這樣不利於開發,因為分辨率的問題.因此就用ViewTreeObserver進行對layout_left設置寬度.
ViewTreeObserver,這個類主要用於對佈局文件的監聽.強烈建議同學們參考這篇文章 android ViewTreeObserver詳細講解,相信讓你對ViewTreeObserver有更一步的瞭解.
其他的就是對GestureDetector手勢的應用,下面我把代碼貼出來:
[java] 
package com.jj.slidingmenu; 
 
import android.app.Activity; 
import android.os.AsyncTask; 
import android.os.Bundle; 
import android.util.Log; 
import android.view.GestureDetector; 
import android.view.KeyEvent; 
import android.view.MotionEvent; 
import android.view.View; 
import android.view.ViewTreeObserver; 
import android.view.ViewTreeObserver.OnPreDrawListener; 
import android.view.Window; 
import android.view.View.OnTouchListener; 
import android.widget.AdapterView; 
import android.widget.AdapterView.OnItemClickListener; 
import android.widget.ArrayAdapter; 
import android.widget.ImageView; 
import android.widget.LinearLayout; 
import android.widget.ListView; 
import android.widget.RelativeLayout; 
import android.widget.Toast; 
import android.widget.LinearLayout.LayoutParams; 
 
/***
 * 滑動菜單
 * 
 * @author jjhappyforever…
 * 
 */ 
public class MainActivity extends Activity implements OnTouchListener, 
        GestureDetector.OnGestureListener { 
    private boolean hasMeasured = false;// 是否Measured. 
    private LinearLayout layout_left; 
    private LinearLayout layout_right; 
    private ImageView iv_set; 
    private ListView lv_set; 
 
    /** 每次自動展開/收縮的范圍 */ 
    private int MAX_WIDTH = 0; 
    /** 每次自動展開/收縮的速度 */ 
    private final static int SPEED = 30; 
 
    private GestureDetector mGestureDetector;// 手勢 
    private boolean isScrolling = false; 
    private float mScrollX; // 滑塊滑動距離 
    private int window_width;// 屏幕的寬度 
 
    private String TAG = "jj"; 
 
    private String title[] = { "待發送隊列", "同步分享設置", "編輯我的資料", "找朋友", "告訴朋友", 
            "節省流量", "推送設置", "版本更新", "意見反饋", "積分兌換", "精品應用", "常見問題", "退出當前帳號" }; 
 
    /***
     * 初始化view
     */ 
    void InitView() { 
        layout_left = (LinearLayout) findViewById(R.id.layout_left); 
        layout_right = (LinearLayout) findViewById(R.id.layout_right); 
        iv_set = (ImageView) findViewById(R.id.iv_set); 
        lv_set = (ListView) findViewById(R.id.lv_set); 
        lv_set.setAdapter(new ArrayAdapter<String>(this, R.layout.item, 
                R.id.tv_item, title)); 
        lv_set.setOnItemClickListener(new OnItemClickListener() { 
 
            @Override 
            public void onItemClick(AdapterView<?> parent, View view, 
                    int position, long id) { 
                Toast.makeText(MainActivity.this, title[position], 1).show(); 
            } 
        }); 
        layout_left.setOnTouchListener(this); 
        iv_set.setOnTouchListener(this); 
        mGestureDetector = new GestureDetector(this); 
        // 禁用長按監聽 
        mGestureDetector.setIsLongpressEnabled(false); 
        getMAX_WIDTH(); 
    } 
 
    /***
     * 獲取移動距離 移動的距離其實就是layout_left的寬度
     */ 
    void getMAX_WIDTH() { 
        ViewTreeObserver viewTreeObserver = layout_left.getViewTreeObserver(); 
        // 獲取控件寬度 
        viewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() { 
            @Override 
            public boolean onPreDraw() { 
                if (!hasMeasured) { 
                    window_width = getWindowManager().getDefaultDisplay() 
                            .getWidth(); 
                    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left 
                            .getLayoutParams(); 
                    // layoutParams.width = window_width; 
                    layout_left.setLayoutParams(layoutParams); 
                    MAX_WIDTH = layout_right.getWidth(); 
                    Log.v(TAG, "MAX_WIDTH=" + MAX_WIDTH + "width=" 
                            + window_width); 
                    hasMeasured = true; 
                } 
                return true; 
            } 
        }); 
 
    } 
 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        requestWindowFeature(Window.FEATURE_NO_TITLE); 
        setContentView(R.layout.main); 
        InitView(); 
 
    } 
 
    // 返回鍵 
    @Override 
    public boolean onKeyDown(int keyCode, KeyEvent event) { 
        if (KeyEvent.KEYCODE_BACK == keyCode && event.getRepeatCount() == 0) { 
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left 
                    .getLayoutParams(); 
            if (layoutParams.leftMargin < 0) { 
                new AsynMove().execute(SPEED); 
                return false; 
            } 
        } 
 
        return super.onKeyDown(keyCode, event); 
    } 
 
    @Override 
    public boolean onTouch(View v, MotionEvent event) { 
        // 松開的時候要判斷,如果不到半屏幕位子則縮回去, 
        if (MotionEvent.ACTION_UP == event.getAction() && isScrolling == true) { 
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left 
                    .getLayoutParams(); 
            // 縮回去 
            if (layoutParams.leftMargin < -window_width / 2) { 
                new AsynMove().execute(-SPEED); 
            } else { 
                new AsynMove().execute(SPEED); 
            } 
        } 
 
        return mGestureDetector.onTouchEvent(event); 
    } 
 
    @Override 
    public boolean onDown(MotionEvent e) { 
        mScrollX = 0; 
        isScrolling = false; 
        // 將之改為true,不然事件不會向下傳遞. 
        return true; 
    } 
 
    @Override 
    public void onShowPress(MotionEvent e) { 
 
    } 
 
    /***
     * 點擊松開執行
     */ 
    @Override 
    public boolean onSingleTapUp(MotionEvent e) { 
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left 
                .getLayoutParams(); 
        // 左移動 
        if (layoutParams.leftMargin >= 0) { 
            new AsynMove().execute(-SPEED); 
        } else { 
            // 右移動 
            new AsynMove().execute(SPEED); 
        } 
 
        return true; 
    } 
 
    /***
     * e1 是起點,e2是終點,如果distanceX=e1.x-e2.x>0說明向左滑動。反之亦如此.
     */ 
    @Override 
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, 
            float distanceY) { 
        isScrolling = true; 
        mScrollX += distanceX;// distanceX:向左為正,右為負 
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left 
                .getLayoutParams(); 
        layoutParams.leftMargin -= mScrollX; 
        if (layoutParams.leftMargin >= 0) { 
            isScrolling = false;// 拖過頭瞭不需要再執行AsynMove瞭 
            layoutParams.leftMargin = 0; 
 
        } else if (layoutParams.leftMargin <= -MAX_WIDTH) { 
            // 拖過頭瞭不需要再執行AsynMove瞭 
            isScrolling = false; 
            layoutParams.leftMargin = -MAX_WIDTH; 
        } 
        layout_left.setLayoutParams(layoutParams); 
        return false; 
    } 
 
    @Override 
    public void onLongPress(MotionEvent e) { 
 
    } 
 
    @Override 
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 
            float velocityY) { 
        return false; 
    } 
 
    class AsynMove extends AsyncTask<Integer, Integer, Void> { 
 
        @Override 
        protected Void doInBackground(Integer… params) { 
            int times = 0; 
            if (MAX_WIDTH % Math.abs(params[0]) == 0)// 整除 
                times = MAX_WIDTH / Math.abs(params[0]); 
            else 
                times = MAX_WIDTH / Math.abs(params[0]) + 1;// 有餘數 
 
            for (int i = 0; i < times; i++) { 
                publishProgress(params[0]); 
                try { 
                    Thread.sleep(Math.abs(params[0])); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                } 
            } 
 
            return null; 
        } 
 
        /**
         * update UI
         */ 
        @Override 
        protected void onProgressUpdate(Integer… values) { 
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) layout_left 
                    .getLayoutParams(); 
            // 右移動 
            if (values[0] > 0) { 
                layoutParams.leftMargin = Math.min(layoutParams.leftMargin 
                        + values[0], 0); 
                Log.v(TAG, "移動右" + layoutParams.rightMargin); 
            } else { 
                // 左移動 
                layoutParams.leftMargin = Math.max(layoutParams.leftMargin 
                        + values[0], -MAX_WIDTH); 
                Log.v(TAG, "移動左" + layoutParams.rightMargin); 
            } 
            layout_left.setLayoutParams(layoutParams); 
 
        } 
 
    } 
 

上面代碼註釋已經很明確,相信大傢都看的明白,我就不過多解釋瞭。
效果圖:截屏出來有點卡,不過在手機虛擬機上是不卡的.


 

怎麼樣,看著還行吧,我們在看下面一個示例:


簡單說明一下,當你滑動的時候左邊會跟著右邊一起滑動,這個效果比上面那個酷吧,上面那個有點死板,其實實現起來也比較容易,隻需要把我們上面那個稍微修改下,對layout_right也進行時時更新,這樣就實現瞭這個效果瞭,如果上面那個理解瞭,這個很輕松就解決瞭,在這裡我又遇到一個問題:此時的listview的item監聽不到手勢,意思就是我左右滑動listview他沒有進行滑動。
本人對touch眾多事件監聽攔截等熟悉度不夠,因此這裡我用到自己寫的方法,也許比較麻煩,如果有更好的解決辦法,請大傢一定要分享哦,再次 thanks for you 瞭.
具體解決辦法:我們重寫listview,對此listview進行手勢監聽,我們自定義一個接口來實現,具體代碼如下:
[java]
package com.jj.slidingmenu; 
 
import android.content.Context; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.GestureDetector; 
import android.view.MotionEvent; 
import android.view.GestureDetector.OnGestureListener; 
import android.view.View; 
import android.widget.ListView; 
import android.widget.Toast; 
 
public class MyListView extends ListView implements OnGestureListener { 
 
    private GestureDetector gd; 
    // 事件狀態 
    public static final char FLING_CLICK = 0; 
    public static final char FLING_LEFT = 1; 
    public static final char FLING_RIGHT = 2; 
    public static char flingState = FLING_CLICK; 
 
    private float distanceX;// 水平滑動的距離 
 
    private MyListViewFling myListViewFling; 
 
    public static boolean isClick = false;// 是否可以點擊 
 
    public void setMyListViewFling(MyListViewFling myListViewFling) { 
        this.myListViewFling = myListViewFling; 
    } 
 
    public float getDistanceX() { 
        return distanceX; 
    } 
 
    public char getFlingState() { 
        return flingState; 
    } 
 
    private Context context; 
 
    public MyListView(Context context) { 
        super(context); 
 
    } 
 
    public MyListView(Context context, AttributeSet attrs) { 
        super(context, attrs); 
        this.context = context; 
        gd = new GestureDetector(this); 
    } 
 
    /**
     * 覆寫此方法,以解決ListView滑動被屏蔽問題
     */ 
    @Override 
    public boolean dispatchTouchEvent(MotionEvent event) { 
        myListViewFling.doFlingOver(event);// 回調執行完畢. 
        this.gd.onTouchEvent(event); 
 
        return super.dispatchTouchEvent(event); 
    } 
 
    @Override 
    public boolean onTouchEvent(MotionEvent ev) { 
        /***
         * 當移動的時候
         */ 
        if (ev.getAction() == MotionEvent.ACTION_DOWN) 
            isClick = true; 
        if (ev.getAction() == MotionEvent.ACTION_MOVE) 
            isClick = false; 
        return super.onTouchEvent(ev); 
    } 
 
    @Override 
    public boolean onDown(MotionEvent e) { 
        int position = pointToPosition((int) e.getX(), (int) e.getY()); 
        if (position != ListView.INVALID_POSITION) { 
            View child = getChildAt(position – getFirstVisiblePosition()); 
        } 
        return true; 
    } 
 
    @Override 
    public void onShowPress(MotionEvent e) { 
 
    } 
 
    @Override 
    public boolean onSingleTapUp(MotionEvent e) { 
 
        return false; 
    } 
 
    @Override 
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, 
            float distanceY) { 
        // 左滑動 
        if (distanceX > 0) { 
            flingState = FLING_RIGHT; 
            Log.v("jj", "左distanceX=" + distanceX); 
            myListViewFling.doFlingLeft(distanceX);// 回調 
            // 右滑動. 
        } else if (distanceX < 0) { 
            flingState = FLING_LEFT; 
            Log.v("jj", "右distanceX=" + distanceX); 
            myListViewFling.doFlingRight(distanceX);// 回調 
        } 
 
        return false; 
    } 
 
    /***
     * 上下文菜單
     */ 
    @Override 
    public void onLongPress(MotionEvent e) { 
        // System.out.println("Listview long press"); 
        // int position = pointToPosition((int) e.getX(), (int) e.getY()); 
        // if (position != ListView.INVALID_POSITION) { 
        // View child = getChildAt(position – getFirstVisiblePosition()); 
        // if (child != null) { 
        // showContextMenuForChild(child); 
        // this.requestFocusFromTouch(); 
        // } 
        // 
        // } 
    } 
 
    @Override 
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 
            float velocityY) { 
 
        return false; 
    } 
 
    /***
     * 回調接口
     * 
     * @author jjhappyforever…
     * 
     */ 
    interface MyListViewFling { 
        void doFlingLeft(float distanceX);// 左滑動執行 
 
        void doFlingRight(float distanceX);// 右滑動執行 
 
        void doFlingOver(MotionEvent event);// 拖拽松開時執行 
 
    } 
 

而在MainActivity.java裡面實現該接口,我這麼一說,我想有的同學們都明白瞭,具體實現起來代碼有點多,我把代碼上傳到網上,大傢可以下載後用心看,我想大傢都能夠明白的.(在這裡我鄙視一下自己,肯定通過對手勢監聽攔截實現對listview的左右滑動,但是自己學業不經,再次再說一下,如有好的解決方案,請一定要分享我一下哦.)
另外有一個問題:當listivew超出一屏的時候,此時的listview滑動的時候可以上下左右一起滑動,在此沒有解決這個問題,如有解決請分享我哦.
效果圖:

 

You May Also Like