zoukankan      html  css  js  c++  java
  • [置顶] Android4.0 Launcher源码研究

    Launcher是一个手机的门面,是一个程序的main函数,也是用户日常应用中使用最多的程序,因此在应用开发中非常重要。系统的Launcher源码写得相当优秀,封装了各种各样的组件,控件,还有界面的绘制,数据异步加载,都值得我们去深入学习。本人因为能力有限,时间有限,只在这里抛砖引玉,写一些初略的学习心得,大家也可以自行导入源码,好好研究研究。
    一.Launcher的UI
        下面是一个Launcher的基本界面元素         

        关于界面的实现,我们从launcher.xml入手。launcher.xml有三个文件,分别对应横屏,竖屏和平板布局,我们从竖屏入手,其他类似。
        大致的简化下结构
        <DragLayer>
            <WorkSpace>
                <CellLayout>
                <CellLayout>
                <CellLayout>
                <CellLayout>
                <CellLayout>
            < /WorkSpace>
            <include layout="@layout/hotseat" android:id="@+id/hotseat"/>
            <include android:id="@+id/qsb_bar" layout="@layout/qsb_bar" />
            <include layout="@layout/apps_customize_pane"  android:id="@+id/apps_customize_pane" />
            <include layout="@layout/workspace_cling"  android:id="@+id/workspace_cling"/>
            <include layout="@layout/folder_cling" android:id="@+id/folder_cling"/>
        </ DragLayer >
        这样看布局,然后对应上面的图,就比较清晰了。Launcher的root布局是一个DragLayer(可拖动的层),DragLayer里面有一个workspace,就是我们所说的idle界面,workspace默认加载了五个CellLayout,也就是我们默认五屏。然后继续往下看,有一个hotseat和一个qsb_bar,看名字就知道是最下面的快捷按钮和最上面的快速搜索栏。
        后面三个布局默认都是不可见的。第一个apps_customize_pane,在点击了hotseat下面最中间那个图标后变为可见,然后加载所有的程序icon。还有两个cling,算是遮罩层,只在开机第一次启动时候加载,之后在也没有出来的机会。
    现在,我们就对这几个组件依次进行初略分析:
    1.DragLayer
        DragLayer继承FrameLayout,并在此基础上组合了DragController实现拖放功能,DragLayer主要监听下面两个用户事件
        onInterceptTouchEvent
        onTouchEvent
        这两个都是触摸事件,前者只存在ViewGroup里面,用来管理子控件的touch事件。当DragLayer接受到这两个事件后,会交给DragController进行处理,DragController根据是否在拖放中等信息控制控件拖放过程处理。
         
         
    这里有两个接口,还有一个接口DropTarget,可以实现控件拖放的组件如WorkSpace和 Folder都实现了该接口。

    2.WorkSpace
        WorkSpace继承PageView,是一个可以分页显示的ViewGroup。Page View主要提供了snapToPage() 方法,可以实现页面间的滑动跳转。WorkSpace实现了DragScroller接口,在DragController处理move事件时候,调用父类snapToPage()方法实现屏幕左右切换。
    WorkSpace是一个自定义布局。该布局定义了一些自己的属性。我们看launcher.xml中关于WorkSpace的定义:
         <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">
    
            <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>
        从这个定义中可以看到,WorkSpace是去掉快速搜索栏和HotSeat之后中间的部分(WorkSpace也是全屏的,不过设置了paddingTop和paddingBottom,所以不会和搜索栏,HotSeat重叠)。以launcher开头的属性都是自定义属性。
        此处默认屏幕是第二屏(从第0屏开始)。每屏默认被划分成4*4的网格。在WorkSpace初始化的时候,如果xml中没有定义cellCountX属性和cellCountY属性,默认也是4*4,但如果是Large屏幕,如平板,会自动根据屏幕尺寸和图标尺寸计算应该是几*几。pageSpacing是屏幕内部的间距,再往下就是CellLayout相关了。

    3.CellLayout
        CellLayout没有实现其他接口,但是会监听down事件,在用户在屏幕上按下的时候,判断有没有点到控件,如果有,把这个控件的信息,比如行列数和高宽记录下俩,存放到CellInfo里面。

    4.AppsCustomizePagedView
        AppsCustomizePagedView也是一个自定义的view,父类和WorkSpace一样都是PageView,可以实现左右滑动。
    点击idle界面HotSeat最中间的icon,idle界面被隐藏,AppsCustomizePagedView
        在走完一个缩放动画后,被设置为可见,Launcher的状态也同时切换为State. APPS_CUSTOMIZE。菜单界面是一个TabHost组件,有TabWidget和AppsCustomizePagedView组成。TabWidget有两个item。一个用来显示所有App,一个用来显示所有widget插件。TabWidget下面的就是AppsCustomizePagedView了。
        现在来看看AppsCustomizePagedView在xml中的定义:
     <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开头全部都是自定义属性。MaxAppCellCountX 和MaxAppCellCounY指的是所有App图标排列的最大行列数。一般设置为-1,表示无限制。pageLayoutWidthGap和pageLayoutHeightGap分别表示菜单界面与屏幕边缘的距离,一般小屏幕这里设置为-1,平板布局中,考虑到用户双手会抓在屏幕边缘,所以这里才会设置一定的边距。pageLayoutPaddingXxx指的是内填充,这个和系统的padding一样。widgetCellWithGap和widgetCellHeightGap指的是widget列表界面各个widget之间的间隔,类似系统的margin属性。widgetCountX和widgetCountY
    值widget列表界面是几行几列显示。
    5. HotSeat & Qsb_bar
        
    6. Cling
        Cling功能主要是在第一次进入launcher的演示界面,在第一次进入idle,第一次进入菜单,第一次使用文件夹等都会出现。Cling是个全屏的FrameLayout,定义在DragLayer的最底部,也就是处于界面的最顶层。因此,当它显示出来的时候,能遮盖住所有界面。Cling类主要封装遮罩层的一些显示逻辑和触摸逻辑,还有图片的回收。在不同的界面,或者横竖屏,Cling都能自动显示对应的布局,并拦截相应位置的触摸事件,当用户点击了之后,Cling同事也变为不可见,并释放图片资源。

    二.Launcher的数据加载
        Launcher中的数据提供者是LauncherProvider,它负责把Launcher的数据保存到本地数据库中。比如在idle界面哪一屏哪一行那一列有哪个icon或者widget,这些都会保存到数据库中(注意菜单界面的数据列表不会保存到数据库中,而是第一次读取后保存在内存中)。LauncherProvider在初始化的时候,新建数据库:
     db.execSQL("CREATE TABLE favorites (" +
                        "_id INTEGER PRIMARY KEY," +
                        "title TEXT," +
                        "intent TEXT," +
                        "container INTEGER," +
                        "screen INTEGER," +
                        "cellX INTEGER," +
                        "cellY INTEGER," +
                        "spanX INTEGER," +
                        "spanY INTEGER," +
                        "itemType INTEGER," +
                        "appWidgetId INTEGER NOT NULL DEFAULT -1," +
                        "isShortcut INTEGER," +
                        "iconType INTEGER," +
                        "iconPackage TEXT," +
                        "iconResource TEXT," +
                        "icon BLOB," +
                        "uri TEXT," +
                        "displayMode INTEGER," +
                        "scene TEXT" +	
                        ");");
        从这条语句我们可以大概看出数据库的表结构。当表被创建好之后,Launcher会加载一些与设置的xml文件。比如默认的每一屏的布局文件default_workspace.xml。LauncherProvider在初始化的时候在读取了default_workspace.xml的id后,执行了两个方法。
            loadFavorites(db, id);
            loadScene(db,id);
        这两个方法,通过解析xml,把xml的配置信息,读取到了数据库中,因此,我们要修改launcher初始化屏幕图标分布,可以修改default_workspace.xml这个文件。
        LauncherProvider中提供了数据的差删改查,也封装了对UI元素的插入删除等操作,例如:addAppWidget(), addUriShortcut(),addFolder()等等,这些操作都只是修改数据,不涉及UI上操作。
        Launcher涉及到的数据的加载,基本都封装到LauncherModel里面。再说LauncherModel之前有个比较重要的类也要提一下,它就是 ItemInfo类,这个类其实非常简单,就是数据库中表的字段的一个映射。这样ItemInfo就作为了一个桥梁。
                    
        Launcher需要ItemInfo来确定在屏幕哪个地方布局什么icon,就从LauncherModel获取相应数据,而LauncerModel回去LauncherProvider中取Cursor数据,再转换成ItemInfo数据。
        从这个也能大概看到Launcher设计中如何分层,即LauncherProvider提供原始的数据库数据,LauncherModel取到好转换为Launcher需要的数据,传给Launcher后,Launcher开始绘制界面。
        因为LauncherModel中大部分数据都是异步加载,因此这里有一个很重要的接口,用来给UI回调。         
        public interface Callbacks {
            public boolean setLoadOnResume();
            public int getCurrentWorkspaceScreen();
            public void startBinding();
            public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
            public void bindFolders(HashMap<Long,FolderInfo> folders);
            public void finishBindingItems();
            public void bindAppWidget(LauncherAppWidgetInfo info);
            public void bindAllApplications(ArrayList<ApplicationInfo> apps);
            public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
            public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
            public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
            public void bindPackagesUpdated();
            public boolean isAllAppsVisible();
            public void bindSearchablesChanged();
            public void clearAndSwitchScene(String scene);
        }
        Launcher实现了这个接口,然后通过
        public void initialize(Callbacks callbacks) {
            synchronized (mLock) {
                mCallbacks = new WeakReference<Callbacks>(callbacks);
            }
        }
        传给LauncherModel,LauncherModel在通过异步线程加载完数据后,触发Launcher中的回调函数执行,绘制界面。
         关于界面元素的绑定基本都在LoadTask这个线程里面。

        
    三.Launcher的数据监听
        Launcher中应用程序随时都会添加或者卸载或者更新,因此,监听系统程序安装卸载的监听器是必不可少的。查看代码发现,LauncherModel本身就是我们要找的监听器。在LauncherModel的onReceive监听中,通过Action来判断是安装,卸载,更新应用还是异常安装。通过data传递包名packageName。让后在线程PackageUpdatedTask中更新内存中数据,数据获取完毕后回调接口     
    来通知UI绘制界面。
        在MTK扩展的Launcher中,还有个功能就是未读消息提醒,比如未读短信,未读电话,未读邮件都会在应用的icon上数字提醒。为了实现这个功能,所以MTK添加了MTKUnreadLoader这个广播接收器来监听未读信息的数据。此处依然使用接口回调,接口定义如下:
            
        因为这个功能是新添加的,所以系统没有发送未读消息的广播。因此MTK添加了 Intent.MTK_ACTION_UNREAD_CHANGED这个Anction。在短消息,联系人,邮件中,当收到新的消息,未接电话,新的邮件,都会发送这个广播。发送的时候会带上 Intent.MTK_EXTRA_UNREAD_NUMBER,传递未读的条数。
        支持显示未读记录的应用都保存在unread_support_shortcuts.xml配置文件中:
    <?xml version="1.0" encoding="UTF-8"?>
    <unreadshortcuts xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"> 
        <shortcut
            launcher:unreadPackageName="com.android.contacts"
            launcher:unreadClassName="com.android.contacts.activities.DialtactsActivity"
            launcher:unreadType="0"
            launcher:unreadKey="com_android_contacts_mtk_unread"
     	/>
     	<shortcut
            launcher:unreadPackageName="com.android.mms"
            launcher:unreadClassName="com.android.mms.ui.BootActivity"
            launcher:unreadType="0"
            launcher:unreadKey="com_android_mms_mtk_unread"
     	/>
     	<shortcut
            launcher:unreadPackageName="com.android.email"
            launcher:unreadClassName="com.android.email.activity.Welcome"
            launcher:unreadType="0"
            launcher:unreadKey="com_android_email_mtk_unread"
     	/>
     	<shortcut
            launcher:unreadPackageName="com.android.calendar"
            launcher:unreadClassName="com.android.calendar.AllInOneActivity"
            launcher:unreadType="0"
            launcher:unreadKey="com_android_calendar_mtk_unread"
     	/>
        </unreadshortcuts>
        通过loadUnreadSupportShortcuts()方法读取后,保存在sUnreadSupportShortcuts集合中,只有在这个集合中的应用才会去更新icon右上角的图标
  • 相关阅读:
    背包九讲
    hdu 2602 Bone Collector(01背包)
    hdu 1176 免费馅饼(类似于hdu 2084 数塔那道题目 )
    hdu 1114 PiggyBank(完全背包)
    hdu 2084 数塔
    hdu 1058 Humble Numbers【丑数】
    hdu 1114 PiggyBank【完全背包】
    hdu 2602 Bone Collector (01背包经典入门)
    hdu 2602 Bone Collector【01背包】
    【Python】python的各种函数
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3067427.html
Copyright © 2011-2022 走看看