标签:
抽屉是用来放置安卓手机中所有需要显示到Launcher上的(当然也可以进行过滤,将不想显示的隐藏起来)应用和小部件,启动应用、添加快捷方式到桌面、卸载等。之前也提到过,有些Launcher是没有抽屉的,如MIUI的Launcher。在Launcher3中,默认是有的,当然,也提供了不显示抽屉的方法,这个后面会说到,在此先了解下抽屉。
<include layout="@layout/apps_customize_pane"
android:id="@+id/apps_customize_pane"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />apps_customize_pane.xml<com.android.launcher3.AppsCustomizeTabHost
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:clipChildren="false">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clipChildren="false">
<FrameLayout
android:id="@+id/fake_page_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<FrameLayout
android:id="@+id/fake_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
android:clipToPadding="false" />
</FrameLayout>
<com.android.launcher3.AppsCustomizePagedView
android:id="@+id/apps_customize_pane_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
launcher:maxGap="@dimen/workspace_max_gap"
launcher:pageIndicator="@+id/apps_customize_page_indicator" />
</FrameLayout>
<include
android:id="@+id/apps_customize_page_indicator"
layout="@layout/page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
</com.android.launcher3.AppsCustomizeTabHost> 这个就是抽屉的树形结构,AppsCustomizeTabHost是根视图,id/content是内容区域,包含一个FrameLayout和页面指示器indicator,这个FrameLayout也包含两块,上面一块是用作过渡页面,下面是AppsCustomizePagedView,就是用来显示app列表或小部件的,是最核心的部分。 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);
}
} 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<View> layerViews = new ArrayList<View>();// DragLayer上的View列表 定义了一些变量,material来判断sdk版本,后面会根据这个布尔变量来进行不同的动画设置,在Android
L及以上采用了material design,所有在较高的版本上可以有一些更好的动画效果。然后还定义动画时长,缩放比例等。 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);
}
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);
}
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();// 执行动画
}
};
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);
} <com.android.launcher3.AppsCustomizePagedView
android:id="@+id/apps_customize_pane_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
launcher:maxGap="@dimen/workspace_max_gap"
launcher:pageIndicator="@+id/apps_customize_page_indicator" />
// 设置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,
<integer name="config_dynamic_grid_max_long_edge_cell_count">6</integer>
这样就变成5行5列了,但是看上去不大协调,目前我的测试机还是适合5*4,这里我们只是了解下怎么修改。
标签:
原文地址:http://blog.csdn.net/dingfengnupt88/article/details/51868928