溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Android實(shí)現(xiàn)雙向滑動(dòng)特效的實(shí)例代碼

發(fā)布時(shí)間:2020-10-21 13:32:29 來(lái)源:腳本之家 閱讀:138 作者:guolin 欄目:移動(dòng)開(kāi)發(fā)

記得在很早之前,我寫(xiě)了一篇關(guān)于Android滑動(dòng)菜單的文章,其中有一個(gè)朋友在評(píng)論中留言,希望我可以幫他將這個(gè)滑動(dòng)菜單改成雙向滑動(dòng)的方式。當(dāng)時(shí)也沒(méi)想花太多時(shí)間,簡(jiǎn)單修改了一下就發(fā)給了他,結(jié)果沒(méi)想到后來(lái)卻有一大批的朋友都來(lái)問(wèn)我要這份雙向滑動(dòng)菜單的代碼。由于這份代碼寫(xiě)得很不用心,我發(fā)了部分朋友之后實(shí)在不忍心繼續(xù)發(fā)下去了,于是決定專(zhuān)門(mén)寫(xiě)一篇文章來(lái)介紹更好的Android雙向滑動(dòng)菜單的實(shí)現(xiàn)方法。

在開(kāi)始動(dòng)手之前先來(lái)講一下實(shí)現(xiàn)原理,在一個(gè)Activity的布局中需要有三部分,一個(gè)是左側(cè)菜單的布局,一個(gè)是右側(cè)菜單的布局,一個(gè)是內(nèi)容布局。左側(cè)菜單居屏幕左邊緣對(duì)齊,右側(cè)菜單居屏幕右邊緣對(duì)齊,然后內(nèi)容布局占滿(mǎn)整個(gè)屏幕,并壓在了左側(cè)菜單和右側(cè)菜單的上面。當(dāng)用戶(hù)手指向右滑動(dòng)時(shí),將右側(cè)菜單隱藏,左側(cè)菜單顯示,然后通過(guò)偏移內(nèi)容布局的位置,就可以讓左側(cè)菜單展現(xiàn)出來(lái)。同樣的道理,當(dāng)用戶(hù)手指向左滑動(dòng)時(shí),將左側(cè)菜單隱藏,右側(cè)菜單顯示,也是通過(guò)偏移內(nèi)容布局的位置,就可以讓右側(cè)菜單展現(xiàn)出來(lái)。原理示意圖所下所示:

Android實(shí)現(xiàn)雙向滑動(dòng)特效的實(shí)例代碼

介紹完了原理,我們就開(kāi)始動(dòng)手實(shí)現(xiàn)吧。新建一個(gè)Android項(xiàng)目,項(xiàng)目名就叫做BidirSlidingLayout。然后新建我們最主要的BidirSlidingLayout類(lèi),這個(gè)類(lèi)就是實(shí)現(xiàn)雙向滑動(dòng)菜單功能的核心類(lèi),代碼如下所示:

public class BidirSlidingLayout extends RelativeLayout implements OnTouchListener { 
 /** 
 * 滾動(dòng)顯示和隱藏左側(cè)布局時(shí),手指滑動(dòng)需要達(dá)到的速度。 
 */ 
 public static final int SNAP_VELOCITY = 200; 
 /** 
 * 滑動(dòng)狀態(tài)的一種,表示未進(jìn)行任何滑動(dòng)。 
 */ 
 public static final int DO_NOTHING = 0; 
 /** 
 * 滑動(dòng)狀態(tài)的一種,表示正在滑出左側(cè)菜單。 
 */ 
 public static final int SHOW_LEFT_MENU = 1; 
 /** 
 * 滑動(dòng)狀態(tài)的一種,表示正在滑出右側(cè)菜單。 
 */ 
 public static final int SHOW_RIGHT_MENU = 2; 
 /** 
 * 滑動(dòng)狀態(tài)的一種,表示正在隱藏左側(cè)菜單。 
 */ 
 public static final int HIDE_LEFT_MENU = 3; 
 /** 
 * 滑動(dòng)狀態(tài)的一種,表示正在隱藏右側(cè)菜單。 
 */ 
 public static final int HIDE_RIGHT_MENU = 4; 
 /** 
 * 記錄當(dāng)前的滑動(dòng)狀態(tài) 
 */ 
 private int slideState; 
 /** 
 * 屏幕寬度值。 
 */ 
 private int screenWidth; 
 /** 
 * 在被判定為滾動(dòng)之前用戶(hù)手指可以移動(dòng)的最大值。 
 */ 
 private int touchSlop; 
 /** 
 * 記錄手指按下時(shí)的橫坐標(biāo)。 
 */ 
 private float xDown; 
 /** 
 * 記錄手指按下時(shí)的縱坐標(biāo)。 
 */ 
 private float yDown; 
 /** 
 * 記錄手指移動(dòng)時(shí)的橫坐標(biāo)。 
 */ 
 private float xMove; 
 /** 
 * 記錄手指移動(dòng)時(shí)的縱坐標(biāo)。 
 */ 
 private float yMove; 
 /** 
 * 記錄手機(jī)抬起時(shí)的橫坐標(biāo)。 
 */ 
 private float xUp; 
 /** 
 * 左側(cè)菜單當(dāng)前是顯示還是隱藏。只有完全顯示或隱藏時(shí)才會(huì)更改此值,滑動(dòng)過(guò)程中此值無(wú)效。 
 */ 
 private boolean isLeftMenuVisible; 
 /** 
 * 右側(cè)菜單當(dāng)前是顯示還是隱藏。只有完全顯示或隱藏時(shí)才會(huì)更改此值,滑動(dòng)過(guò)程中此值無(wú)效。 
 */ 
 private boolean isRightMenuVisible; 
 /** 
 * 是否正在滑動(dòng)。 
 */ 
 private boolean isSliding; 
 /** 
 * 左側(cè)菜單布局對(duì)象。 
 */ 
 private View leftMenuLayout; 
 /** 
 * 右側(cè)菜單布局對(duì)象。 
 */ 
 private View rightMenuLayout; 
 /** 
 * 內(nèi)容布局對(duì)象。 
 */ 
 private View contentLayout; 
 /** 
 * 用于監(jiān)聽(tīng)滑動(dòng)事件的View。 
 */ 
 private View mBindView; 
 /** 
 * 左側(cè)菜單布局的參數(shù)。 
 */ 
 private MarginLayoutParams leftMenuLayoutParams; 
 /** 
 * 右側(cè)菜單布局的參數(shù)。 
 */ 
 private MarginLayoutParams rightMenuLayoutParams; 
 /** 
 * 內(nèi)容布局的參數(shù)。 
 */ 
 private RelativeLayout.LayoutParams contentLayoutParams; 
 /** 
 * 用于計(jì)算手指滑動(dòng)的速度。 
 */ 
 private VelocityTracker mVelocityTracker; 
 /** 
 * 重寫(xiě)B(tài)idirSlidingLayout的構(gòu)造函數(shù),其中獲取了屏幕的寬度和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(); 
 } 
 /** 
 * 綁定監(jiān)聽(tīng)滑動(dòng)事件的View。 
 * 
 * @param bindView 
 *  需要綁定的View對(duì)象。 
 */ 
 public void setScrollEvent(View bindView) { 
 mBindView = bindView; 
 mBindView.setOnTouchListener(this); 
 } 
 /** 
 * 將界面滾動(dòng)到左側(cè)菜單界面,滾動(dòng)速度設(shè)定為-30. 
 */ 
 public void scrollToLeftMenu() { 
 new LeftMenuScrollTask().execute(-30); 
 } 
 /** 
 * 將界面滾動(dòng)到右側(cè)菜單界面,滾動(dòng)速度設(shè)定為-30. 
 */ 
 public void scrollToRightMenu() { 
 new RightMenuScrollTask().execute(-30); 
 } 
 /** 
 * 將界面從左側(cè)菜單滾動(dòng)到內(nèi)容界面,滾動(dòng)速度設(shè)定為30. 
 */ 
 public void scrollToContentFromLeftMenu() { 
 new LeftMenuScrollTask().execute(30); 
 } 
 /** 
 * 將界面從右側(cè)菜單滾動(dòng)到內(nèi)容界面,滾動(dòng)速度設(shè)定為30. 
 */ 
 public void scrollToContentFromRightMenu() { 
 new RightMenuScrollTask().execute(30); 
 } 
 /** 
 * 左側(cè)菜單是否完全顯示出來(lái),滑動(dòng)過(guò)程中此值無(wú)效。 
 * 
 * @return 左側(cè)菜單完全顯示返回true,否則返回false。 
 */ 
 public boolean isLeftLayoutVisible() { 
 return isLeftMenuVisible; 
 } 
 /** 
 * 右側(cè)菜單是否完全顯示出來(lái),滑動(dòng)過(guò)程中此值無(wú)效。 
 * 
 * @return 右側(cè)菜單完全顯示返回true,否則返回false。 
 */ 
 public boolean isRightLayoutVisible() { 
 return isRightMenuVisible; 
 } 
 /** 
 * 在onLayout中重新設(shè)定左側(cè)菜單、右側(cè)菜單、以及內(nèi)容布局的參數(shù)。 
 */ 
 @Override 
 protected void onLayout(boolean changed, int l, int t, int r, int b) { 
 super.onLayout(changed, l, t, r, b); 
 if (changed) { 
  // 獲取左側(cè)菜單布局對(duì)象 
  leftMenuLayout = getChildAt(0); 
  leftMenuLayoutParams = (MarginLayoutParams) leftMenuLayout.getLayoutParams(); 
  // 獲取右側(cè)菜單布局對(duì)象 
  rightMenuLayout = getChildAt(1); 
  rightMenuLayoutParams = (MarginLayoutParams) rightMenuLayout.getLayoutParams(); 
  // 獲取內(nèi)容布局對(duì)象 
  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: 
  // 手指按下時(shí),記錄按下時(shí)的坐標(biāo) 
  xDown = event.getRawX(); 
  yDown = event.getRawY(); 
  // 將滑動(dòng)狀態(tài)初始化為DO_NOTHING 
  slideState = DO_NOTHING; 
  break; 
 case MotionEvent.ACTION_MOVE: 
  xMove = event.getRawX(); 
  yMove = event.getRawY(); 
  // 手指移動(dòng)時(shí),對(duì)比按下時(shí)的坐標(biāo),計(jì)算出移動(dòng)的距離。 
  int moveDistanceX = (int) (xMove - xDown); 
  int moveDistanceY = (int) (yMove - yDown); 
  // 檢查當(dāng)前的滑動(dòng)狀態(tài) 
  checkSlideState(moveDistanceX, moveDistanceY); 
  // 根據(jù)當(dāng)前滑動(dòng)狀態(tài)決定如何偏移內(nèi)容布局 
  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) { 
  // 手指抬起時(shí),進(jìn)行判斷當(dāng)前手勢(shì)的意圖 
  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) { 
  // 當(dāng)左側(cè)菜單顯示時(shí),如果用戶(hù)點(diǎn)擊一下內(nèi)容部分,則直接滾動(dòng)到內(nèi)容界面 
  scrollToContentFromLeftMenu(); 
  } else if (upDistanceX < touchSlop && isRightMenuVisible) { 
  // 當(dāng)右側(cè)菜單顯示時(shí),如果用戶(hù)點(diǎn)擊一下內(nèi)容部分,則直接滾動(dòng)到內(nèi)容界面 
  scrollToContentFromRightMenu(); 
  } 
  recycleVelocityTracker(); 
  break; 
 } 
 if (v.isEnabled()) { 
  if (isSliding) { 
  // 正在滑動(dòng)時(shí)讓控件得不到焦點(diǎn) 
  unFocusBindView(); 
  return true; 
  } 
  if (isLeftMenuVisible || isRightMenuVisible) { 
  // 當(dāng)左側(cè)或右側(cè)布局顯示時(shí),將綁定控件的事件屏蔽掉 
  return true; 
  } 
  return false; 
 } 
 return true; 
 } 
 /** 
 * 根據(jù)手指移動(dòng)的距離,判斷當(dāng)前用戶(hù)的滑動(dòng)意圖,然后給slideState賦值成相應(yīng)的滑動(dòng)狀態(tài)值。 
 * 
 * @param moveDistanceX 
 *  橫向移動(dòng)的距離 
 * @param moveDistanceY 
 *  縱向移動(dòng)的距離 
 */ 
 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); 
  // 如果用戶(hù)想要滑動(dòng)左側(cè)菜單,將左側(cè)菜單顯示,右側(cè)菜單隱藏 
  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); 
  // 如果用戶(hù)想要滑動(dòng)右側(cè)菜單,將右側(cè)菜單顯示,左側(cè)菜單隱藏 
  rightMenuLayout.setVisibility(View.VISIBLE); 
  leftMenuLayout.setVisibility(View.GONE); 
  } 
 } 
 } 
 /** 
 * 在滑動(dòng)過(guò)程中檢查左側(cè)菜單的邊界值,防止綁定布局滑出屏幕。 
 */ 
 private void checkLeftMenuBorder() { 
 if (contentLayoutParams.rightMargin > 0) { 
  contentLayoutParams.rightMargin = 0; 
 } else if (contentLayoutParams.rightMargin < -leftMenuLayoutParams.width) { 
  contentLayoutParams.rightMargin = -leftMenuLayoutParams.width; 
 } 
 } 
 /** 
 * 在滑動(dòng)過(guò)程中檢查右側(cè)菜單的邊界值,防止綁定布局滑出屏幕。 
 */ 
 private void checkRightMenuBorder() { 
 if (contentLayoutParams.leftMargin > 0) { 
  contentLayoutParams.leftMargin = 0; 
 } else if (contentLayoutParams.leftMargin < -rightMenuLayoutParams.width) { 
  contentLayoutParams.leftMargin = -rightMenuLayoutParams.width; 
 } 
 } 
 /** 
 * 判斷是否應(yīng)該滾動(dòng)將左側(cè)菜單展示出來(lái)。如果手指移動(dòng)距離大于左側(cè)菜單寬度的1/2,或者手指移動(dòng)速度大于SNAP_VELOCITY, 
 * 就認(rèn)為應(yīng)該滾動(dòng)將左側(cè)菜單展示出來(lái)。 
 * 
 * @return 如果應(yīng)該將左側(cè)菜單展示出來(lái)返回true,否則返回false。 
 */ 
 private boolean shouldScrollToLeftMenu() { 
 return xUp - xDown > leftMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY; 
 } 
 /** 
 * 判斷是否應(yīng)該滾動(dòng)將右側(cè)菜單展示出來(lái)。如果手指移動(dòng)距離大于右側(cè)菜單寬度的1/2,或者手指移動(dòng)速度大于SNAP_VELOCITY, 
 * 就認(rèn)為應(yīng)該滾動(dòng)將右側(cè)菜單展示出來(lái)。 
 * 
 * @return 如果應(yīng)該將右側(cè)菜單展示出來(lái)返回true,否則返回false。 
 */ 
 private boolean shouldScrollToRightMenu() { 
 return xDown - xUp > rightMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY; 
 } 
 /** 
 * 判斷是否應(yīng)該從左側(cè)菜單滾動(dòng)到內(nèi)容布局,如果手指移動(dòng)距離大于左側(cè)菜單寬度的1/2,或者手指移動(dòng)速度大于SNAP_VELOCITY, 
 * 就認(rèn)為應(yīng)該從左側(cè)菜單滾動(dòng)到內(nèi)容布局。 
 * 
 * @return 如果應(yīng)該從左側(cè)菜單滾動(dòng)到內(nèi)容布局返回true,否則返回false。 
 */ 
 private boolean shouldScrollToContentFromLeftMenu() { 
 return xDown - xUp > leftMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY; 
 } 
 /** 
 * 判斷是否應(yīng)該從右側(cè)菜單滾動(dòng)到內(nèi)容布局,如果手指移動(dòng)距離大于右側(cè)菜單寬度的1/2,或者手指移動(dòng)速度大于SNAP_VELOCITY, 
 * 就認(rèn)為應(yīng)該從右側(cè)菜單滾動(dòng)到內(nèi)容布局。 
 * 
 * @return 如果應(yīng)該從右側(cè)菜單滾動(dòng)到內(nèi)容布局返回true,否則返回false。 
 */ 
 private boolean shouldScrollToContentFromRightMenu() { 
 return xUp - xDown > rightMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY; 
 } 
 /** 
 * 創(chuàng)建VelocityTracker對(duì)象,并將觸摸事件加入到VelocityTracker當(dāng)中。 
 * 
 * @param event 
 *  右側(cè)布局監(jiān)聽(tīng)控件的滑動(dòng)事件 
 */ 
 private void createVelocityTracker(MotionEvent event) { 
 if (mVelocityTracker == null) { 
  mVelocityTracker = VelocityTracker.obtain(); 
 } 
 mVelocityTracker.addMovement(event); 
 } 
 /** 
 * 獲取手指在綁定布局上的滑動(dòng)速度。 
 * 
 * @return 滑動(dòng)速度,以每秒鐘移動(dòng)了多少像素值為單位。 
 */ 
 private int getScrollVelocity() { 
 mVelocityTracker.computeCurrentVelocity(1000); 
 int velocity = (int) mVelocityTracker.getXVelocity(); 
 return Math.abs(velocity); 
 } 
 /** 
 * 回收VelocityTracker對(duì)象。 
 */ 
 private void recycleVelocityTracker() { 
 mVelocityTracker.recycle(); 
 mVelocityTracker = null; 
 } 
 /** 
 * 使用可以獲得焦點(diǎn)的控件在滑動(dòng)的時(shí)候失去焦點(diǎn)。 
 */ 
 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; 
  // 根據(jù)傳入的速度來(lái)滾動(dòng)界面,當(dāng)滾動(dòng)到達(dá)邊界值時(shí),跳出循環(huán)。 
  while (true) { 
  rightMargin = rightMargin + speed[0]; 
  if (rightMargin < -leftMenuLayoutParams.width) { 
   rightMargin = -leftMenuLayoutParams.width; 
   break; 
  } 
  if (rightMargin > 0) { 
   rightMargin = 0; 
   break; 
  } 
  publishProgress(rightMargin); 
  // 為了要有滾動(dòng)效果產(chǎn)生,每次循環(huán)使線(xiàn)程睡眠一段時(shí)間,這樣肉眼才能夠看到滾動(dòng)動(dòng)畫(huà)。 
  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; 
  // 根據(jù)傳入的速度來(lái)滾動(dòng)界面,當(dāng)滾動(dòng)到達(dá)邊界值時(shí),跳出循環(huán)。 
  while (true) { 
  leftMargin = leftMargin + speed[0]; 
  if (leftMargin < -rightMenuLayoutParams.width) { 
   leftMargin = -rightMenuLayoutParams.width; 
   break; 
  } 
  if (leftMargin > 0) { 
   leftMargin = 0; 
   break; 
  } 
  publishProgress(leftMargin); 
  // 為了要有滾動(dòng)效果產(chǎn)生,每次循環(huán)使線(xiàn)程睡眠一段時(shí)間,這樣肉眼才能夠看到滾動(dòng)動(dòng)畫(huà)。 
  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); 
 } 
 } 
 /** 
 * 使當(dāng)前線(xiàn)程睡眠指定的毫秒數(shù)。 
 * 
 * @param millis 
 *  指定當(dāng)前線(xiàn)程睡眠多久,以毫秒為單位 
 */ 
 private void sleep(long millis) { 
 try { 
  Thread.sleep(millis); 
 } catch (InterruptedException e) { 
  e.printStackTrace(); 
 } 
 } 
} 

以上代碼注釋已經(jīng)寫(xiě)得非常詳細(xì),我再來(lái)簡(jiǎn)單解釋一下。首先在onLayout()方法中分別獲取到左側(cè)菜單、右側(cè)菜單和內(nèi)容布局的參數(shù),并將內(nèi)容布局的寬度重定義成屏幕的寬度,這樣就可以保證內(nèi)容布局既能覆蓋住下面的菜單布局,還能偏移出屏幕。然后在onTouch()方法中監(jiān)聽(tīng)觸屏事件,以判斷用戶(hù)手勢(shì)的意圖。這里事先定義好了幾種滑動(dòng)狀態(tài),DO_NOTHING表示沒(méi)有進(jìn)行任何滑動(dòng),SHOW_LEFT_MENU表示用戶(hù)想要滑出左側(cè)菜單,SHOW_RIGHT_MENU表示用戶(hù)想要滑出右側(cè)菜單,HIDE_LEFT_MENU表示用戶(hù)想要隱藏左側(cè)菜單,HIDE_RIGHT_MENU表示用戶(hù)想要隱藏右側(cè)菜單,在checkSlideState()方法中判斷出用戶(hù)到底是想進(jìn)行哪一種滑動(dòng)操作,并給slideState變量賦值,然后根據(jù)slideState的值決定如何偏移內(nèi)容布局。接著當(dāng)用戶(hù)手指離開(kāi)屏幕時(shí),會(huì)根據(jù)當(dāng)前的滑動(dòng)距離,決定后續(xù)的滾動(dòng)方向,通過(guò)LeftMenuScrollTask和RightMenuScrollTask來(lái)完成完整的滑動(dòng)過(guò)程。另外在滑動(dòng)的過(guò)程,內(nèi)容布局上的事件會(huì)被屏蔽掉,主要是通過(guò)一系列的return操作實(shí)現(xiàn)的,對(duì)這一部分不理解的朋友,請(qǐng)參閱 Android從源碼的角度徹底理解事件分發(fā)機(jī)制的解析。

然后我們看一下setScrollEvent方法,這個(gè)方法接收一個(gè)View作為參數(shù),然后為這個(gè)View綁定了一個(gè)touch事件。這是什么意思呢?讓我們來(lái)想象一個(gè)場(chǎng)景,如果內(nèi)容布局是一個(gè)LinearLayout,我可以通過(guò)監(jiān)聽(tīng)LinearLayout上的touch事件來(lái)控制它的偏移。但是如果內(nèi)容布局的LinearLayout里面加入了一個(gè)ListView,而這個(gè)ListView又充滿(mǎn)了整個(gè)LinearLayout,這個(gè)時(shí)候LinearLayout將不可能再被touch到了,這個(gè)時(shí)候我們就需要將touch事件注冊(cè)到ListView上。setScrollEvent方法也就是提供了一個(gè)注冊(cè)接口,touch事件將會(huì)注冊(cè)到傳入的View上。

接下來(lái)打開(kāi)或新建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作為根布局,然后依次加入了三個(gè)子布局分別作為左側(cè)菜單、右側(cè)菜單和內(nèi)容的布局。左側(cè)菜單和右側(cè)菜單中都只是簡(jiǎn)單地放入了一個(gè)TextView用于顯示一段文字,內(nèi)容布局中放入了一個(gè)ListView。注意要讓左側(cè)菜單和父布局左邊緣對(duì)齊,右側(cè)菜單和父布局右邊緣對(duì)齊。

最后打開(kāi)或者創(chuàng)建MainActivity作為程序的主Activity,代碼如下所示:

public class MainActivity extends Activity { 
 /** 
 * 雙向滑動(dòng)菜單布局 
 */ 
 private BidirSlidingLayout bidirSldingLayout; 
 /** 
 * 在內(nèi)容布局上顯示的ListView 
 */ 
 private ListView contentList; 
 /** 
 * ListView的適配器 
 */ 
 private ArrayAdapter<String> contentListAdapter; 
 /** 
 * 用于填充contentListAdapter的數(shù)據(jù)源。 
 */ 
 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填充了幾條數(shù)據(jù),又通過(guò)findViewById()方法獲取到了BidirSlidingLayout對(duì)象,然后調(diào)用它的setScrollEvent()方法,將ListView進(jìn)行綁定,這樣就可以通過(guò)左右滑動(dòng)ListView來(lái)展示左側(cè)菜單和右側(cè)菜單了。

好了,全部編碼工作都已完成,現(xiàn)在讓我們運(yùn)行一下程序吧,效果如下圖所示:

Android實(shí)現(xiàn)雙向滑動(dòng)特效的實(shí)例代碼

看起來(lái)還是挺不錯(cuò)的吧!并且更重要的是,以后我們?cè)陧?xiàng)目的任何地方都可以輕松加入雙向滑動(dòng)菜單功能,只需要以下兩步即可:

1. 在Acitivty的layout中引入我們自定義的BidirSlidingLayout布局,并且給這個(gè)布局要加入三個(gè)直接子元素。

2. 在Activity中通過(guò)setScrollEvent方法,給一個(gè)View注冊(cè)touch事件。

如此一來(lái),一分鐘實(shí)現(xiàn)雙向滑動(dòng)菜單功能妥妥的。

好了,今天的講解到此結(jié)束,有疑問(wèn)的朋友請(qǐng)?jiān)谙旅媪粞浴?/p>

源碼下載,請(qǐng)點(diǎn)擊這里

帶按鈕的版本下載,請(qǐng)點(diǎn)擊這里

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI