Android launcher 桌面抽屉切换动画

泡在网上的日子 / 文 发表于2014-11-30 11:12 次阅读 launcher,动画

转载自:WPJY Android launcher 桌面抽屉切换动画

一、概述

   桌面抽屉之间的切换时Android用户经常触发的行为,好的交互会给用户一个舒适的体验。百度桌面默认是随机切换不同的动画,Android默认是一个大小和透明的渐变的动画,如下:



下面开始分析在Launcher2(KitKat)的源码里面是如何实现这种效果的。


二、下面列举相关的方法和变量
4082:
interface LauncherTransitionable {
    View getContent();
    void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
    void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
    void onLauncherTransitionStep(Launcher l, float t);
    void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
}
2948:
exitSpringLoadedDragMode()
2926:
exitSpringLoadedDragModeDelayed()
2918:
enterSpringLoadedDragMode()
2899:
showAllApps()
2864:
showWorkspace()
2860:
showWorkspace()
2740:
hideAppsCustomizeHelper()
2573:
showAppsCustomizeHelper()
2498:
dispatchOnLauncherTransitionPrepare()
2097:
onClickAllAppsButton()
2048:
onTouch()
2008:
onClick()
1976:
onBackPressed()
1456:
onNewIntent
1273:
mReceiver
749:
onResume()
三、分析

 首先从最直观的方式开始,就是Dock栏进入抽屉的按钮。点击它会从桌面到抽屉,进入抽屉后再按返回键会从抽屉到桌面。这个按钮在Launcher类中对应的变量是mAllAppsButton,因为Launcher类继承了View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,View.OnTouchListener这几个接口,所以点击事情需要由onClick(View v)方法来处理。在Launcher类的onClick(View v)方法中,
/**
     * Launches the intent referred by the clicked shortcut.
     *
     * @param v The view representing the clicked shortcut.
     */
    public void onClick(View v) {
        // Make sure that rogue clicks don't get through while allapps is launching, or after the
        // view has detached (it's possible for this to happen if the view is removed mid touch).
        if (v.getWindowToken() == null) {
            return;
        }
        if (!mWorkspace .isFinishedSwitchingState()) {
            return;
        }
        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds( new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
            boolean success = startActivitySafely(v, intent, tag);
            if (success && v instanceof BubbleTextView) {
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton ) {
            if (isAllAppsVisible()) {
                showWorkspace( true);
            } else {
                onClickAllAppsButton(v);
            }
        }
    }

 从标注地方可以看出,首先对View进行一个判断,如果是mAllAppsButton则开始下面的判断。如果是在抽屉里面,则进入到桌面;如果不是抽屉,则调用onClickAllAppsButton(v)方法。而onClickAllAppsButton(v)方法就是调用showAllApps方法,顾名思义就是进入后抽屉显示所有的app。接着在抽屉里面,如果要返回桌面,按Back键的话会调用onKeyDown或者onBackPressed()方法。
@Override
   public boolean onKeyDown( int keyCode, KeyEvent event) {
       final int uniChar = event.getUnicodeChar();
       final boolean handled = super.onKeyDown(keyCode, event);
       final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
       if (!handled && acceptFilter() && isKeyNotWhitespace) {
           boolean gotKey = TextKeyListener.getInstance().onKeyDown( mWorkspace, mDefaultKeySsb,
                   keyCode, event);
           if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
               // something usable has been typed - start a search
               // the typed text will be retrieved and cleared by
               // showSearchDialog()
               // If there are multiple keystrokes before the search dialog takes focus,
               // onSearchRequested() will be called for every keystroke,
               // but it is idempotent , so it's fine.
               return onSearchRequested();
           }
       }
       // Eat the long press event so the keyboard doesn't come up.
       if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
           return true ;
       }
       return handled;
   }

可以看到在onKeyDown方法中,没有任何关于进出抽屉或者桌面的方法。那么再来看一下onBackPressed()方法:

@Override
  public void onBackPressed() {
      if (isAllAppsVisible()) {
          showWorkspace( true);
      } else if (mWorkspace .getOpenFolder() != null) {
          Folder openFolder = mWorkspace.getOpenFolder();
          if (openFolder.isEditingName()) {
              openFolder.dismissEditingName();
          } else {
              closeFolder();
          }
      } else {
          mWorkspace.exitWidgetResizeMode();
          // Back button is a no-op here, but give at least some feedback for the button press
          mWorkspace.showOutlinesTemporarily();
      }
  }

发现在这里处理了切换的过程。现在可以确定是显示桌面调用的是showWorkspace()方法,进入抽屉调用的是showAllApps()方法。在这两个方法中,又各自调用不同的方法实现各自的逻辑。

 那么这两个方法都是在上面情况下调用的呢?先看showAllApps()方法。
launcher.java类:
1、onResume()
2、onClickAllAppsButton

 再看一下showWorkspace()的情况,
1、onResume()
2、mReceiver()
3、onNewIntent()
4、startSearch()
5、startWallpaper()
6、onBackPressed()
7、onClick()
8、onTouch()
9、showWorkspace()
10、exitSpringLoadedDragModeDelayed()
 可以发现showAllApps()方法只有被调用两次,而showWorkspace()有十次之多。这说明返回桌面的情形比返回抽屉的情况要多很多,而实际的使用情况也确实是这样的。
 接着分别看一下showAllApps()和showWorkspace()各自的具体实现。
void showAllApps( boolean animated) {
       if (mState != State.WORKSPACE) return;
       showAppsCustomizeHelper(animated, false);
       mAppsCustomizeTabHost.requestFocus();
       // Change the state *after* we've called all the transition code
       mState = State. APPS_CUSTOMIZE;
       // Pause the auto-advance of widgets until we are out of AllApps
       mUserPresent = false ;
       updateRunning();
       closeFolder();
       // Send an accessibility event to announce the context change
       getWindow().getDecorView()
               .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
   }

 逐行分析下,第一行是一个判断,如果当前的状态不是在桌面,那么就退出这个方法。第二行代码就是进入抽屉的动画方法。

第三行代码是给抽屉界面焦点。第四行代码进入抽屉后更改当前的状态。之后就不细说了。重点还是showAppsCustomizeHelper(animated, false);这个方法,它就是实现进入抽屉动画的方法。来到这个方法,发现其上面有很长的注释:
/**
    * Things to test when changing the following seven functions.
    *   - Home from workspace
    *          - from center screen
    *          - from other screens
    *   - Home from all apps
    *          - from center screen
    *          - from other screens
    *   - Back from all apps
    *          - from center screen
    *          - from other screens
    *   - Launch app from workspace and quit
    *          - with back
    *          - with home
    *   - Launch app from all apps and quit
    *          - with back
    *          - with home
    *   - Go to a screen that's not the default, then all
    *     apps, and launch and app, and go back
    *          - with back
    *          -with home
    *   - On workspace, long press power and go back
    *          - with back
    *          - with home
    *   - On all apps, long press power and go back
    *          - with back
    *          - with home
    *   - On workspace, power off
    *   - On all apps, power off
    *   - Launch an app and turn off the screen while in that app
    *          - Go back with home key
    *          - Go back with back key  TODO: make this not go to workspace
    *          - From all apps
    *          - From workspace
    *   - Enter and exit car mode (becuase it causes an extra configuration changed)
    *          - From all apps
    *          - From the center workspace
    *          - From another workspace
    */

注释下面是showAppsCustomizeHelper()和hideAppsCustomizeHelper()两个方法,顾名思义hideAppsCustomizeHelper就是离开抽屉的动画实现方法,这两个方法是相对立的。

/**
     * Zoom the camera out from the workspace to reveal 'toView'.
     * Assumes that the view to show is anchored at either the very top or very bottom
     * of the screen.
     */
    private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
        if (mStateAnimation != null) {
            mStateAnimation.setDuration(0);
            mStateAnimation.cancel();
            mStateAnimation = null ;
        }
        final Resources res = getResources();
        final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime );
        final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime );
        final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor );
        final View fromView = mWorkspace ;
        final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
        final int startDelay =
                res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger );
        setPivotsForZoom(toView, scale);
        // Shrink workspaces away if going to AppsCustomize from workspace
        Animator workspaceAnim =
                mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated);
        if (animated) {
            toView.setScaleX(scale);
            toView.setScaleY(scale);
            final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView);
            scaleAnim.
                scaleX(1f).scaleY(1f).
                setDuration(duration).
                setInterpolator( new Workspace.ZoomOutInterpolator());
            toView.setVisibility(View. VISIBLE);
            toView.setAlpha(0f);
            final ObjectAnimator alphaAnim = LauncherAnimUtils
                . ofFloat(toView, "alpha", 0f, 1f)
                .setDuration(fadeDuration);
            alphaAnim.setInterpolator( new DecelerateInterpolator(1.5f));
            alphaAnim.addUpdateListener( new AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    if (animation == null) {
                        throw new RuntimeException("animation is null" );
                    }
                    float t = (Float) animation.getAnimatedValue();
                    dispatchOnLauncherTransitionStep(fromView, t);
                    dispatchOnLauncherTransitionStep(toView, t);
                }
            });
            // toView should appear right at the end of the workspace shrink
            // animation
            mStateAnimation = LauncherAnimUtils.createAnimatorSet();
            mStateAnimation.play(scaleAnim).after(startDelay);
            mStateAnimation.play(alphaAnim).after(startDelay);
            mStateAnimation.addListener(new AnimatorListenerAdapter() {
                boolean animationCancelled = false;
                @Override
                public void onAnimationStart(Animator animation) {
                    updateWallpaperVisibility( true);
                    // Prepare the position
                    toView.setTranslationX(0.0f);
                    toView.setTranslationY(0.0f);
                    toView.setVisibility(View. VISIBLE);
                    toView.bringToFront();
                }
                @Override
                public void onAnimationEnd(Animator animation) {
                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
                    dispatchOnLauncherTransitionEnd(toView, animated, false);
                    if (mWorkspace != null && !springLoaded && !LauncherApplication.isScreenLarge()) {
                        // Hide the workspace scrollbar
                        mWorkspace.hideScrollingIndicator(true);
                        hideDockDivider();
                    }
                    if (!animationCancelled ) {
                        updateWallpaperVisibility( false);
                    }
                    // Hide the search bar
                    if (mSearchDropTargetBar != null) {
                        mSearchDropTargetBar.hideSearchBar(false);
                    }
                }
                @Override
                public void onAnimationCancel(Animator animation) {
                    animationCancelled = true ;
                }
            });
            if (workspaceAnim != null) {
                mStateAnimation.play(workspaceAnim);
            }
            boolean delayAnim = false;
            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
            dispatchOnLauncherTransitionPrepare(toView, animated, false);
            // If any of the objects being animated haven't been measured/laid out
            // yet, delay the animation until we get a layout pass
            if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) ||
                    ( mWorkspace.getMeasuredWidth() == 0) ||
                    (toView.getMeasuredWidth() == 0)) {
                delayAnim = true;
            }
            final AnimatorSet stateAnimation = mStateAnimation;
            final Runnable startAnimRunnable = new Runnable() {
                public void run() {
                    // Check that mStateAnimation hasn't changed while
                    // we waited for a layout/draw pass
                    if (mStateAnimation != stateAnimation)
                        return;
                    setPivotsForZoom(toView, scale);
                    dispatchOnLauncherTransitionStart(fromView, animated, false);
                    dispatchOnLauncherTransitionStart(toView, animated, false);
                    LauncherAnimUtils.startAnimationAfterNextDraw( mStateAnimation, toView);
                }
            };
            if (delayAnim) {
                final ViewTreeObserver observer = toView.getViewTreeObserver();
                observer.addOnGlobalLayoutListener( new OnGlobalLayoutListener() {
                        public void onGlobalLayout() {
                            startAnimRunnable.run();
                            toView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        }
                    });
            } else {
                startAnimRunnable.run();
            }
        } else {
            toView.setTranslationX(0.0f);
            toView.setTranslationY(0.0f);
            toView.setScaleX(1.0f);
            toView.setScaleY(1.0f);
            toView.setVisibility(View. VISIBLE);
            toView.bringToFront();
            if (!springLoaded && !LauncherApplication.isScreenLarge()) {
                // Hide the workspace scrollbar
                mWorkspace.hideScrollingIndicator(true);
                hideDockDivider();
                // Hide the search bar
                if (mSearchDropTargetBar != null) {
                    mSearchDropTargetBar.hideSearchBar(false);
                }
            }
            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
            dispatchOnLauncherTransitionStart(fromView, animated, false);
            dispatchOnLauncherTransitionEnd(fromView, animated, false);
            dispatchOnLauncherTransitionPrepare(toView, animated, false);
            dispatchOnLauncherTransitionStart(toView, animated, false);
            dispatchOnLauncherTransitionEnd(toView, animated, false);
            updateWallpaperVisibility( false);
        }
    }

变量mStateAnimation的类型是AnimatorSet,是专门负责上述两个方法里面的动画执行。变量duration、fadeDuration、scale分别是进入抽屉动画的伸缩的时间、透明度改变的时间以及伸缩的大小。从下面两行代码可以看出

final View fromView = mWorkspace;
final AppsCustomizeTabHost toView = mAppsCustomizeTabHost ;

fromView就是桌面,而toView就是抽屉。startDelay是动画之前的准备时间。
setPivotsForZoom(toView, scale);方法是对View进行一个缩放,scale是缩放的参数。
workspaceAnim是桌面消失的动画,先不去看具体实现。如果有动画需求进入if判断,在这里有一个scaleAnim和alphaAnim,这就是抽屉出现的动画,从代码

mStateAnimation = LauncherAnimUtils.createAnimatorSet();
mStateAnimation.play(scaleAnim).after(startDelay);
mStateAnimation.play(alphaAnim).after(startDelay);

可以看出二者同时执行。接着是mStateAnimation动画的回调接口,具体逻辑不再分析。然后如果workspaceAnim不为空的话,就执行说明消失的动画。再看delayAnim变量,这是用来判断是否需要延迟动画执行。如果需要的话就监听View树是绘制,绘制完毕之后再执行动画;否则执行进入抽屉的动画。还有一个重要的地方是在在上面的方法中出现了

dispatchOnLauncherTransitionPrepare(fromView, animated, false);
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionEnd(fromView, animated, false);

等方法。其实这是在Launcher类中定义的接口里面的方法,具体如下:

interface LauncherTransitionable {
     View getContent();
      void onLauncherTransitionPrepare(Launcher l, boolean animated,
               boolean toWorkspace);
      void onLauncherTransitionStart(Launcher l, boolean animated,
               boolean toWorkspace);
      void onLauncherTransitionStep(Launcher l, float t);
      void onLauncherTransitionEnd(Launcher l, boolean animated,
               boolean toWorkspace);
}
 分别在Workspace、AppsCustomizePagedView、AppsCustomizeTabHost、PagedView中继承这个接口。最后回过头看一下桌面消失动画的实现,它是在Workspace类里面处理的。
Animator getChangeStateAnimation (final SizeState state, boolean animated, int delay) {
   Log.i(TAG, "getChangeStateAnimation");
                                                  
      if (mSizeState == state) {
          return null ;
      }
      // Initialize animation arrays for the first time if necessary
      initAnimationArrays();
      AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
      // Stop any scrolling, move to the current page right away
      setCurrentPage(getNextPage());
      final boolean isEditViewMode = isEditViewMode(state);
                                                    
      final SizeState oldState = mSizeState ;
      final boolean oldStateIsNormal = (oldState == SizeState.NORMAL);
      final boolean oldStateIsSpringLoaded = (oldState == SizeState.SPRING_LOADED );
      final boolean oldStateIsSmall = (oldState == SizeState.SMALL);
      mSizeState = state;
      final boolean stateIsNormal = (state == SizeState.NORMAL);
      final boolean stateIsSpringLoaded = (state == SizeState.SPRING_LOADED );
      final boolean stateIsSmall = (state == SizeState.SMALL);
      float finalScaleFactor = 1.0f;
      float finalBackgroundAlpha = (stateIsSpringLoaded || isEditViewMode) ? 1.0f: 0f;
      float translationX = 0;
      float translationY = 0;
      boolean zoomIn = true;
      if (state != SizeState.NORMAL) {
             if (isEditViewMode) {
                 finalScaleFactor = getCellLayoutScale(state);
            } else {
                 finalScaleFactor = mSpringLoadedShrinkFactor- (state == SizeState.SMALL ? 0.1f : 0);
            }
          finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0);
          setPageSpacing(mSpringLoadedPageSpacing);
          if (oldStateIsNormal && stateIsSmall) {
              zoomIn = false;
              setLayoutScale(finalScaleFactor);
              updateChildrenLayersEnabled( false);
          } else {
              finalBackgroundAlpha = 1.0f;
              setLayoutScale(finalScaleFactor);
          }
      } else {
          setPageSpacing(mOriginalPageSpacing);
          setLayoutScale(1.0f);
      }
      final int duration;
      if (isEditViewMode) {
            duration = getResources().getInteger(R.integer.config_overviewTransitionTime );
        } else if (zoomIn) {
            duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime );
        } else {
            duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime );
        }
                                                    
      for (int i = 0; i < getChildCount(); i++) {
          final CellLayout cl = (CellLayout) getChildAt(i);
          float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded ||(i == mCurrentPage)) ? 1f : 0f;                
          float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
          float initialAlpha = currentAlpha;
                                                        
          // Determine the pages alpha during the state transition
          if ((oldStateIsSmall && stateIsNormal) || (oldStateIsNormal && stateIsSmall)) {
              // To/from workspace - only show the current page unless the transition is not
              //                     animated and the animation end callback below doesn't run;
              //                     or, if we're in spring-loaded mode
              if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {      
                  finalAlpha = 1f;
              } else {
                  initialAlpha = 0f;
                  finalAlpha = 0f;
              }
          }
          mOldAlphas[i] = initialAlpha;
          mNewAlphas[i] = finalAlpha;
          if (animated) {
              mOldTranslationXs[i] = cl.getTranslationX();
              mOldTranslationYs[i] = cl.getTranslationY();
              mOldScaleXs[i] = cl.getScaleX();
              mOldScaleYs[i] = cl.getScaleY();
              mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
              mNewTranslationXs[i] = translationX;
              mNewTranslationYs[i] = translationY;
              mNewScaleXs[i] = finalScaleFactor;
              mNewScaleYs[i] = finalScaleFactor;
              mNewBackgroundAlphas[i] = finalBackgroundAlpha;
          } else {
              cl.setTranslationX(translationX);
              cl.setTranslationY(translationY);
              cl.setScaleX(finalScaleFactor);
              cl.setScaleY(finalScaleFactor);
              cl.setBackgroundAlpha(finalBackgroundAlpha);
              cl.setShortcutAndWidgetAlpha(finalAlpha);
          }
          cl.isEditViewMode(isEditViewMode);
      }
      if (animated) {
          for (int index = 0; index < getChildCount(); index++) {
              final int i = index;
              final CellLayout cl = (CellLayout) getChildAt(i);
              float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
              if (mOldAlphas [i] == 0 && mNewAlphas[i] == 0) {
                  cl.setTranslationX(mNewTranslationXs [i]);
                  cl.setTranslationY(mNewTranslationYs [i]);
                  cl.setScaleX( mNewScaleXs[i]);
                  cl.setScaleY( mNewScaleYs[i]);
                  cl.setBackgroundAlpha(mNewBackgroundAlphas [i]);
                  cl.setShortcutAndWidgetAlpha(mNewAlphas [i]);
                  cl.setRotationY( mNewRotationYs[i]);
              } else {
                  LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl);
                  a.translationX( mNewTranslationXs[i])
                      .translationY(mNewTranslationYs [i])
                      .scaleX( mNewScaleXs[i])
                      .scaleY( mNewScaleYs[i])
                      .setDuration(duration)
                      .setInterpolator(mZoomInInterpolator );
                  anim.play(a);
                  if (mOldAlphas [i] != mNewAlphas [i] || currentAlpha != mNewAlphas [i]) {
                      LauncherViewPropertyAnimator alphaAnim =
                          new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
                      alphaAnim.alpha( mNewAlphas[i])
                          .setDuration(duration)
                          .setInterpolator(mZoomInInterpolator );
                      anim.play(alphaAnim);
                  }
                  if (mOldBackgroundAlphas [i] != 0 ||
                      mNewBackgroundAlphas[i] != 0) {
                      ValueAnimator bgAnim =
                              LauncherAnimUtils.ofFloat(cl, 0f, 1f).setDuration(duration);
                      bgAnim.setInterpolator(mZoomInInterpolator );
                      bgAnim.addUpdateListener( new LauncherAnimatorUpdateListener() {
                              public void onAnimationUpdate(float a, float b) {
                                  cl.setBackgroundAlpha(
                                          a * mOldBackgroundAlphas[i] +
                                          b * mNewBackgroundAlphas[i]);
                              }
                          });
                      anim.play(bgAnim);
                  }
              }
          }
          anim.setStartDelay(delay);
      }
      if (stateIsSpringLoaded) {
          // Right now we're covered by Apps Customize
          // Show the background gradient immediately, so the gradient will
          // be showing once AppsCustomize disappears
          animateBackgroundGradient(getResources().getInteger(
                  R.integer.config_appsCustomizeSpringLoadedBgAlpha ) / 100f, false);
      } else {
          // Fade the background gradient away
          animateBackgroundGradient(0f, true);
      }
      return anim;
  }
在说明方法之前先说一下,Launcher2和Launcher3桌面消失动画的区别。Launcher2进入抽屉后是一个黑色的背景,没有壁纸显示效果,而Launcher3可以看到壁纸。所以getChangeStateAnimation 方法也是有一定区别的。关于这个方法大体可以分为三个部分,一是变量初始赋值阶段,二是动画设置阶段,三是动画返回阶段。重点是第二个动画设置阶段,因为桌面的消失、出现都会调用这个方法。同样的关于这个动画最主要的部分还是伸缩和透明度的变化,理论上应该是这样的,桌面消失时开始变小、透明度逐渐不可见;桌面出现时开始变大、透明度逐渐可见。
 不过从这个方法的实现来看,其性能消耗比较大。它是用一个for循环来遍历Workspace的CellLayout的个数,也就是有几个桌面。然后再对每个CellLayout进行一个动画效果。亲测在低端机器上比如联想s868t是非常卡顿的。
 hideAppsCustomizeHelper()的实现就不再赘述,和showAppsCustomizeHelper()实现机制都是一样的。那么最后总结一下,从桌面进入抽屉调用
hideAppsCustomizeHelper()方法,这个方法实现进入抽屉的动画效果,具体说就是对抽屉这个View进行一个动画。在这方法里面还有一个桌面消失的动画getChangeStateAnimation ,在这个动画执行完毕后才会执行进入抽屉的动画。然后从抽屉返回桌面正好是相反的流程。现在很多桌面进入抽屉都会有其他的动画效果,比如反转、上下等效果,其实就是对以上三个方法进行修改添加就可以了。


收藏 赞 (5) 踩 (0)
上一篇:Android Dagger依赖注入框架浅析
转载自 快乐de胖虎 Android Dagger依赖注入框架浅析 今天接触了Dagger这套android的依赖注入框架(DI框架),感觉跟Spring 的IOC差不多吧。这个框架它的好处是它没有采用反射技术(Spring是用反射的),而是用预编译技术,因为基于反射的DI非常地耗用资源(空
下一篇:MaterialButton 效果进阶 动画自动移动进行对齐效果
做 Android 动画效果一段时间了,感觉深深喜欢上了钻研特效。在手机上显示自己的特效是一件很不错的事情。 废话不多说,前面几天我发布了: [Material Design] 教你做一个Material风格、动画的按钮(MaterialButton) 在其中我讲解了我对 Android L 中 Materia