您好,登錄后才能下訂單哦!
小編給大家分享一下Android如何實現3D推拉門式滑動菜單,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
實現
??我們先來看一下示意圖:
??下面我就來分析一下源碼。
??從效果圖中可以看到的是,滑動的時候菜單會有一個效果,這個效果是沿y軸旋轉的效果,這種效果是用Matrix和Camera來實現,具體怎么實現的我在另一篇文章《對Matrix中preTranslate()和postTranslate()的理解》中做了簡單的說明,可以很容易的實現這樣的效果。
??在Image3DView中,我們封裝了這樣的效果,只要傳入左側菜單界面的View,然后就可以實現了。
??先來看一下布局文件:
<com.example.sliding3dlayout.Sliding3DLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/slidingLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:layout_height="fill_parent" android:layout_width="240dp" android:background="#333333" android:visibility="invisible" > <LinearLayout android:layout_centerInParent="true" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="50dp" android:text="登錄" android:gravity="center" android:textColor="#ffffff" /> <TextView android:layout_width="fill_parent" android:layout_height="50dp" android:text="注冊" android:gravity="center" android:textColor="#ffffff" /> <TextView android:layout_width="fill_parent" android:layout_height="50dp" android:text="退出" android:gravity="center" android:textColor="#ffffff" /> </LinearLayout> </RelativeLayout> <LinearLayout android:id="@+id/content" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentRight="true" android:background="#ffffff" android:orientation="vertical"> <Button android:id="@+id/menuButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Menu" /> <ListView android:id="@+id/contentList" android:layout_width="fill_parent" android:layout_height="fill_parent" android:cacheColorHint="#00000000" > </ListView> </LinearLayout> </com.example.sliding3dlayout.Sliding3DLayout>
??Sliding3DLayout類是定義的該菜單控件,里面有兩個主要的視圖,第一個是菜單視圖,第二個就是主界面視圖。當滑動的時候,我們把左側的菜單視圖隱藏,然后顯示Image3DView控件,也就是沿y軸旋轉,根據滑動的距離,旋轉的角度在不斷變化,Image3DView的視圖也在不斷的變化,當菜單完全顯示的時候,就顯示左側菜單的界面,然后將Image3DView隱藏,這樣就實現了所謂的滑動動畫。
public class Sliding3DLayout extends RelativeLayout implements OnTouchListener{ //滾動顯示和隱藏左側布局時,手指滑動需要達到的速度。 public static final int SNAP_VELOCITY = 200; //滑動狀態的一種,表示未進行任何滑動。 public static final int DO_NOTHING = 0; //滑動狀態的一種,表示正在滑出左側菜單。 public static final int SHOW_MENU = 1; //滑動狀態的一種,表示正在隱藏左側菜單。 public static final int HIDE_MENU = 2; //記錄當前的滑動狀態 private int slideState; //屏幕寬度值。 private int screenWidth; //右側布局最多可以滑動到的左邊緣。 private int leftEdge = 0; //右側布局最多可以滑動到的右邊緣。 private int rightEdge = 0; //在被判定為滾動之前用戶手指可以移動的最大值。 private int touchSlop; //記錄手指按下時的橫坐標。 private float xDown; //記錄手指按下時的縱坐標。 private float yDown; //記錄手指移動時的橫坐標。 private float xMove; //記錄手指移動時的縱坐標。 private float yMove; //記錄手機抬起時的橫坐標。 private float xUp; //左側布局當前是顯示還是隱藏。只有完全顯示或隱藏時才會更改此值,滑動過程中此值無效。 private boolean isLeftLayoutVisible; //是否正在滑動。 private boolean isSliding; //是否已加載過一次layout,這里onLayout中的初始化只需加載一次 private boolean loadOnce; //左側布局對象。 private View leftLayout; //右側布局對象。 private View rightLayout; //在滑動過程中展示的3D視圖 private Image3DView image3dView; //用于監聽側滑事件的View。 private View mBindView; //左側布局的參數,通過此參數來重新確定左側布局的寬度,以及更改leftMargin的值。 private MarginLayoutParams leftLayoutParams; //右側布局的參數,通過此參數來重新確定右側布局的寬度。 private MarginLayoutParams rightLayoutParams; //3D視圖的參數,通過此參數來重新確定3D視圖的寬度。 private ViewGroup.LayoutParams image3dViewParams; //用于計算手指滑動的速度。 private VelocityTracker mVelocityTracker; public Sliding3DLayout(Context context, AttributeSet attrs){ super(context, attrs); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); screenWidth = wm.getDefaultDisplay().getWidth(); touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } public Sliding3DLayout(Context context){ this(context,null); } /** * 左側布局是否完全顯示出來,或完全隱藏,滑動過程中此值無效。 * @return 左側布局完全顯示返回true,完全隱藏返回false。 */ public boolean isLeftLayoutVisible(){ return isLeftLayoutVisible; } /** * 綁定監聽側滑事件的View,即在綁定的View進行滑動才可以顯示和隱藏左側布局。 * @param v * 需要綁定的View對象。 */ public void setScrollEvent(View v){ mBindView = v; mBindView.setOnTouchListener(this); } @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: // 手指移動時,對比按下時的橫坐標,計算出移動的距離,來調整右側布局的leftMargin值,從而顯示和隱藏左側布局 xMove = event.getRawX(); yMove = event.getRawY(); int moveDistanceX = (int)(xMove - xDown); int moveDistanceY = (int)(yMove - yDown); checkSlideState(moveDistanceX, moveDistanceY); switch(slideState){ case SHOW_MENU: rightLayoutParams.rightMargin = -moveDistanceX; onSlide(); break; case HIDE_MENU: rightLayoutParams.rightMargin = rightEdge - moveDistanceX; onSlide(); break; default: break; } break; case MotionEvent.ACTION_UP: xUp = event.getRawX(); int upDistanceX = (int)(xUp - xDown); if(isSliding){ switch (slideState){ case SHOW_MENU: if(shouldScrollToLeftLayout()){ scrollToLeftLayout(); }else{ scrollToRightLayout(); } break; case HIDE_MENU: if(shouldScrollToRightLayout()){ scrollToRightLayout(); }else{ scrollToLeftLayout(); } break; } }else if (upDistanceX < touchSlop && isLeftLayoutVisible){ scrollToRightLayout(); } recycleVelocityTracker(); break; } if (v.isEnabled()){ if (isSliding){ unFocusBindView(); return true; } if (isLeftLayoutVisible) { return true; } return false; } return true; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if(changed&&!loadOnce){ //獲取左側菜單布局 leftLayout = getChildAt(0); leftLayoutParams = (MarginLayoutParams)leftLayout.getLayoutParams(); rightEdge = -leftLayoutParams.width; //獲取右側布局 rightLayout = getChildAt(1); rightLayoutParams = (MarginLayoutParams)rightLayout.getLayoutParams(); rightLayoutParams.width = screenWidth; rightLayout.setLayoutParams(rightLayoutParams); image3dView = new Image3DView(getContext()); /*ViewGroup.LayoutParams params = new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);*/ image3dView.setVisibility(INVISIBLE); addView(image3dView); // 將左側布局傳入3D視圖中作為生成源 image3dView.setSourceView(leftLayout); loadOnce = true; } } /** * 回收VelocityTracker對象。 */ private void recycleVelocityTracker() { mVelocityTracker.recycle(); mVelocityTracker = null; } /** * 將屏幕滾動到左側布局界面,滾動速度設定為10. */ public void scrollToLeftLayout(){ image3dView.clearSourceBitmap(); new ScrollTask().execute(-10); } /** * 將屏幕滾動到右側布局界面,滾動速度設定為-10. */ public void scrollToRightLayout(){ image3dView.clearSourceBitmap(); new ScrollTask().execute(10); } /** * 獲取手指在右側布局的監聽View上的滑動速度。 * * @return 滑動速度,以每秒鐘移動了多少像素值為單位。 */ private int getScrollVelocity() { mVelocityTracker.computeCurrentVelocity(1000); int velocity = (int) mVelocityTracker.getXVelocity(); return Math.abs(velocity); } /** * 判斷是否應該滾動將左側布局展示出來。如果手指移動距離大于屏幕的1/2,或者手指移動速度大于SNAP_VELOCITY, * 就認為應該滾動將左側布局展示出來。 * * @return 如果應該滾動將左側布局展示出來返回true,否則返回false。 */ private boolean shouldScrollToLeftLayout() { return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY; } /** * 判斷是否應該滾動將右側布局展示出來。如果手指移動距離加上leftLayoutPadding大于屏幕的1/2, * 或者手指移動速度大于SNAP_VELOCITY, 就認為應該滾動將右側布局展示出來。 * * @return 如果應該滾動將右側布局展示出來返回true,否則返回false。 */ private boolean shouldScrollToRightLayout(){ return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY; } /** * 執行滑動過程中的邏輯操作,如邊界檢查,改變偏移值,可見性檢查等。 */ private void onSlide(){ checkSlideBorder(); rightLayout.setLayoutParams(rightLayoutParams); image3dView.clearSourceBitmap(); image3dViewParams = image3dView.getLayoutParams(); image3dViewParams.width = -rightLayoutParams.rightMargin; //滑動的同時改變3D視圖的大小 image3dView.setLayoutParams(image3dViewParams); showImage3dView(); } public void toggle(){ if(isLeftLayoutVisible()) scrollToRightLayout(); else scrollToLeftLayout(); } /** * 保證此時讓左側布局不可見,3D視圖可見,從而讓滑動過程中產生3D的效果。 */ private void showImage3dView() { if (image3dView.getVisibility() != View.VISIBLE) { image3dView.setVisibility(View.VISIBLE); } if (leftLayout.getVisibility() != View.INVISIBLE) { leftLayout.setVisibility(View.INVISIBLE); } } /** * 在滑動過程中檢查左側菜單的邊界值,防止綁定布局滑出屏幕。 */ private void checkSlideBorder(){ if (rightLayoutParams.rightMargin > leftEdge){ rightLayoutParams.rightMargin = leftEdge; } else if (rightLayoutParams.rightMargin < rightEdge) { rightLayoutParams.rightMargin = rightEdge; } } /** * 根據手指移動的距離,判斷當前用戶的滑動意圖,然后給slideState賦值成相應的滑動狀態值。 * * @param moveDistanceX * 橫向移動的距離 * @param moveDistanceY * 縱向移動的距離 */ private void checkSlideState(int moveDistanceX, int moveDistanceY) { if (isLeftLayoutVisible) { //如果是向左滑動,則是想隱藏菜單 if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX < 0) { isSliding = true; slideState = HIDE_MENU; } }//向右滑動則是顯示菜單 else if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX > 0 && Math.abs(moveDistanceY) < touchSlop) { isSliding = true; slideState = SHOW_MENU; } } /** * 創建VelocityTracker對象,并將觸摸事件加入到VelocityTracker當中。 * * @param event * 右側布局監聽控件的滑動事件 */ private void createVelocityTracker(MotionEvent event) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } class ScrollTask extends AsyncTask<Integer, Integer, Integer>{ @Override protected Integer doInBackground(Integer... speed){ int rightMargin = rightLayoutParams.rightMargin; // 根據傳入的速度來滾動界面,當滾動到達左邊界或右邊界時,跳出循環。 while(true){ rightMargin+=speed[0]; if (rightMargin < rightEdge) { rightMargin = rightEdge; break; } if (rightMargin > leftEdge) { rightMargin = leftEdge; break; } publishProgress(rightMargin); // 為了要有滾動效果產生,每次循環使線程睡眠5毫秒,這樣肉眼才能夠看到滾動動畫。 sleep(5); } if (speed[0] > 0){ isLeftLayoutVisible = false; } else { isLeftLayoutVisible = true; } isSliding = false; return rightMargin; } @Override protected void onProgressUpdate(Integer... rightMargin) { rightLayoutParams.rightMargin = rightMargin[0]; rightLayout.setLayoutParams(rightLayoutParams); image3dViewParams = image3dView.getLayoutParams(); image3dViewParams.width = -rightLayoutParams.rightMargin; image3dView.setLayoutParams(image3dViewParams); showImage3dView(); unFocusBindView(); } @Override protected void onPostExecute(Integer rightMargin){ rightLayoutParams.rightMargin = rightMargin; rightLayout.setLayoutParams(rightLayoutParams); image3dView.setVisibility(INVISIBLE); if (isLeftLayoutVisible){ leftLayout.setVisibility(View.VISIBLE); } } } /** * 使用可以獲得焦點的控件在滑動的時候失去焦點。 */ private void unFocusBindView() { if (mBindView != null) { mBindView.setPressed(false); mBindView.setFocusable(false); mBindView.setFocusableInTouchMode(false); } } /** * 使當前線程睡眠指定的毫秒數。 * * @param millis * 指定當前線程睡眠多久,以毫秒為單位 */ private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
??在Sliding3DLayout中傳入了一個View,這個View是效果圖中的ListView,為什么要傳入這個View呢?因為我們要監測滑動,也就是在ListView的滑動,然后根據這個滑動來判斷是否要顯示菜單,但是這樣實際出現了問題,我們稍后再說這個問題。
??在Sliding3DLayout中總共有3個View對象,一個是左側的菜單View,一個是主界面的View,最后一個就是Image3DView,在onLayout方法里面我們要得到這三個對象,前兩個我們可以在xml布局文件里面得到,因為在Sliding3DLayout里面我們寫了,而Image3DView沒有寫,所以要生成一個對象,然后調用addView方法加入到Sliding3DLayout里面。接下來我們需要得到的就是MarginLayoutParams對象,包括主界面View的和Image3DView對象的MarginLayoutParams。為什么需要MarginLayoutParams對象,因為得到一個View的MarginLayoutParams對象,就可以設置rightMargin屬性的值,這個值是View距離右邊的距離,如果把該值設置成負數的話,拿主界面來說,rightLayout.setLayoutParams(rightLayoutParams);調用這個方法,主界面就會向右偏移一定的距離,從而實現主界面隨手指向右滑動而滑動,從而實現動畫的連續性。
??在實現的時候,用到了一個我沒見過的類VelocityTracker,郭神說這個類是用來計算手指滑動的速度,具體該怎么使用,我將在下一篇文章中進行說明。
??之前提到的問題,就是設置滑動監聽的View,如果該View不是ListView而是ImageView,TextView,LinearLayout,那么向右滑動的時候就會出現無法滑動的問題。
以上是“Android如何實現3D推拉門式滑動菜單”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。