android 按home键闪桌面背景,Android launcher 桌面抽屉切换动画

本文围绕Android桌面与抽屉切换动画展开,分析了Launcher2(KitKat)源码中实现该效果的方法。确定显示桌面调用showWorkspace()方法,进入抽屉调用showAllApps()方法,还介绍了这两个方法的调用情形及具体实现,指出getChangeStateAnimation方法性能消耗大,最后总结了进出抽屉的流程及动画修改思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

1d1889e49eae4af45716c74be8075d2b.gif

下面开始分析在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栏进入抽屉的按钮

409d8048119b973ccf607fde13f4cc30.gif。点击它会从桌面到抽屉,进入抽屉后再按返回键会从抽屉到桌面。这个按钮在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 ,在这个动画执行完毕后才会执行进入抽屉的动画。然后从抽屉返回桌面正好是相反的流程。现在很多桌面进入抽屉都会有其他的动画效果,比如反转、上下等效果,其实就是对以上三个方法进行修改添加就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值