前言:Launcher是Android的系统应用,在手机开机后第一个见到就是Launcher。用户通过Launcher基本上可以从整体上纵观手机中所存在的所有应用;Android源码中的Launcher分两个主要界面,一个是WorkSpace界面,就是我们俗称的桌面;另一个是AppsCustomizePagedView界面,就是我们俗称的菜单界面;Launcher可以说是Android系统中比较重要的系统应用模块,能够掌握和熟悉Launcher模块,当然是最好的,Launcher这块骨头很硬,必须要慢慢啃;个人认为,在熟悉一个系统模块之前,弄清楚它的整体布局是至关重要的,所以这篇文章主要是重点分析Launcher的相关布局,若有不当之处,还请同行们指点迷津。废话有点多了,开始吧!
一. Launcher主入口
Launcher就是一个系统应用, 如果想要知道Launcher中都有那些四大组件和主入口,那么通过它的AndroidManifest.xml文件代码就可以探知,如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher">
<original-package android:name="com.android.launcher2" />
<!-- Launcher中自定义的权限 -->
<permission
android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="normal"
android:label="@string/permlab_install_shortcut"
android:description="@string/permdesc_install_shortcut" />
<permission
android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="normal"
android:label="@string/permlab_uninstall_shortcut"
android:description="@string/permdesc_uninstall_shortcut"/>
<permission
android:name="com.android.launcher.permission.READ_SETTINGS"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="normal"
android:label="@string/permlab_read_settings"
android:description="@string/permdesc_read_settings"/>
<permission
android:name="com.android.launcher.permission.WRITE_SETTINGS"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="normal"
android:label="@string/permlab_write_settings"
android:description="@string/permdesc_write_settings"/>
<!-- 注册系统权限 -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
<!-- hardwareAccelerated为是否使用硬件加速,largeHeap为是否使用了大的堆内存,值在config.xml文件中定义 -->
<application
android:name="com.android.launcher2.LauncherApplication"
android:label="@string/application_name"
android:icon="@drawable/ic_launcher_home"
android:hardwareAccelerated="@bool/config_hardwareAccelerated"
android:largeHeap="@bool/config_largeHeap">
<activity
android:name="com.android.launcher2.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:theme="@style/Theme"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="nosensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
</intent-filter>
</activity>
<activity
android:name="com.android.launcher2.WallpaperChooser"
style="@style/Theme.WallpaperPicker"
android:label="@string/pick_wallpaper"
android:icon="@drawable/ic_launcher_wallpaper"
android:finishOnCloseSystemDialogs="true"
android:process=":wallpaper_chooser">
<intent-filter>
<action android:name="android.intent.action.SET_WALLPAPER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.wallpaper.preview"
android:resource="@xml/wallpaper_picker_preview" />
</activity>
<activity android:name="com.android.launcher2.RocketLauncher"
android:label="@string/dream_name"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DREAM" />
</intent-filter>
</activity>
<!-- Intent received used to install shortcuts from other applications -->
<receiver
android:name="com.android.launcher2.InstallShortcutReceiver"
android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">
<intent-filter>
<action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
</intent-filter>
</receiver>
<!-- Intent received used to uninstall shortcuts from other applications -->
<receiver
android:name="com.android.launcher2.UninstallShortcutReceiver"
android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT">
<intent-filter>
<action android:name="com.android.launcher.action.UNINSTALL_SHORTCUT" />
</intent-filter>
</receiver>
<!-- The settings provider contains Home's data, like the workspace favorites -->
<provider
android:name="com.android.launcher2.LauncherProvider"
android:authorities="com.android.launcher2.settings"
android:writePermission="com.android.launcher.permission.WRITE_SETTINGS"
android:readPermission="com.android.launcher.permission.READ_SETTINGS" />
</application>
</manifest>
AndroidManifest.xm文件中的代码可以知道:
Application。某种意义上来说是Launcher的主入口类;在Launcher应用创建时,最先执行的是LauncherApplication类中的onCreate()方法;LauncherApplication类中主要实现注册一些广播接收器和监听数据库改变的ContentObserver,同时对外(主要是主Activity)提供一些获取相关对象(LauncherModel、IconCache、LauncherProvider等)和屏幕密度的接口函数。
2). com.android.launcher2.Launcher为Launcher模块的主Activity,Launcher中的灵魂类。在该主Activity的onCreate方法中执行setContentView(R.layout.launcher)加载Launcher的整个布局。
3). com.android.launcher2.WallpaperChooser为长按桌面空白处弹出的选择壁纸相关的Activity组件。
4).com.android.launcher2.InstallShortcutReceiver为安装快捷方式时负责接收相关广播进行操作的BroadcastReceiver组件。比如,要为自己开发的应用添加第一次启动时在桌面创建快捷键的功能,那么就需要注册com.android.launcher.permission.INSTALL_SHORTCUT权限,同时发送Intent的Action为com.android.launcher.action.INSTALL_SHORTCUT的广播。
BroadcastReceiver组件。
7). com.android.launcher2.LauncherProvider为Launcher进行数据库相关操作的ContentProvider组件。
二. Launcher总布局文件launcher.xml浅析
通过上文的介绍,可以知道,布局文件launcher.xml是在Launcher的onCreate方法中加载的。
1. Launcher.xml文件代码(竖向布局):
<?xml version="1.0" encoding="utf-8"?>
<com.android.launcher2.DragLayer
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
<!-- dock_divider为桌面分隔线,将hotseat和Workspace分隔 -->
<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"
android:layout_gravity="bottom" />
<!-- 桌面分隔线的上指示条,Workspace翻页的时候显示 -->
<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" />
<!-- The workspace contains 5 screens of cells -->
<!-- Workspace即手机桌面,默认系统是包含可翻转5页 -->
<com.android.launcher2.Workspace
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/qsb_bar_height_inset"
android:paddingBottom="@dimen/button_bar_height"
launcher:defaultScreen="2"
launcher:cellCountX="4"
launcher:cellCountY="4"
launcher:pageSpacing="@dimen/workspace_page_spacing"
launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left"
launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right">
<!-- Workspace总共可翻转5个页面,一个 workspace_screen定义一个页面布局-->
<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>
<!-- qsb_bar布局包含桌面上的可搜索框 以及长按桌面上图标时显示删除和应用信息的操作框-->
<include
android:id="@+id/qsb_bar"
layout="@layout/qsb_bar" />
<!-- 点击hotseat中心图标进入的界面,该界面显示所有应用和小部件 -->
<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" />
<!-- hotseat为桌面分隔线下的界面, 不随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" />
<!-- 手机刚开机,或者对launcher应用清空数据第一次进入到workspace时弹出的操作介绍界面 -->
<include layout="@layout/workspace_cling"
android:id="@+id/workspace_cling"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<!-- 手机刚开机,或者对launcher应用清空数据,第一次打开将workspace上两个以上的图标拖到一起形成 的文件夹时弹出的操作界面-->
<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.DragLayer>
2. 以下列出Launcher主要界面的截图:
图1 图2 图3
上面三个图比较直观的说明Launcher的主要布局:
图1:Launcher的桌面布局(从上到下为搜索框、Workspace、分割线、hotseat)。
图2:菜单界面(apps_customize_pane)之一的显示所有已安装的应用程序;
图3:菜单界面(apps_customize_pane)之一的显示所有已创建的widget(小部件)
注:菜单界面整体是一个TabHost,由两个子Tab组成;一个就是显示图2界面的子Tab,另一个就是显示图3界面的子Tab
3. 从上到下分析Launcher.xml文件中的代码:
DragLayer类继承自FrameLayout,属于自定义实现的ViewGroup类型容器视图组件。DragLayer类中主要负责Launcher中相关的拖拽处理;
id为dock_divider的子视图(分割线):通过< include />元素添加布局为workspace_divider(对应的布局),我们到workspace_divider.xml文件看看该子视图的真正布局,如下:
workspace_divider.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:paddingLeft="@dimen/workspace_divider_padding_left"
android:paddingRight="@dimen/workspace_divider_padding_right"
android:paddingTop="@dimen/workspace_divider_padding_top"
android:paddingBottom="@dimen/workspace_divider_padding_bottom"
android:scaleType="fitXY"
android:src="@drawable/hotseat_track_holo" />
由此可知id为dock_divider的子视图就是一个ImageView对象,即一条点9图的分割线;该分割线把Workspace和Hotseat分割出来;
3). id为paged_view_indicator的子视图:该子视图对应布局为scroll_indicator,scroll_indicator.xml文件的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:visibility="gone"
android:alpha="0"
android:scaleType="fitXY"
android:src="@drawable/hotseat_scrubber_holo" />
所以,该视图也为ImageView对象,即一个点9图的蓝色指示线,当我们在桌面上滑动翻页时,该蓝色指示线会显示,用于指示翻页操作;
Workspace界面视图):该子视图为自定义类Workspace,Workspace继承自SmoothPagedView,SmoothPagedView继承自PagedView,PagedView继承自ViewGroup;所以Workspace的终极父类也是ViewGroup;即该子视图为ViewGroup类型的自定义容器视图;在Workspace视图中又包含了5个id分别为cell1、cell2、cell3、cell4、cell5的子视图。它们对应的布局均为workspace_screen,workspace_screen.xml文件的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<com.android.launcher2.CellLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
style="@style/WorkspaceScreen"
android:paddingLeft="@dimen/workspace_left_padding"
android:paddingRight="@dimen/workspace_right_padding"
android:paddingTop="@dimen/workspace_top_padding"
android:paddingBottom="@dimen/workspace_bottom_padding"
android:hapticFeedbackEnabled="false"
launcher:cellWidth="@dimen/workspace_cell_width"
launcher:cellHeight="@dimen/workspace_cell_height"
launcher:widthGap="@dimen/workspace_width_gap"
launcher:heightGap="@dimen/workspace_height_gap"
launcher:maxGap="@dimen/workspace_max_gap" />
所以,Workspace中包含的五个子视图均为自定义视图类CellLayout(CellLayout继承ViewGroup);一个
CellLayout对应Workspace中的一页(一屏),Workspace中默认情况下总共可翻转5页,所以Workspace中有5个CellLayout(cell1、cell2、cell3、cell4、cell5);Workspace的区域就是图1中位于搜索框和分割线之间的区域;
qsb_bar.xml的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<com.android.launcher2.SearchDropTargetBar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
style="@style/QSBBar"
android:focusable="false"
android:background="@drawable/search_bg_panel">
<!-- Search buttons container -->
<!-- 桌面上可点击的搜索框 -->
<include android:id="@+id/qsb_search_bar"
layout="@layout/search_bar" />
<!-- Drag specific targets container -->
<!-- 长按菜单界面快捷图标进入到桌面时显示删除和应用信息的操作框,这时候搜索框会隐藏,操作框会显示在 搜索框的位置-->
<LinearLayout
style="@style/SearchDropTargetBar"
android:id="@+id/drag_target_bar"
android:visibility="gone">
<!-- 显示删除的布局 -->
<FrameLayout
style="@style/DropTargetButtonContainer"
android:layout_weight="1">
<!-- Delete target -->
<com.android.launcher2.DeleteDropTarget
style="@style/DropTargetButton"
android:id="@+id/delete_target_text"
android:text="@string/delete_zone_label_workspace"
android:drawableLeft="@drawable/delete_target_selector" />
</FrameLayout>
<!-- 显示应用信息的布局 -->
<FrameLayout
style="@style/DropTargetButtonContainer"
android:layout_weight="1">
<!-- Info target -->
<com.android.launcher2.InfoDropTarget
style="@style/DropTargetButton"
android:id="@+id/info_target_text"
android:text="@string/info_target_label"
android:drawableLeft="@drawable/info_target_selector" />
</FrameLayout>
</LinearLayout>
</com.android.launcher2.SearchDropTargetBar>
该子视图的布局包含搜索框的视图布局和操作框的视图布局:
-->搜索框对应的是id为qsb_search_bar的视图,它对应的布局文件为search_bar.xml;
拽应用图标至该区域时会进入显示应用程序信息的界面;如下图就是从菜单拖拽应用图标到桌面是操作框显示的示意图(屏幕上方显示操作框):
apps_customize_pane文件代码如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- 点击hotseat中心图标显示的菜单界面布局,该界面显示所有应用和小部件 -->
<com.android.launcher2.AppsCustomizeTabHost
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:background="#FF000000">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The layout_width of the tab bar gets overriden to align the content
with the text in the tabs in AppsCustomizeTabHost. -->
<!-- Tab标签"应用程序"、"窗口小部件"和应用商店图标的视图布局 -->
<FrameLayout
android:id="@+id/tabs_container"
android:layout_width="wrap_content"
android:layout_height="@dimen/apps_customize_tab_bar_height"
android:layout_gravity="center_horizontal">
<com.android.launcher2.FocusOnlyTabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="left"
android:background="@drawable/tab_unselected_holo"
android:tabStripEnabled="false" />
<include
android:id="@+id/market_button"
layout="@layout/market_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right" />
</FrameLayout>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 承载所有应用程序图标和widget的视图布局 -->
<com.android.launcher2.AppsCustomizePagedView
android:id="@+id/apps_customize_pane_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:cellCountX="@integer/apps_customize_cellCountX"
launcher:cellCountY="@integer/apps_customize_cellCountY"
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" />
<ImageView
android:id="@+id/animation_buffer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<!-- 蓝色翻页指示线 -->
<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>
<!-- 第一次进入到菜单界面时弹出的操作介绍界面 -->
<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>
菜单界面视图由自定义视图类AppsCustomizeTabHost描述,该类继承TabHost,在菜单界面视图中,包含了一个LinearLayout布局和一个id为all_apps_cling的视图布局(对应的布局为all_apps_cling):
LinearLayout布局中又包含了两个FrameLayout(对应的id分别为tabs_container、tabcontent)。
tabs_container的FrameLayout中包含了菜单界面中"应用程序"和“窗口小部件”这两个子Tab标签的视图布局(FocusOnlyTabWidget)和点击进入“应用商店“的图标(对应布局为market_button)。
tabcontent的FrameLayout中包含了用来承载应用程序图标和widget的界面视图布局(AppsCustomizePagedView)和指示翻页操作的蓝色指示线(对应布局为scroll_indicator)。
id为all_apps_cling的视图布局为手机恢复出厂设置(或清空Launcher数据)后第一次进入到菜单界面时弹出的操作介绍界面,如下图的界面:
7). id为hotseat的子视图:该子视图对应的布局为hotseat,hotseat.xml文件的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<com.android.launcher2.Hotseat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:background="@drawable/hotseat_bg_panel"
launcher:cellCountX="5"
launcher:cellCountY="1">
<com.android.launcher2.CellLayout
android:id="@+id/layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:paddingTop="@dimen/button_bar_height_top_padding"
android:paddingBottom="@dimen/button_bar_height_bottom_padding"
android:paddingLeft="@dimen/button_bar_width_left_padding"
android:paddingRight="@dimen/button_bar_width_right_padding"
launcher:cellWidth="@dimen/hotseat_cell_width"
launcher:cellHeight="@dimen/hotseat_cell_height"
launcher:widthGap="@dimen/hotseat_width_gap"
launcher:heightGap="@dimen/hotseat_height_gap"
launcher:maxGap="@dimen/workspace_max_gap" />
</com.android.launcher2.Hotseat>
图1中分割线以下的区域,如下图所示:
点击中间的菜单按钮实现进入菜单界面,hotseat中的快捷图标也可以任意拖拽放置(也可以将hotseat中的快捷图标拖拽到Workspace中放置);
8). id为workspace_cling的子视图:该视图对应的布局文件为workspace_cling.xml。视图布局为手机恢复出厂设置(或清空Launcher数据)后第一次进入到workspace时弹出的操作介绍界面,如下图所示:
该视图对应的布局文件为folder_cling.xml;视图布局为手机恢复出厂设置(或清空Launcher数据)后第一次打开文件夹时弹出的操作介绍界面,如下图所示: