【Launcher开发】Android桌面布局分析

    安卓桌面主要由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做了哪些工作。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值