国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android實現3D推拉門式滑動菜單源碼解析

2019-10-22 18:21:51
字體:
來源:轉載
供稿:網友

前言

  又看了郭霖大神的一篇博客《Android 3D滑動菜單完全解析,實現推拉門式的立體特效》,是關于自定義控件方面的,因為自己關于自定義控件了解的不過,以前的要求是會用就行,但是后來越發的明白只會用是不夠的,出現問題都不知道該怎么分析,所以我才打算把別人博客里的自定義控件的源碼給看懂,雖然可能時間花的時間長,但是,絕對是值得的!
  因為源碼的東西比較多,看完之后發現還存在可以優化的地方,郭神的代碼當時是為了例子講解,所以對這個控件類的封裝就沒有仔細去做,所以我就進行了封裝和優化,是的移植到項目的時候會更加方便,解耦性更強。

實現

  我們先來看一下示意圖:

Android推拉門式滑動菜單,Android,3D滑動菜單,Android滑動菜單

  下面我就來分析一下源碼。

  從效果圖中可以看到的是,滑動的時候菜單會有一個效果,這個效果是沿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,那么向右滑動的時候就會出現無法滑動的問題,大家可以自己試一下,我也沒找到解決的方法,所以如果大家找到了解決方法,希望能和我交流一下。

小結

  終于把源碼看完了,還是佩服郭神的實力,代碼確實很驚艷,而且包括了很多的東西,自己看完并且弄懂之后對自己也是一種提高。希望看源碼之路能越走越遠!

源碼下載,點這里

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 曲阜市| 临夏县| 蓬莱市| 于田县| 邳州市| 宁化县| 祥云县| 察哈| 乌兰察布市| 莆田市| 荣昌县| 原阳县| 西乌珠穆沁旗| 拜泉县| 望江县| 兰坪| 潍坊市| 萨嘎县| 霸州市| 平昌县| 霍州市| 吉首市| 聂拉木县| 淮南市| 太康县| 洛南县| 巴彦县| 翼城县| 沙田区| 泽普县| 河津市| 河东区| 峨山| 花垣县| 疏附县| 镇江市| 绵竹市| 丹凤县| 三江| 五华县| 革吉县|