編輯:關於Android編程
抽屜是用來放置安卓手機中所有需要顯示到Launcher上的(當然也可以進行過濾,將不想顯示的隱藏起來)應用和小部件,啟動應用、添加快捷方式到桌面、卸載等。之前也提到過,有些Launcher是沒有抽屜的,如MIUI的Launcher。在Launcher3中,默認是有的,當然,也提供了不顯示抽屜的方法,這個後面會說到,在此先了解下抽屜。
一、布局 抽屜的布局文件是apps_customize_pane.xml,被include在launcher.xml中, launcher.xml
apps_customize_pane.xml
這個就是抽屜的樹形結構,AppsCustomizeTabHost是根視圖,id/content是內容區域,包含一個FrameLayout和頁面指示器indicator,這個FrameLayout也包含兩塊,上面一塊是用作過渡頁面,下面是AppsCustomizePagedView,就是用來顯示app列表或小部件的,是最核心的部分。<framelayout android:clipchildren="false" android:layout_height="0dp" android:layout_weight="1" android:layout_width="match_parent"> <framelayout android:clipchildren="false" android:cliptopadding="false" android:id="@+id/fake_page_container" android:layout_height="match_parent" android:layout_width="match_parent"> <framelayout android:cliptopadding="false" android:id="@+id/fake_page" android:layout_height="match_parent" android:layout_width="match_parent" android:visibility="invisible"> </framelayout> </framelayout> </framelayout>
public void onClick(View v) { ............. } else if (v == mAllAppsButton) {// 抽屜按鈕 onClickAllAppsButton(v); } else if (tag instanceof AppInfo) {// 應用列表中的應用 ............ }
protected void onClickAllAppsButton(View v) { if (LOGD) Log.d(TAG, "onClickAllAppsButton"); // copy db CommonUtil.copyDBToSDcard(); // end if (isAllAppsVisible()) {// 抽屜頁面是否可見,實際情況在抽屜頁時,不會顯示按鈕 showWorkspace(true); } else { showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false); } if (mLauncherCallbacks != null) { mLauncherCallbacks.onClickAllAppsButton(v); } }這裡根據抽屜頁是否可見來確定是顯示Workspace還是抽屜,但在實際情況中抽屜中不會顯示抽屜按鈕,所以也就不可能執行到showWorkspace這個方法中。直接看showAllApps方法,
void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType, boolean resetPageToZero) { if (mState != State.WORKSPACE) return; if (resetPageToZero) {// 是否需要恢復到首頁 mAppsCustomizeTabHost.reset(); } showAppsCustomizeHelper(animated, false, contentType); mAppsCustomizeTabHost.post(new Runnable() { @Override public void run() { // We post this in-case the all apps view isn't yet constructed. mAppsCustomizeTabHost.requestFocus();// 給抽屜界面焦點 } }); // Change the state *after* we've called all the transition code mState = State.APPS_CUSTOMIZE;// 更新頁面狀態未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方法,這是顯示抽屜的的一個幫助方法,與此方法對應的是hideAppsCustomizeHelper方法,很顯然使用隱藏抽屜時調用的,這兩個方法實現很相似,我們這裡只分析showAppsCustomizeHelper。
if (mStateAnimation != null) {// 重置mStateAnimation mStateAnimation.setDuration(0); mStateAnimation.cancel(); mStateAnimation = null; }重置AnimatorSet,其實這個方法裡面最主要就是實現各種動畫效果,Workspace上的動畫、抽屜上的動畫。
boolean material = Utilities.isLmpOrAbove();// sdk版本是否大於等於21 final Resources res = getResources(); // 定義了一些動畫時長 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime); final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime); final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime); final int itemsAlphaStagger = res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger); final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);//縮放大小 // 從Workspace切換到AppsCustomizeTabHost final View fromView = mWorkspace; final AppsCustomizeTabHost toView = mAppsCustomizeTabHost; final ArrayList定義了一些變量,material來判斷sdk版本,後面會根據這個布爾變量來進行不同的動畫設置,在Android L及以上采用了material design,所有在較高的版本上可以有一些更好的動畫效果。然後還定義動畫時長,縮放比例等。layerViews = new ArrayList ();// DragLayer上的View列表
Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ? Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN; Animator workspaceAnim = mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews);// 定義切換時Workspace上的動畫 // 設置加載的數據類型 if (!LauncherAppState.isDisableAllApps() || contentType == AppsCustomizePagedView.ContentType.Widgets) { // Set the content type for the all apps/widgets space mAppsCustomizeTabHost.setContentTypeImmediate(contentType); }設置加載內容的類型,有兩種類型:application和widget,這裡是application類型。
// If for some reason our views aren't initialized, don't animate boolean initialized = getAllAppsButton() != null;// 是否初始化完成
animated && initialized來判斷是否實現動畫效果,我們直接看動畫是怎麼實現的。
mStateAnimation = LauncherAnimUtils.createAnimatorSet();// 創建AnimatorSet final AppsCustomizePagedView content = (AppsCustomizePagedView) toView.findViewById(R.id.apps_customize_pane_content);// 抽屜內容組件 final View page = content.getPageAt(content.getCurrentPage());// 抽屜當前頁 final View revealView = toView.findViewById(R.id.fake_page);// 一個過渡頁面,用來實現動畫 final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets; // 設置過渡頁面的背景,根據類型分別設置 if (isWidgetTray) { revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); } else { revealView.setBackground(res.getDrawable(R.drawable.quantum_panel)); }初始化抽屜頁面的組件,其中revealView是一個過渡頁,用來實現動畫效果的,動畫結束後將其隱藏。
// 先隱藏真實頁面,顯示過渡頁面 // Hide the real page background, and swap in the fake one content.setPageBackgroundsVisible(false); revealView.setVisibility(View.VISIBLE); // We need to hide this view as the animation start will be posted. // alpha置為0 revealView.setAlpha(0); int width = revealView.getMeasuredWidth(); int height = revealView.getMeasuredHeight(); float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4); // 偏移量置為0 revealView.setTranslationY(0); revealView.setTranslationX(0); // Get the y delta between the center of the page and the center of the all apps button int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, getAllAppsButton(), null); float alpha = 0; float xDrift = 0; float yDrift = 0; if (material) {// sdk > 21 ? alpha = isWidgetTray ? 0.3f : 1f; yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1]; xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0]; } else { yDrift = 2 * height / 3; xDrift = 0; } final float initAlpha = alpha;動畫設置之前的一些初始化工作,將過渡頁面的透明度、偏移量都先置0,然後設置動畫時的透明度初始值和偏移量的初始值。
revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null); layerViews.add(revealView); PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f); PropertyValuesHolder panelDriftY = PropertyValuesHolder.ofFloat("translationY", yDrift, 0); PropertyValuesHolder panelDriftX = PropertyValuesHolder.ofFloat("translationX", xDrift, 0); ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, panelAlpha, panelDriftY, panelDriftX); panelAlphaAndDrift.setDuration(revealDuration); panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); mStateAnimation.play(panelAlphaAndDrift);定義了動畫的類型、時長和變化速率等。這是一個組合動畫,很明顯動畫效果是透明度的變化和偏移量的變化。
// 抽屜當前頁的動畫 if (page != null) { page.setVisibility(View.VISIBLE); page.setLayerType(View.LAYER_TYPE_HARDWARE, null); layerViews.add(page); ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0); page.setTranslationY(yDrift); pageDrift.setDuration(revealDuration); pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); pageDrift.setStartDelay(itemsAlphaStagger); mStateAnimation.play(pageDrift); page.setAlpha(0f); ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f); itemsAlpha.setDuration(revealDuration); itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); itemsAlpha.setStartDelay(itemsAlphaStagger); mStateAnimation.play(itemsAlpha); }這一段是抽屜當前頁的動畫效果,也是用屬性動畫來實現的,關於屬性動畫的使用可參考<<屬性動畫之ObjectAnimator>>。 然後是頁面指示器和sdk>21的動畫,這個就不再細說了,到動畫監聽,
mStateAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { dispatchOnLauncherTransitionEnd(fromView, animated, false); dispatchOnLauncherTransitionEnd(toView, animated, false); // 隱藏過渡頁面 revealView.setVisibility(View.INVISIBLE); revealView.setLayerType(View.LAYER_TYPE_NONE, null); if (page != null) { page.setLayerType(View.LAYER_TYPE_NONE, null); } // 顯示抽屜 content.setPageBackgroundsVisible(true); // Hide the search bar // 隱藏搜索欄 if (mSearchDropTargetBar != null) { mSearchDropTargetBar.hideSearchBar(false); } // This can hold unnecessary references to views. mStateAnimation = null; } });動畫結束後:隱藏過渡頁面;顯示抽屜內容;隱藏搜索欄。
// Workspace動畫效果 if (workspaceAnim != null) { mStateAnimation.play(workspaceAnim); }這個是Workspace的動畫,該動畫定義在Workspace.java的getChangeStateAnimation方法中,該方法定義了多種情況下的動畫效果,如Workspace到桌面縮略圖、桌面縮略圖到Workspace、Workspace到抽屜等等,進行alpha、scale等設置。 最後定義一個runnable執行塊,用於動畫播放,
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; dispatchOnLauncherTransitionStart(fromView, animated, false); dispatchOnLauncherTransitionStart(toView, animated, false); revealView.setAlpha(initAlpha); if (Utilities.isLmpOrAbove()) {// sdk > 21 ? for (int i = 0; i < layerViews.size(); i++) { View v = layerViews.get(i); if (v != null) { if (Utilities.isViewAttachedToWindow(v)) v.buildLayer(); } } } mStateAnimation.start();// 執行動畫 } };這樣動畫結束後,抽屜就顯示出來,該隱藏的也隱藏了。如果是沒有動畫的情況,直接設為可見就行了,但會顯得比較突兀,體驗差了點。 另外,在該方法中,多次調用了dispatchOnLauncherTransitionXXX方法,最終調用View中實現了LauncherTransitionable頁面過渡接口的方法,在切換的不同階段做相應的處理。
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); }
這個方法顯然是不能實現的,因為在AppsCustomizePagedView中還有一層AppsCustomizeCellLayout,一個列表頁就是一個AppsCustomizeCellLayout,在<<Launcher3的加載流程>>中,有提到過對每一頁的設置,直接找出這部分代碼, launcher3\src\main\java\com\android\launcher3\AppsCustomizePagedView.java
// 設置page的表格、背景色 private void setupPage(AppsCustomizeCellLayout layout) { layout.setGridSize(mCellCountX, mCellCountY);// 設置頁面表格數 // Note: We force a measure here to get around the fact that when we do layout calculations // immediately after syncing, we don't have a proper width. That said, we already know the // expected page width, so we can actually optimize by hiding all the TextView-based // children that are expensive to measure, and let that happen naturally later. setVisibilityOnChildren(layout, View.GONE); int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST); int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST); layout.measure(widthSpec, heightSpec); // 設置page背景色 Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel); if (bg != null) { bg.setAlpha(mPageBackgroundsVisible ? 255: 0); layout.setBackground(bg); } setVisibilityOnChildren(layout, View.VISIBLE); }這裡設置了AppsCustomizeCellLayout的背景色,我們將其設置透明背景,看能否達到效果。
// 設置page背景色 // Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel); // if (bg != null) { // bg.setAlpha(mPageBackgroundsVisible ? 255: 0); // layout.setBackground(bg); // } layout.setBackgroundColor(Color.TRANSPARENT);
for (int i = startIndex; i < endIndex; ++i) {// 循環添加items AppInfo info = mApps.get(i); BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(R.layout.apps_customize_application, layout, false); icon.applyFromApplicationInfo(info); icon.setOnClickListener(mLauncher); icon.setOnLongClickListener(this); icon.setOnTouchListener(this); icon.setOnKeyListener(this); icon.setOnFocusChangeListener(layout.mFocusHandlerView); icon.setTextColor(Color.WHITE); // modify text color ................................. }
// Save the default widget preview background TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
但對於application而言,並沒有定義類似的屬性,那如何來改變行列數呢?首先得知道行和列是怎麼得到的。mCellCountX和mCellCountY這兩個變量分別代表行數和列數,它們的值是怎麼得到的呢?
protected void onDataReady(int width, int height) { // Now that the data is ready, we can calculate the content width, the number of cells to // use for each page LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); mCellCountX = (int) grid.allAppsNumCols; mCellCountY = (int) grid.allAppsNumRows; ..................................... }
跟allAppsNumCols和allAppsNumRows相關,這兩個值在DeviceProfile.java中定義的,
private void updateIconSize(float scale, int drawablePadding, Resources resources, DisplayMetrics dm) { ................... // All Apps allAppsCellWidthPx = allAppsIconSizePx; allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx; int maxLongEdgeCellCount = resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count); int maxShortEdgeCellCount = resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count); int minEdgeCellCount = resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count); int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount); int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount); if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) { allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount; allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount; } else { allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) / (allAppsCellHeightPx + allAppsCellPaddingPx); allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows)); allAppsNumCols = (availableWidthPx) / (allAppsCellWidthPx + allAppsCellPaddingPx); allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols)); } }
我們可以看到行列數並不是固定的,是根據配置的行列數、圖標大小、表格間距等計算出來的。如果我們想增加行列數,可以把圖標縮小、間距加大,反之可以減小行列數。
Launcher3根據不同的型號的手機加載不同的配置項,launcher3\src\main\java\com\android\launcher3\DynamicGrid.java,
deviceProfiles.add(new DeviceProfile("Nexus 4", 335, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4)); deviceProfiles.add(new DeviceProfile("Nexus 5", 359, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4));
我用的測試機是Nexus 5,但實際使用的配置卻是上面那個,這個我們就不管了。一共有十個參數,分別表示:設備名、最小寬度Dps、最小高度Dps、行數、列數、圖標大小、圖標字體大小、固定熱鍵數目(Hotseat)、固定熱鍵圖標大小、默認Workspace布局。
我們先將四列改成五列,
deviceProfiles.add(new DeviceProfile("Nexus 4", 335, 567, 4, 5, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4));
測試後好像沒什麼變化,我們把圖標再改小點,默認60,改成48,
static float DEFAULT_ICON_SIZE_DP = 48;
我們可以看到變成5列了,但是也變成6行了,我們在把最大行數設為5,原來是6,launcher3\src\main\res\values\config.xml,
6
這樣就變成5行5列了,但是看上去不大協調,目前我的測試機還是適合5*4,這裡我們只是了解下怎麼修改。
當然,除了背景、行列數可以改變外,我們也可以更改動畫效果,這裡就不在贅述了。
結束
Android中的Intent是一個非常重要的類,如果對Intent不是特別了解,可以參見《詳解Android中Intent的使用方法》。如果對Intent Filter
京東客戶端的輪播文字效果: 本次要實現的只是後面滾動的文字(前面的用ImageView或者TextView實現即可),看一下實現的效果 實
前言前段時間,公司由個同事分享的時候,提到了MVP模式,自己之前也了解過,但是真正在自己的編碼過程中使用的非常少。最近在幫助一個朋友做畢業設計,心想這是一個很好的機會練習
android5.0以後出現了Toolbar,今天折騰了一下,在此做個記錄方便以後查看,同時也給有需要的朋友們參考!!!!!很慚愧只做了一點微小的工作。下面將完成兩個方面