編輯:關於Android編程
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()
/** * 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
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是動畫之前的准備時間。
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 方法也是有一定區別的。關於這個方法大體可以分為三個部分,一是變量初始賦值階段,二是動畫設置階段,三是動畫返回階段。重點是第二個動畫設置階段,因為桌面的消失、出現都會調用這個方法。同樣的關於這個動畫最主要的部分還是伸縮和透明度的變化,理論上應該是這樣的,桌面消失時開始變小、透明度逐漸不可見;桌面出現時開始變大、透明度逐漸可見。
EditText可以通過layer-list來繪制背景: //用白色來填充裡面
Toast大家都很熟,不多說。直接上圖上代碼。 具體代碼如下:main.xml:<?xml version=1.0 enc
本文實例為大家分享了Android實現單項、多項選擇操作的相關代碼,供大家參考,具體內容如下1、單項選擇1.1.布局<?xml version=1.0 en
正文 進入正題,主要講解viewpager+fragment實現微信滑動切換頁面的功能,並且附帶切換效果,功能其實並不難,只是需要把知識點關聯起來1.分析用到的知識點(1