1. 程式人生 > >android之SlideMenu雙向滑動

android之SlideMenu雙向滑動

slidemenu 表示 sed getx menus 頁面 asynctask event att

技術分享

開始動手之前先來講一下實現原理,在一個Activity的布局中需要有三部分,一個是左側菜單的布局,一個是右側菜單的布局,一個是內容布局。左側菜單居屏幕左邊緣對齊,右側菜單居屏幕右邊緣對齊,然後內容布局占滿整個屏幕,並壓在了左側菜單和右側菜單的上面。當用戶手指向右滑動時,將右側菜單隱藏,左側菜單顯示,然後通過偏移內容布局的位置,就可以讓左側菜單展現出來。同樣的道理,當用戶手指向左滑動時,將左側菜單隱藏,右側菜單顯示,也是通過偏移內容布局的位置,就可以讓右側菜單展現出來。 1.新建Android項目,然後新建一個DoubleSlideMenu繼承自RelativeLayout,核心類代碼:
public
class DoubleMenu extends RelativeLayout implements OnTouchListener { //滾動和隱藏菜單布局時手指所需要的速度 private static final int SNAP_VELOCITY=200; //未進行任何滑動 private static final int DO_NOTHING=0; //左側滑動菜單的顯示 private static final int SHOW_LEFT_MENU=1; //右側菜單顯示 private static final int
SHOW_RIGHT_MENU=2; //左側菜單隱藏 private static final int HIDE_LEFT_MENU=3; //右側菜單隱藏 private static final int HIDE_RIGHT_MENU=4; //記錄滑動的狀態 private int slideState; //滑動菜單最小的距離 private int touchSlop; //屏幕寬 private int screenWidth; //按下的橫坐標 private float
xDown; //按下的縱坐標 private float yDown; //移動的橫坐標 private float xMove; //移動的縱坐標 private float yMove; //彈起的橫坐標 private float xUp; //標誌左側菜單是否顯示 private boolean isLeftMenuVisible; //標誌右側菜單是否顯示 private boolean isRightMenuVisible; //左側菜單布局 private View leftMenuLayout; //右側菜單布局 private View rightMenuLayout; //左側菜單布局參數 private MarginLayoutParams leftMenuLayoutParams; //右側菜單布局參數 private MarginLayoutParams rightMenuLayoutParams; //內容頁面布局 private View contentLayout; //內容頁面布局參數 private RelativeLayout.LayoutParams contentLayoutParams; //標誌菜單是否正在滑動 private boolean isSliding; //用於監聽滑動事件的View private View mBindView; //手指的速度 private VelocityTracker mVelocityTracker; public DoubleMenu(Context context, AttributeSet attrs) { super(context, attrs); WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE); screenWidth=wm.getDefaultDisplay().getWidth(); touchSlop=ViewConfiguration.get(context).getScaledTouchSlop(); //觸發滾動菜單事件的最小距離 } @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); } } //綁定監聽滑動的View public void setScrollEvent(View bindView){ mBindView=bindView; mBindView.setOnTouchListener(this); } //判斷左側布局是否顯示 public boolean isLeftMenuVisible(){ return isLeftMenuVisible; } //判斷右側布局是否顯示 public boolean isRightMenuVisible(){ return isRightMenuVisible; } //顯示左側菜單時初始化 public void initShowLeftState(){ contentLayoutParams.rightMargin=0; contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0); contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); contentLayout.setLayoutParams(contentLayoutParams); leftMenuLayout.setVisibility(View.VISIBLE); rightMenuLayout.setVisibility(View.GONE); } //顯示右側菜單時初始化 public void initShowRightState(){ contentLayoutParams.leftMargin=0; 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 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; initShowLeftState(); } else if(!isSliding&&Math.abs(moveDistanceX)>touchSlop&&moveDistanceX<0&&Math.abs(moveDistanceY)<touchSlop){ isSliding=true; slideState=SHOW_RIGHT_MENU; initShowRightState(); } } } //在滑動中,檢查左側菜單的邊界值 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; } } /** * 創建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 boolean shouldScrollToLeftMenu(){ return xUp-xDown>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY; } //判斷是否應將右側菜單顯示 private boolean shouldScrollToRightMenu(){ return xDown-xUp>rightMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY; } //判斷是否應從左側轉到內容頁面 private boolean shouldScrollToContentFromLeft(){ return xDown-xUp>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY; } //判斷是否應從右側轉到內容頁面 private boolean shouldScrollToContentFromRight(){ return xUp-xDown>rightMenuLayoutParams.width/2|getScrollVelocity()>SNAP_VELOCITY; } //讓獲得焦點的控件在滑動過程中失去焦點 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>0){ //隱藏 rightMargin=0; break; } if(rightMargin<-leftMenuLayoutParams.width){ rightMargin=-leftMenuLayoutParams.width; break; } publishProgress(rightMargin); sleep(15); } if(speed[0]>0){ isLeftMenuVisible=false; } else{ isLeftMenuVisible=true; } isSliding=false; return rightMargin; } @Override protected void onProgressUpdate(Integer... values) { contentLayoutParams.rightMargin=values[0]; contentLayout.setLayoutParams(contentLayoutParams); unFocusBindView(); } @Override protected void onPostExecute(Integer result) { contentLayoutParams.rightMargin=result; 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>0){ leftMargin=0; break; } if(leftMargin<-rightMenuLayoutParams.width){ leftMargin=-rightMenuLayoutParams.width; break; } publishProgress(leftMargin); sleep(20); } if(speed[0]>0){ isRightMenuVisible=false; } else{ isRightMenuVisible=true; } isSliding=false; return leftMargin; } @Override protected void onProgressUpdate(Integer... values) { contentLayoutParams.leftMargin=values[0]; contentLayout.setLayoutParams(contentLayoutParams); unFocusBindView(); } @Override protected void onPostExecute(Integer result) { contentLayoutParams.leftMargin=result; contentLayout.setLayoutParams(contentLayoutParams); } } private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } //將界面滾到左側菜單 public void scrollToLeftMenu(){ new LeftMenuScrollTask().execute(-30); } //將界面滾動到右側菜單 public void scrollToRightMenu(){ new RightMenuScrollTask().execute(-30); } //將界面從左側滾到內容 public void scrollToContentFromLeft(){ new LeftMenuScrollTask().execute(30); } //將界面從右側滾到內容 public void scrollToContentFromRight(){ new RightMenuScrollTask().execute(30); } @Override public boolean onTouch(View v, MotionEvent event) { createVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: xDown=event.getRawX(); yDown=event.getRawY(); 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); break; 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); 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{ scrollToContentFromLeft(); } break; case HIDE_LEFT_MENU: if(shouldScrollToContentFromLeft()){ scrollToContentFromLeft(); } else{ scrollToLeftMenu(); } break; case SHOW_RIGHT_MENU: if(shouldScrollToRightMenu()){ scrollToRightMenu(); } else{ scrollToContentFromRight(); } break; case HIDE_RIGHT_MENU: if(shouldScrollToContentFromRight()){ scrollToContentFromRight(); } else{ scrollToRightMenu(); } default: break; } } else if(upDistanceX<touchSlop&&isLeftMenuVisible){ scrollToContentFromLeft(); } else if(upDistanceX<touchSlop&&isRightMenuVisible){ scrollToContentFromRight(); } recycleVelocityTracker(); break; } if(v.isClickable()){ //這裏要求控件必須是clickable的. if(isSliding){ unFocusBindView(); //正在滑動時取消控件焦點 return true; } if(isLeftMenuVisible||isRightMenuVisible){ return true;//當左菜單或右菜單顯示時,取消觸摸事件 } return false; } return true; } }

首先在onLayout()方法中分別獲取到左側菜單、右側菜單和內容布局的參數,並將內容布局的寬度重定義成屏幕的寬度,這樣就可以保證內容布局既能覆蓋住下面的菜單布局,還能偏移出屏幕。然後在onTouch()方法中監聽觸屏事件,以判斷用戶手勢的意圖。這裏事先定義好了幾種滑動狀態,DO_NOTHING表示沒有進行任何滑動,SHOW_LEFT_MENU表示用戶想要滑出左側菜單,SHOW_RIGHT_MENU表示用戶想要滑出右側菜單,HIDE_LEFT_MENU表示用戶想要隱藏左側菜單,HIDE_RIGHT_MENU表示用戶想要隱藏右側菜單,在checkSlideState()方法中判斷出用戶到底是想進行哪一種滑動操作,並給slideState變量賦值,然後根據slideState的值決定如何偏移內容布局。接著當用戶手指離開屏幕時,會根據當前的滑動距離,決定後續的滾動方向,通過LeftMenuScrollTask和RightMenuScrollTask來完成完整的滑動過程。另外在滑動的過程,內容布局上的事件會被屏蔽掉,主要是通過一系列的return操作實現的.

2.打開activity_main.xml

<com.example.doubleslidemenu.DoubleMenu 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:id="@+id/double_slideMenu"
    tools:context=".MainActivity" >
    <RelativeLayout 
        android:layout_width="270dp"
        android:layout_height="fill_parent"
        android:id="@+id/left_menu"
        android:layout_alignParentLeft="true"
        android:background="#00ccff"
        android:visibility="invisible" 
        >
                <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="This is left menu"
            android:textColor="#000000"
            android:textSize="28sp" />
    </RelativeLayout>
    
        <RelativeLayout
        android:id="@+id/right_menu"
        android:layout_width="270dip"
        android:layout_height="fill_parent"
        android:layout_alignParentRight="true"
        android:background="#00ffcc"
        android:visibility="invisible" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="This is right menu"
            android:textColor="#000000"
            android:textSize="28sp" />
    </RelativeLayout>
     <LinearLayout
        android:id="@+id/content"
        android:layout_width="320dip"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:background="#e9e9e9" >
        <RelativeLayout 
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            >
            <Button 
                android:id="@+id/show_left_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:text="show left"
                />
            <Button 
                android:id="@+id/show_right_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:text="show right"
                />
        </RelativeLayout>
        <ListView
            android:id="@+id/contentList"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:scrollbars="none"
            android:cacheColorHint="#00000000" >
        </ListView>
    </LinearLayout>
</com.example.doubleslidemenu.DoubleMenu>

3.打開MainActivity.java

public class MainActivity extends Activity {
    /**
     * 雙向滑動菜單布局
     */
    private DoubleMenu bidirSldingLayout;
    /**
     * 在內容布局上顯示的ListView
     */
    private ListView contentList;
    /**
     * ListView的適配器
     */
    private ArrayAdapter<String> contentListAdapter;
    /**
     * 用於填充contentListAdapter的數據源。
     */
    private String[] contentItems = { "Content Item 1", "Content Item 2", "Content Item 3",
            "Content Item 4", "Content Item 5", "Content Item 6", "Content Item 7",
            "Content Item 8", "Content Item 9", "Content Item 10", "Content Item 11",
            "Content Item 12", "Content Item 13", "Content Item 14", "Content Item 15",
            "Content Item 16" };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bidirSldingLayout = (DoubleMenu) findViewById(R.id.double_slideMenu);
        Button showLeftButton = (Button) findViewById(R.id.show_left_button);
        Button showRightButton = (Button) findViewById(R.id.show_right_button);
        contentList = (ListView) findViewById(R.id.contentList);
        contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
                contentItems);
        contentList.setAdapter(contentListAdapter);
        bidirSldingLayout.setScrollEvent(contentList);
        showLeftButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (bidirSldingLayout.isLeftMenuVisible()) {
                    bidirSldingLayout.scrollToContentFromLeft();
                } else {
                    bidirSldingLayout.initShowLeftState();
                    bidirSldingLayout.scrollToLeftMenu();
                }
            }
        });
        showRightButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (bidirSldingLayout.isRightMenuVisible()) {
                    bidirSldingLayout.scrollToContentFromRight();
                } else {
                    bidirSldingLayout.initShowRightState();
                    bidirSldingLayout.scrollToRightMenu();
                }
            }
        });
    }
}

至此,可以雙向滑動的SlideMenu就完成了.

android之SlideMenu雙向滑動