欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android實現(xiàn)3D推拉門式滑動菜單源碼解析

 更新時間:2017年11月30日 10:49:52   作者:潘建成  
這篇文章主要為大家詳細解析了Android實現(xiàn)3D推拉門式滑動菜單源碼以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

前言

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

實現(xiàn)

  我們先來看一下示意圖:

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

  從效果圖中可以看到的是,滑動的時候菜單會有一個效果,這個效果是沿y軸旋轉(zhuǎn)的效果,這種效果是用Matrix和Camera來實現(xiàn),具體怎么實現(xiàn)的我在另一篇文章《對Matrix中preTranslate()和postTranslate()的理解》中做了簡單的說明,可以很容易的實現(xiàn)這樣的效果。

  在Image3DView中,我們封裝了這樣的效果,只要傳入左側(cè)菜單界面的View,然后就可以實現(xiàn)了。

  先來看一下布局文件:

<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類是定義的該菜單控件,里面有兩個主要的視圖,第一個是菜單視圖,第二個就是主界面視圖。當滑動的時候,我們把左側(cè)的菜單視圖隱藏,然后顯示Image3DView控件,也就是沿y軸旋轉(zhuǎn),根據(jù)滑動的距離,旋轉(zhuǎn)的角度在不斷變化,Image3DView的視圖也在不斷的變化,當菜單完全顯示的時候,就顯示左側(cè)菜單的界面,然后將Image3DView隱藏,這樣就實現(xiàn)了所謂的滑動動畫。

public class Sliding3DLayout extends RelativeLayout implements OnTouchListener{

 //滾動顯示和隱藏左側(cè)布局時,手指滑動需要達到的速度。
  public static final int SNAP_VELOCITY = 200;
  //滑動狀態(tài)的一種,表示未進行任何滑動。
  public static final int DO_NOTHING = 0;
   //滑動狀態(tài)的一種,表示正在滑出左側(cè)菜單。
  public static final int SHOW_MENU = 1;
  //滑動狀態(tài)的一種,表示正在隱藏左側(cè)菜單。
  public static final int HIDE_MENU = 2;
   //記錄當前的滑動狀態(tài)
  private int slideState;
   //屏幕寬度值。
  private int screenWidth;
   //右側(cè)布局最多可以滑動到的左邊緣。
  private int leftEdge = 0;
  //右側(cè)布局最多可以滑動到的右邊緣。
  private int rightEdge = 0;
  //在被判定為滾動之前用戶手指可以移動的最大值。
  private int touchSlop;
   //記錄手指按下時的橫坐標。
  private float xDown;
   //記錄手指按下時的縱坐標。
  private float yDown;
   //記錄手指移動時的橫坐標。
  private float xMove;
  //記錄手指移動時的縱坐標。
  private float yMove;
  //記錄手機抬起時的橫坐標。
  private float xUp;
  //左側(cè)布局當前是顯示還是隱藏。只有完全顯示或隱藏時才會更改此值,滑動過程中此值無效。
  private boolean isLeftLayoutVisible;
  //是否正在滑動。
  private boolean isSliding;
  //是否已加載過一次layout,這里onLayout中的初始化只需加載一次
  private boolean loadOnce;
  //左側(cè)布局對象。
  private View leftLayout;
  //右側(cè)布局對象。
  private View rightLayout;
   //在滑動過程中展示的3D視圖
  private Image3DView image3dView;
   //用于監(jiān)聽側(cè)滑事件的View。
  private View mBindView;
  //左側(cè)布局的參數(shù),通過此參數(shù)來重新確定左側(cè)布局的寬度,以及更改leftMargin的值。
  private MarginLayoutParams leftLayoutParams;
  //右側(cè)布局的參數(shù),通過此參數(shù)來重新確定右側(cè)布局的寬度。
  private MarginLayoutParams rightLayoutParams;
  //3D視圖的參數(shù),通過此參數(shù)來重新確定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);
 }

 /**
  * 左側(cè)布局是否完全顯示出來,或完全隱藏,滑動過程中此值無效。
  * @return 左側(cè)布局完全顯示返回true,完全隱藏返回false。
  */
 public boolean isLeftLayoutVisible(){
  return isLeftLayoutVisible;
 }

 /**
  * 綁定監(jiān)聽側(cè)滑事件的View,即在綁定的View進行滑動才可以顯示和隱藏左側(cè)布局。
  * @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:
   // 手指移動時,對比按下時的橫坐標,計算出移動的距離,來調(diào)整右側(cè)布局的leftMargin值,從而顯示和隱藏左側(cè)布局
   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){
   //獲取左側(cè)菜單布局
   leftLayout = getChildAt(0);
   leftLayoutParams = (MarginLayoutParams)leftLayout.getLayoutParams();
   rightEdge = -leftLayoutParams.width;

   //獲取右側(cè)布局
   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);
   // 將左側(cè)布局傳入3D視圖中作為生成源
   image3dView.setSourceView(leftLayout);
   loadOnce = true;
  }
 }

 /**
  * 回收VelocityTracker對象。
  */
 private void recycleVelocityTracker() {
  mVelocityTracker.recycle();
  mVelocityTracker = null;
 }

 /**
  * 將屏幕滾動到左側(cè)布局界面,滾動速度設(shè)定為10.
  */
 public void scrollToLeftLayout(){
  image3dView.clearSourceBitmap();

  new ScrollTask().execute(-10);
 }

 /**
  * 將屏幕滾動到右側(cè)布局界面,滾動速度設(shè)定為-10.
  */
 public void scrollToRightLayout(){
  image3dView.clearSourceBitmap();
  new ScrollTask().execute(10);
 }

 /**
  * 獲取手指在右側(cè)布局的監(jiān)聽View上的滑動速度。
  * 
  * @return 滑動速度,以每秒鐘移動了多少像素值為單位。
  */
 private int getScrollVelocity() {
  mVelocityTracker.computeCurrentVelocity(1000);
  int velocity = (int) mVelocityTracker.getXVelocity();
  return Math.abs(velocity);
 }

 /**
  * 判斷是否應(yīng)該滾動將左側(cè)布局展示出來。如果手指移動距離大于屏幕的1/2,或者手指移動速度大于SNAP_VELOCITY,
  * 就認為應(yīng)該滾動將左側(cè)布局展示出來。
  * 
  * @return 如果應(yīng)該滾動將左側(cè)布局展示出來返回true,否則返回false。
  */
 private boolean shouldScrollToLeftLayout() {
  return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
 }

 /**
  * 判斷是否應(yīng)該滾動將右側(cè)布局展示出來。如果手指移動距離加上leftLayoutPadding大于屏幕的1/2,
  * 或者手指移動速度大于SNAP_VELOCITY, 就認為應(yīng)該滾動將右側(cè)布局展示出來。
  * 
  * @return 如果應(yīng)該滾動將右側(cè)布局展示出來返回true,否則返回false。
  */
 private boolean shouldScrollToRightLayout(){
  return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
 }
 /**
  * 執(zhí)行滑動過程中的邏輯操作,如邊界檢查,改變偏移值,可見性檢查等。
  */
 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();
 }

 /**
  * 保證此時讓左側(cè)布局不可見,3D視圖可見,從而讓滑動過程中產(chǎn)生3D的效果。
  */
 private void showImage3dView() {
  if (image3dView.getVisibility() != View.VISIBLE) {
   image3dView.setVisibility(View.VISIBLE);
  }
  if (leftLayout.getVisibility() != View.INVISIBLE) {
   leftLayout.setVisibility(View.INVISIBLE);
  }
 }

 /**
  * 在滑動過程中檢查左側(cè)菜單的邊界值,防止綁定布局滑出屏幕。
  */
 private void checkSlideBorder(){
  if (rightLayoutParams.rightMargin > leftEdge){
   rightLayoutParams.rightMargin = leftEdge;
  } else if (rightLayoutParams.rightMargin < rightEdge) {
   rightLayoutParams.rightMargin = rightEdge;
  }
 }

 /**
  * 根據(jù)手指移動的距離,判斷當前用戶的滑動意圖,然后給slideState賦值成相應(yīng)的滑動狀態(tài)值。
  * 
  * @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;
  }
 }

 /**
  * 創(chuàng)建VelocityTracker對象,并將觸摸事件加入到VelocityTracker當中。
  * 
  * @param event
  *   右側(cè)布局監(jiān)聽控件的滑動事件
  */
 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;
   // 根據(jù)傳入的速度來滾動界面,當滾動到達左邊界或右邊界時,跳出循環(huán)。
   while(true){
    rightMargin+=speed[0];
    if (rightMargin < rightEdge) {
     rightMargin = rightEdge;
     break;
    }
    if (rightMargin > leftEdge) {
     rightMargin = leftEdge;
     break;
    }
    publishProgress(rightMargin);
    // 為了要有滾動效果產(chǎn)生,每次循環(huán)使線程睡眠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);
  }
 }

 /**
  * 使當前線程睡眠指定的毫秒數(shù)。
  * 
  * @param millis
  *   指定當前線程睡眠多久,以毫秒為單位
  */
 private void sleep(long millis) {
  try {
   Thread.sleep(millis);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }
}

  在Sliding3DLayout中傳入了一個View,這個View是效果圖中的ListView,為什么要傳入這個View呢?因為我們要監(jiān)測滑動,也就是在ListView的滑動,然后根據(jù)這個滑動來判斷是否要顯示菜單,但是這樣實際出現(xiàn)了問題,我們稍后再說這個問題。

  在Sliding3DLayout中總共有3個View對象,一個是左側(cè)的菜單View,一個是主界面的View,最后一個就是Image3DView,在onLayout方法里面我們要得到這三個對象,前兩個我們可以在xml布局文件里面得到,因為在Sliding3DLayout里面我們寫了,而Image3DView沒有寫,所以要生成一個對象,然后調(diào)用addView方法加入到Sliding3DLayout里面。接下來我們需要得到的就是MarginLayoutParams對象,包括主界面View的和Image3DView對象的MarginLayoutParams。為什么需要MarginLayoutParams對象,因為得到一個View的MarginLayoutParams對象,就可以設(shè)置rightMargin屬性的值,這個值是View距離右邊的距離,如果把該值設(shè)置成負數(shù)的話,拿主界面來說,rightLayout.setLayoutParams(rightLayoutParams);調(diào)用這個方法,主界面就會向右偏移一定的距離,從而實現(xiàn)主界面隨手指向右滑動而滑動,從而實現(xiàn)動畫的連續(xù)性。

  在實現(xiàn)的時候,用到了一個我沒見過的類VelocityTracker,郭神說這個類是用來計算手指滑動的速度,具體該怎么使用,我將在下一篇文章中進行說明。

  之前提到的問題,就是設(shè)置滑動監(jiān)聽的View,如果該View不是ListView而是ImageView,TextView,LinearLayout,那么向右滑動的時候就會出現(xiàn)無法滑動的問題,大家可以自己試一下,我也沒找到解決的方法,所以如果大家找到了解決方法,希望能和我交流一下。

小結(jié)

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

源碼下載,點這里。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論