安卓桌面主要由Launcher和SystemUI组成。SystemUI负责可下拉状态栏、导航栏、锁屏页面的显示;Launcher则作为桌面的主要部分,负责各应用与Widget的排列与增删,其布局可占到整个桌面的90%以上。所以我们有必要先从布局层面开始了解其构成。
从AndroidManifest.xml文件可以知道,Launcher.java是应用的主Activity,其布局也正是我们所看到的桌面,以下就是launcher.xml的主要内容。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/workspace_bg">
<!--DragLayer依赖于创建的ImageView展示Icon图标的拖拽状态-->
<com.android.launcher2.DragLayer
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!--HotSeat与WorkSpace的分隔线,它在all apps页面处于隐藏状态-->
<include
android:id="@+id/dock_divider"
layout="@layout/workspace_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/button_bar_height_plus_padding"
android:layout_gravity="bottom|center_horizontal" />
<!--当workspace滑动时出现的tab_indicator-->
<include
android:id="@+id/paged_view_indicator"
layout="@layout/scroll_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/button_bar_height_plus_padding" />
<!-- 负责排列ItemInfo即各应用,是一个分页的PageVIew-->
<com.android.launcher2.Workspace
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/workspace_left_padding"
android:paddingEnd="@dimen/workspace_right_padding"
android:paddingTop="@dimen/workspace_top_padding"
android:paddingBottom="@dimen/workspace_bottom_padding"
launcher:defaultScreen="2"
launcher:cellCountX="@integer/cell_count_x"
launcher:cellCountY="@integer/cell_count_y"
launcher:pageSpacing="@dimen/workspace_page_spacing"
launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left"
launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right">
<!--WorkSpace内部由多个CellLayout构成,每个CellLayout可以理解为PageView中每一个页面-->
<include android:id="@+id/cell1" layout="@layout/workspace_screen" />
<include android:id="@+id/cell2" layout="@layout/workspace_screen" />
<include android:id="@+id/cell3" layout="@layout/workspace_screen" />
<include android:id="@+id/cell4" layout="@layout/workspace_screen" />
<include android:id="@+id/cell5" layout="@layout/workspace_screen" />
</com.android.launcher2.Workspace>
<!--热区,一般位于底部-->
<include layout="@layout/hotseat"
android:id="@+id/hotseat"
android:layout_width="match_parent"
android:layout_height="@dimen/button_bar_height_plus_padding"
android:layout_gravity="bottom" />
<!--顶部searchbar-->
<include
android:id="@+id/qsb_bar"
layout="@layout/qsb_bar" />
<!-- 此控件是当首次打开应用时出现的控件遮盖提示-->
<include layout="@layout/workspace_cling"
android:id="@+id/workspace_cling"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<!--此控件是第一次打开folder时出现-->
<include layout="@layout/folder_cling"
android:id="@+id/folder_cling"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.android.launcher2.DrawableStateProxyView
android:id="@+id/voice_button_proxy"
android:layout_width="80dp"
android:layout_height="@dimen/qsb_bar_height"
android:layout_marginEnd="@dimen/qsb_voice_proxy_padding_right"
android:layout_gravity="top|end"
android:clickable="true"
android:onClick="onClickVoiceButton"
android:importantForAccessibility="no"
launcher:sourceViewId="@+id/voice_button" />
<!--all apps页面,刚进入时默认处于隐藏状态-->
<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" />
</com.android.launcher2.DragLayer>
</FrameLayout>
布局文件可能不够直观,这些控件在手机上的位置可用下面这幅图表示:
当HotSeat被创建时,默认会在中间添加一个all apps按钮,通过此按钮我们可以进入到all apps页面,当然,all apps页面也属于上面launcher.xml的一部分,只不过它在onCreate之后默认处于隐藏状态。以下代码就是HotSeat添加此按钮的关键处。
void resetLayout() {
mContent.removeAllViewsInLayout();
// Add the Apps button
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
BubbleTextView allAppsButton = (BubbleTextView)
inflater.inflate(R.layout.application, mContent, false);
allAppsButton.setCompoundDrawablesWithIntrinsicBounds(null,
context.getResources().getDrawable(R.drawable.all_apps_button_icon), null, null);
allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
allAppsButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mLauncher != null &&
(event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
mLauncher.onTouchDownAllAppsButton(v);
}
return false;
}
});
allAppsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(android.view.View v) {
if (mLauncher != null) {
mLauncher.onClickAllAppsButton(v);
}
}
});
// Note: We do this to ensure that the hotseat is always laid out in the orientation of
// the hotseat in order regardless of which orientation they were added
int x = getCellXFromOrder(mAllAppsButtonRank);
int y = getCellYFromOrder(mAllAppsButtonRank);
CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x,y,1,1);
lp.canReorder = false;
//添加all app到CellLayout的第x,y位置,即中间位置
mContent.addViewToCellLayout(allAppsButton, -1, 0, lp, true);
}
可以看出此按钮本身也是一个BubbleTextView,不同的是它的点击事件没有被Launcher.java中的onClick接收,而是进行单独处理在onClickAllAppsButton方法中。而在onClickAllAppsButton方法就是展开launcher的apps_customize_pane布局进行显示。要展示的布局如下。
<?xml version="1.0" encoding="utf-8"?>
<com.android.launcher2.AppsCustomizeTabHost
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:background="#FF000000">
<LinearLayout
android:id="@+id/apps_customize_content"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<!-- The layout_width of the tab bar gets overriden to align the content
with the text in the tabs in AppsCustomizeTabHost. -->
<FrameLayout
android:id="@+id/tabs_container"
android:layout_width="wrap_content"
android:layout_height="@dimen/apps_customize_tab_bar_height"
android:layout_marginTop="@dimen/apps_customize_tab_bar_margin_top"
android:layout_gravity="center_horizontal">
<!--顶部的TabLayout-->
<com.android.launcher2.FocusOnlyTabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="start"
android:background="@drawable/tab_unselected_holo"
android:tabStripEnabled="false"
android:divider="@null" />
<!--顶部的Market按钮,如果当前应用中有满足Category为Intent.CATEGORY_APP_MARKET的应用,那就显示-->
<include
android:id="@+id/market_button"
layout="@layout/market_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end" />
</FrameLayout>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.launcher2.AppsCustomizePagedView
android:id="@+id/apps_customize_pane_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:maxAppCellCountX="@integer/apps_customize_maxCellCountX"
launcher:maxAppCellCountY="@integer/apps_customize_maxCellCountY"
launcher:pageLayoutWidthGap="@dimen/apps_customize_pageLayoutWidthGap"
launcher:pageLayoutHeightGap="@dimen/apps_customize_pageLayoutHeightGap"
launcher:pageLayoutPaddingTop="@dimen/apps_customize_pageLayoutPaddingTop"
launcher:pageLayoutPaddingBottom="@dimen/apps_customize_pageLayoutPaddingBottom"
launcher:pageLayoutPaddingLeft="@dimen/apps_customize_pageLayoutPaddingLeft"
launcher:pageLayoutPaddingRight="@dimen/apps_customize_pageLayoutPaddingRight"
launcher:widgetCellWidthGap="@dimen/apps_customize_widget_cell_width_gap"
launcher:widgetCellHeightGap="@dimen/apps_customize_widget_cell_height_gap"
launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
launcher:clingFocusedX="@integer/apps_customize_cling_focused_x"
launcher:clingFocusedY="@integer/apps_customize_cling_focused_y"
launcher:maxGap="@dimen/workspace_max_gap" />
<!--此控件没有在Launcher中使用到-->
<FrameLayout
android:id="@+id/animation_buffer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF000000"
android:visibility="gone" />
<!--此控件位于底部,当AppsCustomizePagedView滑动时在底部显示的指示器-->
<include
android:id="@+id/paged_view_indicator"
layout="@layout/scroll_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</FrameLayout>
</LinearLayout>
<!--当我们在all apps页面第一次长进入时,出现的遮罩提示控件,主要用于提示用户长按图标即可拖拽到WorkSpace中-->
<include layout="@layout/all_apps_cling"
android:id="@+id/all_apps_cling"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</com.android.launcher2.AppsCustomizeTabHost>
我们可以用下面两幅图直观的表示,图一即是当第一次进入all apps页面时出现的提示遮罩。当我们长按图中所标识的地方时,会发现当前页面处于拖拽状态(此拖拽和DragLayer有关)。图二上部分是FocusOnlyTabWidget即Tab标签。右边的MarketButton由于系统中没有满足条件的应用所以没有显示出来。此页面排列了系统中所有的用户应用,当我们进行滑动时,底部还会出现指示器。
OK,布局的话先介绍到这里,之后的文章我想从以下几个方面来理解Launcher。
1.Launcher是启动流程分析。
2.DragController如何控制拖动。
3.Launcher数据库构建与CRUD的方面。
4.FolderIcon的了解与深入。
5.壁纸是显示到Launcher桌面的。
6.WorSpace与CellLayout的代码结构。
7.Launcher为适配TV做了哪些工作。