1. 程式人生 > >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();
		}
	}
}

以上程式碼註釋已經寫得非常詳細,我再來簡單解釋一下。首先在onLayout()方法中分別獲取到左側選單、右側選單和內容佈局的引數,並將內容佈局的寬度重定義成螢幕的寬度,這樣就可以保證內容佈局既能覆蓋住下面的選單佈局,還能偏移出螢幕。然後在onTouch()方法中監聽觸屏事件,以判斷使用者手勢的意圖。這裡事先定義好了幾種滑動狀態,DO_NOTHING表示沒有進行任何滑動,SHOW_LEFT_MENU表示使用者想要滑出左側選單,SHOW_RIGHT_MENU表示使用者想要滑出右側選單,HIDE_LEFT_MENU表示使用者想要隱藏左側選單,HIDE_RIGHT_MENU表示使用者想要隱藏右側選單,在checkSlideState()方法中判斷出使用者到底是想進行哪一種滑動操作,並給slideState變數賦值,然後根據slideState的值決定如何偏移內容佈局。接著當用戶手指離開螢幕時,會根據當前的滑動距離,決定後續的滾動方向,通過LeftMenuScrollTask和RightMenuScrollTask來完成完整的滑動過程。另外在滑動的過程,內容佈局上的事件會被遮蔽掉,主要是通過一系列的return操作實現的,對這一部分不理解的朋友,請參閱 

Android事件分發機制完全解析,帶你從原始碼的角度徹底理解 。

然後我們看一下setScrollEvent方法,這個方法接收一個View作為引數,然後為這個View綁定了一個touch事件。這是什麼意思呢?讓我們來想象一個場景,如果內容佈局是一個LinearLayout,我可以通過監聽LinearLayout上的touch事件來控制它的偏移。但是如果內容佈局的LinearLayout裡面加入了一個ListView,而這個ListView又充滿了整個LinearLayout,這個時候LinearLayout將不可能再被touch到了,這個時候我們就需要將touch事件註冊到ListView上。setScrollEvent方法也就是提供了一個註冊介面,touch事件將會註冊到傳入的View上。

接下來開啟或新建activity_main.xml檔案,加入如下程式碼:

<com.example.bidirslidinglayout.BidirSlidingLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/bidir_sliding_layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <RelativeLayout
        android:id="@+id/left_menu"
        android:layout_width="270dip"
        android:layout_height="fill_parent"
        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:background="#e9e9e9" >

        <ListView
            android:id="@+id/contentList"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:scrollbars="none"
            android:cacheColorHint="#00000000" >
        </ListView>
    </LinearLayout>

</com.example.bidirslidinglayout.BidirSlidingLayout>

可以看到,我們使用了自定義的BidirSlidingLayout作為根佈局,然後依次加入了三個子佈局分別作為左側選單、右側選單和內容的佈局。左側選單和右側選單中都只是簡單地放入了一個TextView用於顯示一段文字,內容佈局中放入了一個ListView。注意要讓左側選單和父佈局左邊緣對齊,右側選單和父佈局右邊緣對齊。

最後開啟或者建立MainActivity作為程式的主Activity,程式碼如下所示:

public class MainActivity extends Activity {

	/**
	 * 雙向滑動選單佈局
	 */
	private BidirSlidingLayout 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 = (BidirSlidingLayout) findViewById(R.id.bidir_sliding_layout);
		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);
	}

}

這裡我們給ListView填充了幾條資料,又通過findViewById()方法獲取到了BidirSlidingLayout物件,然後呼叫它的setScrollEvent()方法,將ListView進行繫結,這樣就可以通過左右滑動ListView來展示左側選單和右側選單了。

好了,全部編碼工作都已完成,現在讓我們執行一下程式吧,效果如下圖所示:


看起來還是挺不錯的吧!並且更重要的是,以後我們在專案的任何地方都可以輕鬆加入雙向滑動選單功能,只需要以下兩步即可:

1. 在Acitivty的layout中引入我們自定義的BidirSlidingLayout佈局,並且給這個佈局要加入三個直接子元素。

2. 在Activity中通過setScrollEvent方法,給一個View註冊touch事件。

如此一來,一分鐘實現雙向滑動選單功能妥妥的。

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

關注我的技術公眾號,每天都有優質技術文章推送。關注我的娛樂公眾號,工作、學習累了的時候放鬆一下自己。

微信掃一掃下方二維碼即可關注:

        

相關推薦

Android雙向滑動選單完全解析如何分鐘實現雙向滑動特效

記得在很早之前,我寫了一篇關於Android滑動選單的文章,其中有一個朋友在評論中留言,希望我可以幫他將這個滑動選單改成雙向滑動的方式。當時也沒想花太多時間,簡單修改了一下就發給了他,結果沒想到後來卻有一大批的朋友都來問我要這份雙向滑動選單的程式碼。由於這份程式碼寫得很不用心

Android檢視繪製流程完全解析步步深入瞭解View(二)

在上一篇文章中,我帶著大家一起剖析了一下LayoutInflater的工作原理,可以算是對View進行深入瞭解的第一步吧。那麼本篇文章中,我們將繼續對View進行深入探究,看一看它的繪製流程到底是什麼樣的。如果你還沒有看過我的上一篇文章,可以先去閱讀 Android Layo

Android 3D滑動選單完全解析實現推拉門式的立體特效

轉載地址:http://blog.csdn.net/guolin_blog/article/details/10471245 下面還是回到正題,首先來講一下這次的實現原理吧,其實傳統的滑動選單功能就是把選單部分放在了下面,主佈局放在了上面,然後根據手指滑動的距離來偏

Android事件分發機制完全解析從原始碼的角度徹底理解(上)-郭霖

其實我一直準備寫一篇關於Android事件分發機制的文章,從我的第一篇部落格開始,就零零散散在好多地方使用到了Android事件分發的知識。也有好多朋友問過我各種問題,比如:onTouch和onTouchEvent有什麼區別,又該如何使用?為什麼給ListView引入了一

Android事件分發機制完全解析從原始碼的角度徹底理解(下)-郭霖

記得在前面的文章中,我帶大家一起從原始碼的角度分析了Android中View的事件分發機制,相信閱讀過的朋友對View的事件分發已經有比較深刻的理解了。 還未閱讀過的朋友,請先參考 Android事件分發機制完全解析,帶你從原始碼的角度徹底理解(上) 。 那麼今天我們將繼

Android ListView工作原理完全解析從原始碼的角度徹底理解

在Android所有常用的原生控制元件當中,用法最複雜的應該就是ListView了,它專門用於處理那種內容元素很多,手機螢幕無法展示出所有內容的情況。ListView可以使用列表的形式來展示內容,超出螢幕部分的內容只需要通過手指滑動就可以移動到螢幕內了。另外ListView還

Android事件分發機制完全解析從原始碼的角度徹底理解(上)

其實我一直準備寫一篇關於Android事件分發機制的文章,從我的第一篇部落格開始,就零零散散在好多地方使用到了Android事件分發的知識。也有好多朋友問過我各種問題,比如:onTouch和onTouchEvent有什麼區別,又該如何使用?為什麼給ListView引入

Android事件分發機制完全解析從原始碼的角度徹底理解(下)

記得在前面的文章中,我帶大家一起從原始碼的角度分析了Android中View的事件分發機制,相信閱讀過的朋友對View的事件分發已經有比較深刻的理解了。那麼今天我們將繼續上次未完成的話題,從原始碼的角度分析ViewGroup的事件分發。首先我們來探討一下,什麼是ViewGro

Android事件分發機制完全解析從原始碼的角度徹底理解(下) (出自郭林老師)

記得在前面的文章中,我帶大家一起從原始碼的角度分析了Android中View的事件分發機制,相信閱讀過的朋友對View的事件分發已經有比較深刻的理解了。 那麼今天我們將繼續上次未完成的話題,從原始碼的角度分析ViewGroup的事件分發。 首先我們來探討一下,什

Android事件分發機制完全解析從原始碼的角度徹底理解(上) (出自郭霖老師)

其實我一直準備寫一篇關於Android事件分發機制的文章,從我的第一篇部落格開始,就零零散散在好多地方使用到了Android事件分發的知識。也有好多朋友問過我各種問題,比如:onTouch和onTouchEvent有什麼區別,又該如何使用?為什麼給ListView引入了

Android冷啟動白屏解析步步分析和解決問題

本文同步發表於我的微信公眾號,掃一掃文章底部的二維碼或在微信搜尋 郭霖 即可關注,每天都有文章更新。 寫在前面 記得在本月初,我發表了一篇文章叫《 Android Studio新功能解析,你真的瞭解Instant Run嗎?》,裡面詳細講解了

Android AsyncTask完全解析從原始碼的角度徹底理解

我們都知道,Android UI是執行緒不安全的,如果想要在子執行緒裡進行UI操作,就需要藉助Android的非同步訊息處理機制。之前我也寫過了一篇文章從原始碼層面分析了Android的非同步訊息處理機制,感興趣的朋友可以參考 Android Handler、Message完

Vue雙向繫結原理實現雙向繫結

當今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個陣營基本上無法立足於前端,甚至是兩個或者三個陣營都要選擇,大勢所趨。 所以我們要時刻保持好奇心,擁抱變化,只有在不斷的變化中你才能利於不敗之地,保守只能等死。 最近在學習 Vue,一直以來對它的雙向繫結只能算了解並不深入,最近幾天

live555原始碼詳細分析解析如何使用live555接收rtsp資料

最近繼續看了看live555的程式碼,進行了一些具體的實驗。 對live555的部分特性有了一定的瞭解。 稍微看了網上的一些關於live555的文章,千篇一律。 大部分都是轉載自一個人的部落格。 很少人進行一些實驗或進行一些比較深入的程式碼閱讀,然後寫出一些其他的經驗之談。

Android 軟鍵盤的全面解析不再怕控件被遮蓋

() details 聲明 token andro 博文 blog 轉載 targe 博客地址:http://blog.csdn.net/l540675759/article/details/74528641 (2)在軟鍵盤彈出時,是否需要Activity對此進行調整adj

Excel快速列印底端標題肯定行!

pdf技巧之家 2018-11-10 11:06:00今天跟大家分享一個關於Excel如何快速列印底端標題的技巧,需要的趕緊學起來! 方法步驟: 首先我們在工作表中輸入底部要顯示的標題文字,然後使用截圖工具把這部分文字截圖儲存為圖片,接下來將工作表中的底端標題文字清除掉。 點選工具欄【

Excel快速打印底端標題肯定行!

一個 我們 ffffff 對話 mage 單擊 ima 頁腳 img pdf技巧之家 2018-11-10 11:06:00今天跟大家分享一個關於Excel如何快速打印底端標題的技巧,需要的趕緊學起來! 方法步驟: 首先我們在工作表中輸入底部要顯示的標題文字,然後使用截圖

java堆記憶體又溢位了招必殺技

JAVA堆記憶體管理是影響效能主要因素之一。 堆記憶體溢位是JAVA專案非常常見的故障,在解決該問題之前,必須先了解下JAVA堆記憶體是怎麼工作的。 先看下JAVA堆記憶體是如何劃分的,如圖: JVM記憶體劃分為堆記憶體和非堆記憶體,堆記憶體分為年輕代(Young Gen

北京java程式設計師招改變渾渾噩噩的狀態:潘金森法則

  大家有沒有這樣的經歷,接到一項新任務,截止時間是2天,結果,前面一天半,你寫了一個開頭就去幹別的事情了,截止時間只剩下一點點的時候,才打起精神,超級專注,很快完成,最後結果還不錯。   但是,明明可以半小時完成後,去做新工作,為什麼一定讓那YI天半都承受沒有完成工

被監視了嗎?電腦上搜索什麼就出現什麼廣告招解決

  很多人一直有這樣的苦惱:為什麼只要在百度等搜尋引擎或者天貓、京東等購物平臺上搜索過一樣東西后,在幾天之內去瀏覽其他網站時,都會出現關於那樣東西的廣告,甚至直接彈出視窗廣告。而且推送廣告的效率還非常高,有時候剛搜尋,就隨時出現類似的廣告了。