Android Launcher3源码分析与修改

时间:2022-11-11 09:24:06

Launcher和Setting是客户需求经常改动的地方,不过其代码量也不容小觑。今天就初略来看一下,以下内容都是本人查阅资料加上自己的理解得出,由于自己水平有限,如果误导还请指出:

先从AndroidManifest文件入手,Launcher3的工程名是ToggleWeightWatcher,包名是com.android.launcher3。

关于权限,Launcher3的权限有permission和uses-permission两种。

permission是自定义的权限,uses-permission是调用系统的permission。其中自定义permission有几个属性:

android:permissionGroup 可选,为Permission进行分组,可以由以下常量定义:

ACCOUNTS:  账户管理相关

COST_MONEY:让用户花钱但不需要通过与他们直接牵涉

DEVELOPMENT_TOOLS:开发相关

HARDWARE_CONTROLS:直接访问硬件设备

LOCATION:  访问用户当前位置

MESSAGE:  信息相关

NETWORK:  访问网络服务相关

PERSONAL_INFO:访问用户私人数据相关

PHONE_CALLS:拨号相关

STORAGE:SD卡相关

SYSTEM_TOOLS:系统API有关

android:protectionLevel必有,

normal:低风险权限,只要申请了就可以使用(在AndroidManifest.xml中添加<uses-permission>标签),安装时不需要用户确认;
dangerous:高风险权限,安装时需要用户的确认才可使用;
signature:只有当申请权限的应用程序的数字签名与声明此权限的应用程序的数字签名相同时(如果是申请系统权限,则需要与系统签名相同),才能将权限授给它;
signatureOrSystem:签名相同,或者申请权限的应用为系统应用(在system image中)

其他的android:label, android:name, android:description是描述性信息

[html]  view plain copy
  1. <permission  
  2.        android:name="com.android.launcher3.permission.PRELOAD_WORKSPACE"  
  3.        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"  
  4.        android:protectionLevel="system|signature" />  
  5.    <permission  
  6.        android:name="com.android.launcher.permission.INSTALL_SHORTCUT"  
  7.        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"  
  8.        android:protectionLevel="dangerous"  
  9.        android:label="@string/permlab_install_shortcut"  
  10.        android:description="@string/permdesc_install_shortcut" />  
  11.    <permission  
  12.        android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"  
  13.        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"  
  14.        android:protectionLevel="dangerous"  
  15.        android:label="@string/permlab_uninstall_shortcut"  
  16.        android:description="@string/permdesc_uninstall_shortcut"/>  
  17.    <permission  
  18.        android:name="com.android.launcher3.permission.READ_SETTINGS"  
  19.        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"  
  20.        android:protectionLevel="normal"  
  21.        android:label="@string/permlab_read_settings"  
  22.        android:description="@string/permdesc_read_settings"/>  
  23.    <permission  
  24.        android:name="com.android.launcher3.permission.WRITE_SETTINGS"  
  25.        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"  
  26.        android:protectionLevel="normal"  
  27.        android:label="@string/permlab_write_settings"  
  28.        android:description="@string/permdesc_write_settings"/>  
  29.   
  30.    <permission  
  31.        android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS"  
  32.        android:protectionLevel="signature"  
  33.        />  
  34.   
  35.    <uses-permission android:name="android.permission.CALL_PHONE" />  
  36.    <uses-permission android:name="android.permission.SET_WALLPAPER" />  
  37.    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />  
  38.    <uses-permission android:name="android.permission.VIBRATE" />  
  39.    <uses-permission android:name="android.permission.BIND_APPWIDGET" />  
  40.    <uses-permission android:name="android.permission.GET_ACCOUNTS" />  
  41.    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  
  42.    <uses-permission android:name="android.permission.READ_MEDIA_STORAGE" />  
  43.    <uses-permission android:name="android.permission.ADVANCED_WIDGET_API"/>  
  44.    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />  
  45.    <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />  
  46.    <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />  
  47.    <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />  
  48.    <uses-permission android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS" />  
  49.    <!-- M: hide apps activity requires this permission to get package size. -->  
  50.    <uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>  

下面逐一看一下各个权限:

PRELOAD_WORKSPACE

INSTALL_SHORTCUT / UNINSTALL_SHORTCUT 创建 / 删除快捷方式

READ_SETTINGS / WRITE_SETTINGS 读取 / 写入设置配置

RECEIVE_LAUNCH_BROADCASTS 接收启动广播

CALL_PHONE 拨打电话

SET_WALLPAPER 设置壁纸

SET_WALLPAPER_HINTS  设置壁纸(桌面)提示,目前不懂这个什么用,是初次启动Launcher的使用提示吗?

VIBRATE 震动

BIND_APPWIDGET 访问AppWidget数据

GET_ACCOUNTS 访问账号服务的账号列表

READ_EXTERNAL_STORAGE 读取外部存储

READ_MEDIA_STORAGE 读取媒体存储

ADVANCED_WIDGET_API    高级Widget接口

GET_PACKAGE_SIZE  获取应用包大小


接着看一下AndroidManifest文件中的<application>部分:

这里面声明的都是显式Activity,Service,Provider之类的。

先上源码:

[html]  view plain copy
  1. <application  
  2.       android:name="com.android.launcher3.LauncherApplication"  
  3.       android:label="@string/application_name"  
  4.       android:icon="@mipmap/ic_launcher_home"  
  5.       android:hardwareAccelerated="true"  
  6.       android:largeHeap="@bool/config_largeHeap"  
  7.       android:supportsRtl="true">  
  8.       <activity  
  9.           android:name="com.android.launcher3.Launcher"  
  10.           android:launchMode="singleTask"  
  11.           android:clearTaskOnLaunch="true"  
  12.           android:stateNotNeeded="true"  
  13.           android:theme="@style/Theme"  
  14.           android:windowSoftInputMode="adjustPan"  
  15.           android:screenOrientation="nosensor">  
  16.           <intent-filter>  
  17.               <action android:name="android.intent.action.MAIN" />  
  18.               <category android:name="android.intent.category.HOME" />  
  19.               <category android:name="android.intent.category.DEFAULT" />  
  20.               <category android:name="android.intent.category.MONKEY"/>  
  21.           </intent-filter>  
  22.       </activity>  
  23.   
  24.       <!-- M: hide apps activity used to configure apps to be hidden in apps list. -->  
  25.       <activity android:name="com.android.launcher3.HideAppsActivity"  
  26.           android:label="@string/hideapps"  
  27.           android:icon="@drawable/ic_launcher_home"  
  28.           android:screenOrientation="nosensor" >  
  29.           <intent-filter>  
  30.               <category android:name="android.intent.category.DEFAULT" />  
  31.           </intent-filter>  
  32.       </activity>  
  33.   
  34.       <activity  
  35.           android:name="com.android.launcher3.ToggleWeightWatcher"  
  36.           android:label="@string/toggle_weight_watcher"  
  37.           android:enabled="@bool/debug_memory_enabled"  
  38.           android:icon="@mipmap/ic_launcher_home">  
  39.           <intent-filter>  
  40.               <action android:name="android.intent.action.MAIN" />  
  41.               <category android:name="android.intent.category.DEFAULT" />  
  42.               <category android:name="android.intent.category.LAUNCHER" />  
  43.           </intent-filter>  
  44.       </activity>  
  45.   
  46.       <activity  
  47.           android:name="com.android.launcher3.WallpaperPickerActivity"  
  48.           android:theme="@style/Theme.WallpaperCropper"  
  49.           android:label="@string/pick_wallpaper"  
  50.           android:icon="@mipmap/ic_launcher_wallpaper"  
  51.           android:finishOnCloseSystemDialogs="true"  
  52.           android:process=":wallpaper_chooser"  
  53.    android:configChanges="orientation|keyboard|keyboardHidden|screenSize">  
  54.           <intent-filter>  
  55.               <action android:name="android.intent.action.SET_WALLPAPER" />  
  56.               <category android:name="android.intent.category.DEFAULT" />  
  57.           </intent-filter>  
  58.       </activity>  
  59.   
  60.       <activity  
  61.           android:name="com.android.launcher3.WallpaperCropActivity"  
  62.           android:theme="@style/Theme.WallpaperCropper"  
  63.           android:label="@string/crop_wallpaper"  
  64.           android:icon="@mipmap/ic_launcher_wallpaper"  
  65.           android:finishOnCloseSystemDialogs="true"  
  66.           android:process=":wallpaper_chooser">  
  67.           <intent-filter>  
  68.               <action android:name="android.service.wallpaper.CROP_AND_SET_WALLPAPER" />  
  69.               <category android:name="android.intent.category.DEFAULT" />  
  70.               <data android:mimeType="image/*" />  
  71.           </intent-filter>  
  72.       </activity>  
  73.   
  74.       <!-- Debugging tools -->  
  75.       <activity  
  76.           android:name="com.android.launcher3.MemoryDumpActivity"  
  77.           android:theme="@android:style/Theme.NoDisplay"  
  78.           android:label="@string/debug_memory_activity"  
  79.           android:enabled="@bool/debug_memory_enabled"  
  80.           android:excludeFromRecents="true"  
  81.           android:icon="@mipmap/ic_launcher_home"  
  82.           >  
  83.           <intent-filter>  
  84.               <action android:name="android.intent.action.MAIN" />  
  85.               <category android:name="android.intent.category.DEFAULT" />  
  86.               <category android:name="android.intent.category.LAUNCHER" />  
  87.           </intent-filter>  
  88.       </activity>  
  89.   
  90.       <service android:name="com.android.launcher3.MemoryTracker"  
  91.           android:enabled="@bool/debug_memory_enabled"  
  92.           >  
  93.       </service>  
  94.   
  95.       <!-- Intent received used to prepopulate the default workspace. -->  
  96.       <receiver  
  97.           android:name="com.android.launcher3.PreloadReceiver"  
  98.           android:permission="com.android.launcher3.permission.PRELOAD_WORKSPACE">  
  99.           <intent-filter>  
  100.               <action android:name="com.android.launcher3.action.PRELOAD_WORKSPACE" />  
  101.           </intent-filter>  
  102.       </receiver>  
  103.   
  104.       <!-- Intent received used to install shortcuts from other applications -->  
  105.       <receiver  
  106.           android:name="com.android.launcher3.InstallShortcutReceiver"  
  107.           android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">  
  108.           <intent-filter>  
  109.               <action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />  
  110.           </intent-filter>  
  111.       </receiver>  
  112.   
  113.       <!-- Intent received used to uninstall shortcuts from other applications -->  
  114.       <receiver  
  115.           android:name="com.android.launcher3.UninstallShortcutReceiver"  
  116.           android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT">  
  117.           <intent-filter>  
  118.               <action android:name="com.android.launcher.action.UNINSTALL_SHORTCUT" />  
  119.           </intent-filter>  
  120.       </receiver>  
  121.   
  122.       <!-- New user initialization; set up initial wallpaper -->  
  123.       <receiver  
  124.           android:name="com.android.launcher3.UserInitializeReceiver"  
  125.           android:exported="false">  
  126.           <intent-filter>  
  127.               <action android:name="android.intent.action.USER_INITIALIZE" />  
  128.           </intent-filter>  
  129.       </receiver>  
  130.   
  131.       <receiver android:name="com.android.launcher3.PackageChangedReceiver" >  
  132.           <intent-filter>  
  133.               <action android:name="android.intent.action.PACKAGE_CHANGED"/>  
  134.               <action android:name="android.intent.action.PACKAGE_REPLACED"/>  
  135.               <action android:name="android.intent.action.PACKAGE_REMOVED"/>  
  136.               <data android:scheme="package"></data>  
  137.           </intent-filter>  
  138.       </receiver>  
  139.   
  140.       <!-- The settings provider contains Home's data, like the workspace favorites -->  
  141.       <provider  
  142.           android:name="com.android.launcher3.LauncherProvider"  
  143.           android:authorities="com.android.launcher3.settings"  
  144.           android:exported="true"  
  145.           android:writePermission="com.android.launcher3.permission.WRITE_SETTINGS"  
  146.           android:readPermission="com.android.launcher3.permission.READ_SETTINGS" />  
  147.   
  148.       <meta-data android:name="android.nfc.disable_beam_default"  
  149.                      android:value="true" />  
  150.   </application>  


下面先分析一下各个Activity的大致功能,后期将进行验证:

com.android.launcher3.Launcher Activity,主Activity

com.android.launcher3.ToggleWeightWatcher  Activity,启动Activity

com.android.launcher3.HideAppsActivity Activity,隐藏应用抽屉程序清单中应用的Activity。

com.android.launcher3.WallpaperPickerActivity Activity,选择壁纸的Activity

com.android.launcher3.WallpaperCropActivity  Activity,裁剪壁纸的Activity

com.android.launcher3.MemoryDumpActivity  Activity,这个是调试用的,可忽略

com.android.launcher3.MemoryTracker Service,调试用

com.android.launcher3.PreloadReceiver Receiver,用来响应预加载Launcher默认工作空间

com.android.launcher3.InstallShortcutReceiver  Receiver,用来响应添加其他应用的快捷方式

com.android.launcher3.UninstallShortcutReceiver Receiver,和上一个相反,用来响应去除其他应用的快捷方式

com.android.launcher3.UserInitializeReceiver  Receiver,用来响应新用户初始化,设置初始壁纸

com.android.launcher3.PackageChangedReceiver Receiver, 用来响应包变化

com.android.launcher3.LauncherProvider Provider,包括主页屏的数据,比如图标和AppWidget的位置、大小等。



AllAppList

这是一个比较核心的类,所有应用的信息都在这儿了。

下面看一下AllAppList类的成员变量和成员函数:

data是所有应用的清单:

[java]  view plain copy
  1. public ArrayList<AppInfo> data =  
  2.            new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);  

added是自上次 更新(notify()广播)后新 增加的应用清单:

[java]  view plain copy
  1. public ArrayList<AppInfo> added =  
  2.         new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);  


removed是自上次更新以来,所移除的应用清单:
[java]  view plain copy
  1. public ArrayList<AppInfo> removed = new ArrayList<AppInfo>();  


modified是自上次更新以来,所修改的应用清单:

[java]  view plain copy
  1. public ArrayList<AppInfo> modified = new ArrayList<AppInfo>();  


mIconCache 图标缓存

mAppFilter 应用过滤器

[java]  view plain copy
  1. private IconCache mIconCache;  
  2.   
  3. private AppFilter mAppFilter;  


新添加一个应用及其信息到list中,如果已存在则不添加:

[java]  view plain copy
  1. public void add(AppInfo info) {  
  2.        if (LauncherLog.DEBUG) {  
  3.            LauncherLog.d(TAG, "Add application in app list: app = " + info.componentName  
  4.                    + ", title = " + info.title);  
  5.        }  
  6.   
  7.        if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {  
  8.            return;  
  9.        }  
  10.        if (findActivity(data, info.componentName)) {  
  11.            LauncherLog.i(TAG, "Application " + info + " already exists in app list, app = " + info);  
  12.            return;  
  13.        }  
  14.        data.add(info);  
  15.        added.add(info);  
  16.    }  

清空列表中的应用信息,包括data,added,removed,modified:
[java]  view plain copy
  1. public void clear() {  
  2.     if (LauncherLog.DEBUG) {  
  3.         LauncherLog.d(TAG, "clear all data in app list: app size = " + data.size());  
  4.     }  
  5.   
  6.     data.clear();  
  7.     // TODO: do we clear these too?  
  8.     added.clear();  
  9.     removed.clear();  
  10.     modified.clear();  
  11. }  


为指定包名添加Icon信息到matches 表单:
[java]  view plain copy
  1. public void addPackage(Context context, String packageName) {  
  2.     final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);  
  3.   
  4.     if (LauncherLog.DEBUG) {  
  5.         LauncherLog.d(TAG, "addPackage: packageName = " + packageName + ", matches = " + matches.size());  
  6.     }  
  7.   
  8.     if (matches.size() > 0) {  
  9.         for (ResolveInfo info : matches) {  
  10.             add(new AppInfo(context.getPackageManager(), info, mIconCache, null));  
  11.         }  
  12.     }  
  13. }  


从data表单中删除指定包名的应用的包信息:
[java]  view plain copy
  1. public void removePackage(String packageName) {  
  2.      final List<AppInfo> data = this.data;  
  3.      if (LauncherLog.DEBUG) {  
  4.          LauncherLog.d(TAG, "removePackage: packageName = " + packageName + ", data size = " + data.size());  
  5.      }  
  6.   
  7.      for (int i = data.size() - 1; i >= 0; i--) {  
  8.          AppInfo info = data.get(i);  
  9.          final ComponentName component = info.intent.getComponent();  
  10.          if (packageName.equals(component.getPackageName())) {  
  11.              removed.add(info);  
  12.              data.remove(i);  
  13.          }  
  14.      }  

更新包信息,若不存在,直接添加应用信息;若已存在,则先删除旧信息,然后添加新信息。
[java]  view plain copy
  1. public void updatePackage(Context context, String packageName) {  
  2.     final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);  
  3.     if (LauncherLog.DEBUG) {  
  4.         LauncherLog.d(TAG, "updatePackage: packageName = " + packageName + ", matches = " + matches.size());  
  5.     }  
  6.   
  7.     if (matches.size() > 0) {  
  8.         // Find disabled/removed activities and remove them from data and add them  
  9.         // to the removed list.  
  10.         for (int i = data.size() - 1; i >= 0; i--) {  
  11.             final AppInfo applicationInfo = data.get(i);  
  12.             final ComponentName component = applicationInfo.intent.getComponent();  
  13.             if (packageName.equals(component.getPackageName())) {  
  14.                 if (!findActivity(matches, component)) {  
  15.                     removed.add(applicationInfo);  
  16.                     mIconCache.remove(component);  
  17.                     data.remove(i);  
  18.                 }  
  19.             }  
  20.         }  
  21.   
  22.         // Find enabled activities and add them to the adapter  
  23.         // Also updates existing activities with new labels/icons  
  24.         int count = matches.size();  
  25.         for (int i = 0; i < count; i++) {  
  26.             final ResolveInfo info = matches.get(i);  
  27.             final String pkgName = info.activityInfo.applicationInfo.packageName;  
  28.             final String className = info.activityInfo.name;  
  29.   
  30.             AppInfo applicationInfo = findApplicationInfoLocked(  
  31.                     info.activityInfo.applicationInfo.packageName,  
  32.                     info.activityInfo.name);  
  33.             if (applicationInfo == null) {  
  34.                 add(new AppInfo(context.getPackageManager(), info, mIconCache, null));  
  35.             } else {  
  36.                 mIconCache.remove(applicationInfo.componentName);  
  37.                 mIconCache.getTitleAndIcon(applicationInfo, info, null);  
  38.                 modified.add(applicationInfo);  
  39.             }  
  40.         }  
  41.     } else {  
  42.         // Remove all data for this package.  
  43.         for (int i = data.size() - 1; i >= 0; i--) {  
  44.             final AppInfo applicationInfo = data.get(i);  
  45.             final ComponentName component = applicationInfo.intent.getComponent();  
  46.             if (packageName.equals(component.getPackageName())) {  
  47.                 if (LauncherLog.DEBUG) {  
  48.                     LauncherLog.d(TAG, "Remove application from launcher: component = " + component);  
  49.                 }  
  50.                 removed.add(applicationInfo);  
  51.                 mIconCache.remove(component);  
  52.                 data.remove(i);  
  53.             }  
  54.         }  
  55.     }  
  56. }  


供MAIN/LAUNCHER通过包名packageName查询包管理器:
[java]  view plain copy
  1. static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) {  
  2.     final PackageManager packageManager = context.getPackageManager();  
  3.   
  4.     final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);  
  5.     mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);  
  6.     mainIntent.setPackage(packageName);  
  7.   
  8.     final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);  
  9.     return apps != null ? apps : new ArrayList<ResolveInfo>();  
  10. }  


根据component是否找到对应的应用:
[java]  view plain copy
  1. /** 
  2.      * Returns whether <em>apps</em> contains <em>component</em>. 
  3.      */  
  4.     private static boolean findActivity(List<ResolveInfo> apps, ComponentName component) {  
  5.         final String className = component.getClassName();  
  6.         for (ResolveInfo info : apps) {  
  7.             final ActivityInfo activityInfo = info.activityInfo;  
  8.             if (activityInfo.name.equals(className)) {  
  9.                 return true;  
  10.             }  
  11.         }  
  12.         return false;  
  13.     }  
  14.   
  15.     /** 
  16.      * Returns whether <em>apps</em> contains <em>component</em>. 
  17.      */  
  18.     private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component) {  
  19.         final int N = apps.size();  
  20.         for (int i=0; i<N; i++) {  
  21.             final AppInfo info = apps.get(i);  
  22.             if (info.componentName.equals(component)) {  
  23.                 return true;  
  24.             }  
  25.         }  
  26.         return false;  
  27.     }  

根据指定的包名packageName,类明className返回一个Component对象:
[java]  view plain copy
  1. private AppInfo findApplicationInfoLocked(String packageName, String className) {  
  2.     for (AppInfo info: data) {  
  3.         final ComponentName component = info.intent.getComponent();  
  4.         if (packageName.equals(component.getPackageName())  
  5.                 && className.equals(component.getClassName())) {  
  6.             return info;  
  7.         }  
  8.     }  
  9.     return null;  
  10. }  


从default_toppackage.xml文件中加载默认顶部应用,加载成功返回true:
[java]  view plain copy
  1. static boolean loadTopPackage(final Context context) {  
  2.     boolean bRet = false;  
  3.     if (sTopPackages != null) {  
  4.         return bRet;  
  5.     }  
  6.   
  7.     sTopPackages = new ArrayList<TopPackage>();  
  8.   
  9.     try {  
  10.         XmlResourceParser parser = context.getResources().getXml(R.xml.default_toppackage);  
  11.         AttributeSet attrs = Xml.asAttributeSet(parser);  
  12.         XmlUtils.beginDocument(parser, TAG_TOPPACKAGES);  
  13.   
  14.         final int depth = parser.getDepth();  
  15.   
  16.         int type = -1;  
  17.         while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)  
  18.                 && type != XmlPullParser.END_DOCUMENT) {  
  19.   
  20.             if (type != XmlPullParser.START_TAG) {  
  21.                 continue;  
  22.             }  
  23.   
  24.             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopPackage);  
  25.   
  26.             sTopPackages.add(new TopPackage(a.getString(R.styleable.TopPackage_topPackageName),  
  27.                     a.getString(R.styleable.TopPackage_topClassName), a.getInt(  
  28.                             R.styleable.TopPackage_topOrder, 0)));  
  29.   
  30.             LauncherLog.d(TAG, "loadTopPackage: packageName = "  
  31.                     + a.getString(R.styleable.TopPackage_topPackageName)  
  32.                     + ", className = "  
  33.                     + a.getString(R.styleable.TopPackage_topClassName));  
  34.   
  35.             a.recycle();  
  36.         }  
  37.     } catch (XmlPullParserException e) {  
  38.         LauncherLog.w(TAG, "Got XmlPullParserException while parsing toppackage.", e);  
  39.     } catch (IOException e) {  
  40.         LauncherLog.w(TAG, "Got IOException while parsing toppackage.", e);  
  41.     }  
  42.   
  43.     return bRet;  
  44. }  


根据应用信息appInfo匹配默认顶部应用的对应应用,返回该应用下标:

[java]  view plain copy
  1. static int getTopPackageIndex(final AppInfo appInfo) {  
  2.     int retIndex = -1;  
  3.     if (sTopPackages == null || sTopPackages.isEmpty() || appInfo == null) {  
  4.         return retIndex;  
  5.     }  
  6.   
  7.     for (TopPackage tp : sTopPackages) {  
  8.         if (appInfo.componentName.getPackageName().equals(tp.packageName)  
  9.                 && appInfo.componentName.getClassName().equals(tp.className)) {  
  10.             retIndex = tp.order;  
  11.             break;  
  12.         }  
  13.     }  
  14.   
  15.     return retIndex;  
  16. }  


重排默认顶部应用所有应用的下标:
[java]  view plain copy
  1. void reorderApplist() {  
  2.     final long sortTime = DEBUG_LOADERS_REORDER ? SystemClock.uptimeMillis() : 0;  
  3.   
  4.     if (sTopPackages == null || sTopPackages.isEmpty()) {  
  5.         return;  
  6.     }  
  7.     ensureTopPackageOrdered();  
  8.   
  9.     final ArrayList<AppInfo> dataReorder = new ArrayList<AppInfo>(  
  10.             DEFAULT_APPLICATIONS_NUMBER);  
  11.   
  12.     for (TopPackage tp : sTopPackages) {  
  13.         int loop = 0;  
  14.         for (AppInfo ai : added) {  
  15.             if (DEBUG_LOADERS_REORDER) {  
  16.                 LauncherLog.d(TAG, "reorderApplist: remove loop = " + loop);  
  17.             }  
  18.   
  19.             if (ai.componentName.getPackageName().equals(tp.packageName)  
  20.                     && ai.componentName.getClassName().equals(tp.className)) {  
  21.                 if (DEBUG_LOADERS_REORDER) {  
  22.                     LauncherLog.d(TAG, "reorderApplist: remove packageName = "  
  23.                             + ai.componentName.getPackageName());  
  24.                 }  
  25.                 data.remove(ai);  
  26.                 dataReorder.add(ai);  
  27.                 dumpData();  
  28.                 break;  
  29.             }  
  30.             loop++;  
  31.         }  
  32.     }  
  33.   
  34.     for (TopPackage tp : sTopPackages) {  
  35.         int loop = 0;  
  36.         int newIndex = 0;  
  37.         for (AppInfo ai : dataReorder) {  
  38.             if (DEBUG_LOADERS_REORDER) {  
  39.                 LauncherLog.d(TAG, "reorderApplist: added loop = " + loop + ", packageName = "  
  40.                         + ai.componentName.getPackageName());  
  41.             }  
  42.   
  43.             if (ai.componentName.getPackageName().equals(tp.packageName)  
  44.                     && ai.componentName.getClassName().equals(tp.className)) {  
  45.                 newIndex = Math.min(Math.max(tp.order, 0), added.size());  
  46.                 if (DEBUG_LOADERS_REORDER) {  
  47.                     LauncherLog.d(TAG, "reorderApplist: added newIndex = " + newIndex);  
  48.                 }  
  49.                 /// M: make sure the array list not out of bound  
  50.                 if (newIndex < data.size()) {  
  51.                     data.add(newIndex, ai);  
  52.                 } else {  
  53.                     data.add(ai);  
  54.                 }  
  55.                 dumpData();  
  56.                 break;  
  57.             }  
  58.             loop++;  
  59.         }  
  60.     }  
  61.   
  62.     if (added.size() == data.size()) {  
  63.         added = (ArrayList<AppInfo>) data.clone();  
  64.         LauncherLog.d(TAG, "reorderApplist added.size() == data.size()");  
  65.     }  
  66.   
  67.     if (DEBUG_LOADERS_REORDER) {  
  68.         LauncherLog.d(TAG, "sort and reorder took " + (SystemClock.uptimeMillis() - sortTime) + "ms");  
  69.     }  
  70. }  


Dump掉data列表中的应用信息:

[java]  view plain copy
  1. void dumpData() {  
  2.     int loop2 = 0;  
  3.     for (AppInfo ai : data) {  
  4.         if (DEBUG_LOADERS_REORDER) {  
  5.             LauncherLog.d(TAG, "reorderApplist data loop2 = " + loop2);  
  6.             LauncherLog.d(TAG, "reorderApplist data packageName = "  
  7.                     + ai.componentName.getPackageName());  
  8.         }  
  9.         loop2++;  
  10.     }  
  11. }  

确保top_package.xml中条目有序,以防特殊情况下top_package.xml会使数组列表越界:
[java]  view plain copy
  1. static void ensureTopPackageOrdered() {  
  2.     ArrayList<TopPackage> tpOrderList = new ArrayList<TopPackage>(DEFAULT_APPLICATIONS_NUMBER);  
  3.     boolean bFirst = true;  
  4.     for (TopPackage tp : sTopPackages) {  
  5.         if (bFirst) {  
  6.             tpOrderList.add(tp);  
  7.             bFirst = false;  
  8.         } else {  
  9.             for (int i = tpOrderList.size() - 1; i >= 0; i--) {  
  10.                 TopPackage tpItor = tpOrderList.get(i);  
  11.                 if (0 == i) {  
  12.                     if (tp.order < tpOrderList.get(0).order) {  
  13.                         tpOrderList.add(0, tp);  
  14.                     } else {  
  15.                         tpOrderList.add(1, tp);  
  16.                     }  
  17.                     break;  
  18.                 }  
  19.   
  20.                 if ((tp.order < tpOrderList.get(i).order)  
  21.                     && (tp.order >= tpOrderList.get(i - 1).order)) {  
  22.                     tpOrderList.add(i, tp);  
  23.                     break;  
  24.                 } else if (tp.order > tpOrderList.get(i).order) {  
  25.                     tpOrderList.add(i + 1, tp);  
  26.                     break;  
  27.                 }  
  28.             }  
  29.         }  
  30.     }  
  31.   
  32.     if (sTopPackages.size() == tpOrderList.size()) {  
  33.         sTopPackages = (ArrayList<TopPackage>) tpOrderList.clone();  
  34.         tpOrderList = null;  
  35.         LauncherLog.d(TAG, "ensureTopPackageOrdered done");  
  36.     } else {  
  37.         LauncherLog.d(TAG, "some mistake may occur when ensureTopPackageOrdered");  
  38.     }  
  39. }  


添加应用信息到add列表,注意不是added列表:
[java]  view plain copy
  1. public void addApp(final AppInfo info) {  
  2.     if (LauncherLog.DEBUG_EDIT) {  
  3.         LauncherLog.d(TAG, "Add application to data list: app = " + info.componentName);  
  4.     }  
  5.   
  6.     if (findActivity(data, info.componentName)) {  
  7.         LauncherLog.i(TAG, "The app " + info + " is already exist in data list.");  
  8.         return;  
  9.     }  
  10.     data.add(info);  
  11. }  

AppInfo

这个类也很关键,不过相对AllAppList来讲要简单很多,从代码量上就可以看出来。主要是App的详细信息,包括图标,初次安装时间之类的,下面细看AppInfo的成员变量和函数:

图标:

[java]  view plain copy
  1. Bitmap iconBitmap;  

应用的初次安装时间:

[java]  view plain copy
  1. long firstInstallTime;  


应用图标是否显示,true为显示。

[java]  view plain copy
  1. boolean isVisible = true;  

无参初始化,为应用创建快捷方式:
[java]  view plain copy
  1. AppInfo() {  
  2.        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;  
  3.    }  


得到应用的图标和标题:

[java]  view plain copy
  1. public AppInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,  
  2.           HashMap<Object, CharSequence> labelCache) {  
  3.       final String packageName = info.activityInfo.applicationInfo.packageName;  
  4.   
  5.       this.componentName = new ComponentName(packageName, info.activityInfo.name);  
  6.       this.container = ItemInfo.NO_ID;  
  7.       this.setActivity(componentName,  
  8.               Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);  
  9.   
  10.       this.setFlagAndInstallTime(pm, packageName);  
  11.       iconCache.getTitleAndIcon(this, info, labelCache);  
  12.   }  


初始化标志信息,指明应用是系统应用,还是用户下载:
[java]  view plain copy
  1. public static int initFlags(PackageInfo pi) {  
  2.     int appFlags = pi.applicationInfo.flags;  
  3.     int flags = 0;  
  4.     if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {  
  5.         flags |= DOWNLOADED_FLAG;  
  6.   
  7.         if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {  
  8.             flags |= UPDATED_SYSTEM_APP_FLAG;  
  9.         }  
  10.     }  
  11.     return flags;  
  12. }  


AppInfp的成员接口,可以获取组件名componentName,标题title,intent,标志flags,初次安装时间firstInstallTime等信息:
[java]  view plain copy
  1. public AppInfo(AppInfo info) {  
  2.     super(info);  
  3.     componentName = info.componentName;  
  4.     title = info.title.toString();  
  5.     intent = new Intent(info.intent);  
  6.     flags = info.flags;  
  7.     firstInstallTime = info.firstInstallTime;  
  8.   
  9.     /// M: copy value from source applcation info.  
  10.     pos = info.pos;  
  11.     isVisible = info.isVisible;  
  12.     stateChanged = info.stateChanged;  
  13. }  


根据类名和launchFlags创建应用Intent:
[java]  view plain copy
  1. final void setActivity(ComponentName className, int launchFlags) {  
  2.     intent = new Intent(Intent.ACTION_MAIN);  
  3.     intent.addCategory(Intent.CATEGORY_LAUNCHER);  
  4.     intent.setComponent(className);  
  5.     intent.setFlags(launchFlags);  
  6.     itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;  
  7. }  


设置标志,记录初次安装时间:
[java]  view plain copy
  1. public void setFlagAndInstallTime(final PackageManager pm, String packageName) {  
  2.     try {  
  3.         PackageInfo pi = pm.getPackageInfo(packageName, 0);  
  4.         flags = initFlags(pi);  
  5.         firstInstallTime = initFirstInstallTime(pi);  
  6.     } catch (NameNotFoundException e) {  
  7.         Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);  
  8.     }  
  9. }  
[java]  view plain copy
  1. public void setFlagAndInstallTime(final PackageManager pm) {  
  2.     String packageName = getPackageName();  
  3.     setFlagAndInstallTime(pm, packageName);  
  4. }  

返回快捷方式要启动应用的包名,如不存在则返回一个空字符串:
[java]  view plain copy
  1. String getPackageName() {  
  2.     String packageName = "";  
  3.     if (intent != null) {  
  4.         packageName = intent.getPackage();  
  5.         if (packageName == null && intent.getComponent() != null) {  
  6.             packageName = intent.getComponent().getPackageName();  
  7.         }          
  8.     }  
  9.     return packageName;  
  10. }  




今天就先这两个类吧,已经不短了。不过源码真心长,真心多,真心眼花缭乱,慢慢啃吧。



今天来看一下CropView和DynamicGrid两部分。

CropView是设置壁纸时通过缩放手势截取图片,DynamicGrid则是根据设备来指定图标显示的行列,下面细看一下:

CropView

为了便于查看,吸取前两篇的教训,直接把注释写在代码里,成员变量:

[java]  view plain copy
  1. private ScaleGestureDetector mScaleGestureDetector;  
  2. private long mTouchDownTime;  
  3. private float mFirstX, mFirstY;<span style="white-space:pre"> </span>// 初始坐标位置  
  4. private float mLastX, mLastY;<span style="white-space:pre">   </span>// 结束坐标位置  
  5. private float mCenterX, mCenterY;<span style="white-space:pre">   </span>// 中间坐标位置  
  6. private float mMinScale;  
  7. private boolean mTouchEnabled = true;  
  8. private RectF mTempEdges = new RectF();  
  9. private float[] mTempPoint = new float[] { 00 };  
  10. private float[] mTempCoef = new float[] { 00 };  
  11. private float[] mTempAdjustment = new float[] { 00 };  
  12. private float[] mTempImageDims = new float[] { 00 };  
  13. private float[] mTempRendererCenter = new float[] { 00 };  
  14. TouchCallback mTouchCallback;  
  15. Matrix mRotateMatrix;  
  16. Matrix mInverseRotateMatrix;  

成员函数:

CropView的构造函数:

[java]  view plain copy
  1. public CropView(Context context) {  
  2.     this(context, null);  
  3. }  
  4.   
  5. public CropView(Context context, AttributeSet attrs) {  
  6.     super(context, attrs);  
  7.     mScaleGestureDetector = new ScaleGestureDetector(context, this);  
  8.     mRotateMatrix = new Matrix();  
  9.     mInverseRotateMatrix = new Matrix();  
  10. }  

得到图像的分辨率:

[java]  view plain copy
  1. private float[] getImageDims() {  
  2.     final float imageWidth = mRenderer.source.getImageWidth();<span style="white-space:pre">  </span>// 图像宽度  
  3.     final float imageHeight = mRenderer.source.getImageHeight();<span style="white-space:pre">    </span>// 图像高度  
  4.     float[] imageDims = mTempImageDims;  
  5.     imageDims[0] = imageWidth;  
  6.     imageDims[1] = imageHeight;  
  7.     mRotateMatrix.mapPoints(imageDims);  
  8.     imageDims[0] = Math.abs(imageDims[0]);  
  9.     imageDims[1] = Math.abs(imageDims[1]);  
  10.     return imageDims;  
  11. }  


得到上下左右的边界:
[java]  view plain copy
  1. private void getEdgesHelper(RectF edgesOut) {  
  2.     final float width = getWidth();  
  3.     final float height = getHeight();  
  4.     final float[] imageDims = getImageDims();  
  5.     final float imageWidth = imageDims[0];  
  6.     final float imageHeight = imageDims[1];  
  7.   
  8.     float initialCenterX = mRenderer.source.getImageWidth() / 2f;  
  9.     float initialCenterY = mRenderer.source.getImageHeight() / 2f;  
  10.   
  11.     float[] rendererCenter = mTempRendererCenter;  
  12.     rendererCenter[0] = mCenterX - initialCenterX;  
  13.     rendererCenter[1] = mCenterY - initialCenterY;  
  14.     mRotateMatrix.mapPoints(rendererCenter);  
  15.     rendererCenter[0] += imageWidth / 2;  
  16.     rendererCenter[1] += imageHeight / 2;  
  17.   
  18.     final float scale = mRenderer.scale;  
  19.     float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)  
  20.             * scale + width / 2f;  
  21.     float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)  
  22.             * scale + height / 2f;  
  23.     float leftEdge = centerX - imageWidth / 2f * scale;  
  24.     float rightEdge = centerX + imageWidth / 2f * scale;  
  25.     float topEdge = centerY - imageHeight / 2f * scale;  
  26.     float bottomEdge = centerY + imageHeight / 2f * scale;  
  27.   
  28.     edgesOut.left = leftEdge;  
  29.     edgesOut.right = rightEdge;  
  30.     edgesOut.top = topEdge;  
  31.     edgesOut.bottom = bottomEdge;  
  32. }  


得到截取的形状:
[java]  view plain copy
  1. public RectF getCrop() {  
  2.     final RectF edges = mTempEdges;  
  3.     getEdgesHelper(edges);  
  4.     final float scale = mRenderer.scale;  
  5.   
  6.     float cropLeft = -edges.left / scale;  
  7.     float cropTop = -edges.top / scale;  
  8.     float cropRight = cropLeft + getWidth() / scale;  
  9.     float cropBottom = cropTop + getHeight() / scale;  
  10.   
  11.     return new RectF(cropLeft, cropTop, cropRight, cropBottom);  
  12. }  

设置TileSource,根据TileSource更新Scale:

[java]  view plain copy
  1. public void setTileSource(TileSource source, Runnable isReadyCallback) {  
  2.         super.setTileSource(source, isReadyCallback);  
  3.         mCenterX = mRenderer.centerX;  
  4.         mCenterY = mRenderer.centerY;  
  5.         mRotateMatrix.reset();  
  6.         mRotateMatrix.setRotate(mRenderer.rotation);  
  7.         mInverseRotateMatrix.reset();  
  8.         mInverseRotateMatrix.setRotate(-mRenderer.rotation);  
  9.         updateMinScale(getWidth(), getHeight(), source, true);  
  10.     }  


宽和高改变后,重新设置Scale:

[java]  view plain copy
  1. protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  2.         updateMinScale(w, h, mRenderer.source, false);  
  3.     }  
  4.   
  5.     public void setScale(float scale) {  
  6.         synchronized (mLock) {  
  7.             mRenderer.scale = scale;  
  8.         }  
  9.     }  
  10.   
  11.     private void updateMinScale(int w, int h, TileSource source,  
  12.             boolean resetScale) {  
  13.         synchronized (mLock) {  
  14.             if (resetScale) {  
  15.                 mRenderer.scale = 1;  
  16.             }  
  17.             if (source != null) {  
  18.                 final float[] imageDims = getImageDims();  
  19.                 final float imageWidth = imageDims[0];  
  20.                 final float imageHeight = imageDims[1];  
  21.                 mMinScale = Math.max(w / imageWidth, h / imageHeight);  
  22.                 mRenderer.scale = Math.max(mMinScale, mRenderer.scale);  
  23.             }  
  24.         }  
  25.     }  

核心的来了,触摸响应:

[java]  view plain copy
  1. public boolean onTouchEvent(MotionEvent event) {  
  2.         int action = event.getActionMasked();  
  3.         final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;  
  4.         final int skipIndex = pointerUp ? event.getActionIndex() : -1;  
  5.   
  6.         // Determine focal point  
  7.         float sumX = 0, sumY = 0;  
  8.         final int count = event.getPointerCount();  
  9.         for (int i = 0; i < count; i++) {  
  10.             if (skipIndex == i)  
  11.                 continue;  
  12.             sumX += event.getX(i);  
  13.             sumY += event.getY(i);  
  14.         }  
  15.         final int div = pointerUp ? count - 1 : count;  
  16.         float x = sumX / div;  
  17.         float y = sumY / div;  
  18.   
  19.         if (action == MotionEvent.ACTION_DOWN) {<span style="white-space:pre">    </span>// 响应按下事件  
  20.             mFirstX = x;  
  21.             mFirstY = y;  
  22.             mTouchDownTime = System.currentTimeMillis();  
  23.             if (mTouchCallback != null) {  
  24.                 mTouchCallback.onTouchDown();  
  25.             }  
  26.         } else if (action == MotionEvent.ACTION_UP) {<span style="white-space:pre">   </span>// 响应抬起事件  
  27.             ViewConfiguration config = ViewConfiguration.get(getContext());  
  28.   
  29.             float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y)  
  30.                     * (mFirstY - y);  
  31.             float slop = config.getScaledTouchSlop()  
  32.                     * config.getScaledTouchSlop();  
  33.             long now = System.currentTimeMillis();  
  34.             if (mTouchCallback != null) {  
  35.                 // only do this if it's a small movement  
  36.                 if (squaredDist < slop  
  37.                         && now < mTouchDownTime  
  38.                                 + ViewConfiguration.getTapTimeout()) {  
  39.                     mTouchCallback.onTap();  
  40.                 }  
  41.                 mTouchCallback.onTouchUp();  
  42.             }  
  43.         }  
  44.   
  45.         if (!mTouchEnabled) {  
  46.             return true;  
  47.         }  
  48.   
  49.         synchronized (mLock) {<span style="white-space:pre">      </span>// 同步锁  
  50.             mScaleGestureDetector.onTouchEvent(event);<span style="white-space:pre">  </span>// 手势缩放  
  51.             switch (action) {  
  52.             case MotionEvent.ACTION_MOVE:  
  53.                 float[] point = mTempPoint;  
  54.                 point[0] = (mLastX - x) / mRenderer.scale;  
  55.                 point[1] = (mLastY - y) / mRenderer.scale;  
  56.                 mInverseRotateMatrix.mapPoints(point);  
  57.                 mCenterX += point[0];  
  58.                 mCenterY += point[1];  
  59.                 updateCenter();<span style="white-space:pre">     </span>// 更新中心点  
  60.                 invalidate();  
  61.                 break;  
  62.             }  
  63.             if (mRenderer.source != null) {  
  64.                 // Adjust position so that the wallpaper covers the entire area  
  65.                 // of the screen  
  66.                 final RectF edges = mTempEdges;  
  67.                 getEdgesHelper(edges);  
  68.                 final float scale = mRenderer.scale;  
  69.   
  70.                 float[] coef = mTempCoef;  
  71.                 coef[0] = 1;  
  72.                 coef[1] = 1;  
  73.                 mRotateMatrix.mapPoints(coef);  
  74.                 float[] adjustment = mTempAdjustment;  
  75.                 mTempAdjustment[0] = 0;  
  76.                 mTempAdjustment[1] = 0;  
  77.                 if (edges.left > 0) {  
  78.                     adjustment[0] = edges.left / scale;  
  79.                 } else if (edges.right < getWidth()) {  
  80.                     adjustment[0] = (edges.right - getWidth()) / scale;  
  81.                 }  
  82.                 if (edges.top > 0) {  
  83.                     adjustment[1] = FloatMath.ceil(edges.top / scale);  
  84.                 } else if (edges.bottom < getHeight()) {  
  85.                     adjustment[1] = (edges.bottom - getHeight()) / scale;  
  86.                 }  
  87.                 for (int dim = 0; dim <= 1; dim++) {  
  88.                     if (coef[dim] > 0)  
  89.                         adjustment[dim] = FloatMath.ceil(adjustment[dim]);  
  90.                 }  
  91.   
  92.                 mInverseRotateMatrix.mapPoints(adjustment);  
  93.                 mCenterX += adjustment[0];  
  94.                 mCenterY += adjustment[1];  
  95.                 updateCenter();  
  96.             }  
  97.         }  
  98.   
  99.         mLastX = x;  
  100.         mLastY = y;  
  101.         return true;  
  102.     }  





DynamicGrid

动态网格?先看两个核心类:

查询获取设备信息,宽和高以及分辨率:

[java]  view plain copy
  1. class DeviceProfileQuery {  
  2.     float widthDps;  
  3.     float heightDps;  
  4.     float value;  
  5.     PointF dimens;  
  6.   
  7.     DeviceProfileQuery(float w, float h, float v) {  
  8.         widthDps = w;  
  9.         heightDps = h;  
  10.         value = v;  
  11.         dimens = new PointF(w, h);  
  12.     }  
  13. }  

下面这个就长了,洋洋洒洒几百行:

[java]  view plain copy
  1. class DeviceProfile {  
  2.     String name;  
  3.     float minWidthDps;  
  4.     float minHeightDps;  
  5.     float numRows;  
  6.     float numColumns;  
  7.     float iconSize;  
  8.     float iconTextSize;  
  9.     float numHotseatIcons;  
  10.     float hotseatIconSize;  
  11.   
  12.     boolean isLandscape;  
  13.     boolean isTablet;  
  14.     boolean isLargeTablet;  
  15.     boolean transposeLayoutWithOrientation;  
  16.   
  17.     int desiredWorkspaceLeftRightMarginPx;  
  18.     int edgeMarginPx;  
  19.     Rect defaultWidgetPadding;  
  20.   
  21.     int widthPx;  
  22.     int heightPx;  
  23.     int availableWidthPx;  
  24.     int availableHeightPx;  
  25.     int iconSizePx;<span style="white-space:pre">     </span>// 图标大小  
  26.     int iconTextSizePx;<span style="white-space:pre">     </span>// 文字大小  
  27.     int cellWidthPx;  
  28.     int cellHeightPx;  
  29.     int folderBackgroundOffset;<span style="white-space:pre"> </span>// 文件夹背景  
  30.     int folderIconSizePx;<span style="white-space:pre">   </span>// 文件夹图标像素大小  
  31.     int folderCellWidthPx;  
  32.     int folderCellHeightPx;  
  33.     int hotseatCellWidthPx;<span style="white-space:pre"> </span>// 固定热键宽度  
  34.     int hotseatCellHeightPx;<span style="white-space:pre">    </span>// 固定热键高度  
  35.     int hotseatIconSizePx;  
  36.     int hotseatBarHeightPx;<span style="white-space:pre"> </span>// 固定热键上端的分割线  
  37.     int hotseatAllAppsRank;  
  38.     int allAppsNumRows;<span style="white-space:pre">     </span>// 应用行数  
  39.     int allAppsNumCols;<span style="white-space:pre">     </span>// 应用列数  
  40.     int searchBarSpaceWidthPx;<span style="white-space:pre">      </span>// 搜索条  
  41.     int searchBarSpaceMaxWidthPx;  
  42.     int searchBarSpaceHeightPx;  
  43.     int searchBarHeightPx;  
  44.     int pageIndicatorHeightPx;  
  45.   
  46.     private static String APP_COUNTX = SystemProperties.get("ro.appscellcountx.value");  
  47.     private static String APP_COUNTY = SystemProperties.get("ro.appscellcounty.value");  
  48.     private static int MAX_ROW = 12;<span style="white-space:pre">    </span>// 最大行  
  49.     private static int MAX_COLS = 12;<span style="white-space:pre">   </span>// 最大列  
  50.     private static int MIN_ROW = 4;<span style="white-space:pre"> </span>// 最小行  
  51.     private static int MIN_COLS = 4;<span style="white-space:pre">    </span>// 最小列  
  52.     private static boolean IS_CONFIG = !TextUtils.isEmpty(APP_COUNTX) & !TextUtils.isEmpty(APP_COUNTY);  
  53.     private static boolean IS_AVAILABLY = (Integer.parseInt(APP_COUNTX) > MIN_ROW)  &   
  54.                     (Integer.parseInt(APP_COUNTX) < MAX_ROW)  &  
  55.                     (Integer.parseInt(APP_COUNTY) > MIN_COLS) &   
  56.                                     (Integer.parseInt(APP_COUNTY) < MAX_COLS);  
  57.   
  58.     DeviceProfile(String n, float w, float h, float r, float c,  
  59.                   float is, float its, float hs, float his) {  
  60.         // Ensure that we have an odd number of hotseat items (since we need to place all apps)  
  61.         if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {<span style="white-space:pre">  </span>// 热键数目必须是奇数,因为中间是应用抽屉的入口  
  62.             throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");  
  63.         }  
  64.   
  65.         name = n;  
  66.         minWidthDps = w;  
  67.         minHeightDps = h;  
  68.         numRows = r;  
  69.         numColumns = c;  
  70.         iconSize = is;  
  71.         iconTextSize = its;  
  72.         numHotseatIcons = hs;  
  73.         hotseatIconSize = his;  
  74.     }  
  75.   
  76.     DeviceProfile(Context context,  
  77.                   ArrayList<DeviceProfile> profiles,  
  78.                   float minWidth, float minHeight,  
  79.                   int wPx, int hPx,  
  80.                   int awPx, int ahPx,  
  81.                   Resources resources) {  
  82.         DisplayMetrics dm = resources.getDisplayMetrics();  
  83.         ArrayList<DeviceProfileQuery> points =  
  84.                 new ArrayList<DeviceProfileQuery>();  
  85.         transposeLayoutWithOrientation =  
  86.                 resources.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);  
  87.         minWidthDps = minWidth;  
  88.         minHeightDps = minHeight;  
  89.   
  90.         ComponentName cn = new ComponentName(context.getPackageName(),  
  91.                 this.getClass().getName());  
  92.         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);  
  93.         edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);  
  94.         desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;  
  95.         pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);  
  96.   
  97.         // 列  
  98.         for (DeviceProfile p : profiles) {  
  99.             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));  
  100.         }  
  101.         numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));  
  102.         // 行  
  103.         points.clear();  
  104.         for (DeviceProfile p : profiles) {  
  105.             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));  
  106.         }  
  107.         numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));  
  108.         // 图标大小  
  109.         points.clear();  
  110.         for (DeviceProfile p : profiles) {  
  111.             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));  
  112.         }  
  113.         iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);  
  114.         iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);  
  115.   
  116.         // 文字大小  
  117.         points.clear();  
  118.         for (DeviceProfile p : profiles) {  
  119.             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));  
  120.         }  
  121.         iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);  
  122.         iconTextSizePx = DynamicGrid.pxFromSp(iconTextSize, dm);  
  123.   
  124.         // 热键大小  
  125.         points.clear();  
  126.         for (DeviceProfile p : profiles) {  
  127.             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));  
  128.         }  
  129.         numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));  
  130.         // Interpolate the hotseat icon size  
  131.         points.clear();  
  132.         for (DeviceProfile p : profiles) {  
  133.             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));  
  134.         }  
  135.         // 热键  
  136.         hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);  
  137.         hotseatIconSizePx = DynamicGrid.pxFromDp(hotseatIconSize, dm);  
  138.         hotseatAllAppsRank = (int) (numColumns / 2);  
  139.   
  140.         // Calculate other vars based on Configuration  
  141.         updateFromConfiguration(resources, wPx, hPx, awPx, ahPx);  
  142.   
  143.         // 搜索条  
  144.         searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);  
  145.         searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);  
  146.         searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);  
  147.         searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;  
  148.   
  149.         // 计算实际标题大小  
  150.         Paint textPaint = new Paint();  
  151.         textPaint.setTextSize(iconTextSizePx);  
  152.         FontMetrics fm = textPaint.getFontMetrics();  
  153.         cellWidthPx = iconSizePx;  
  154.         cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);  
  155.   
  156.         // At this point, if the cells do not fit into the available height, then we need  
  157.         // to shrink the icon size  
  158.         /* 
  159.         Rect padding = getWorkspacePadding(isLandscape ? 
  160.                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 
  161.         int h = (int) (numRows * cellHeightPx) + padding.top + padding.bottom; 
  162.         if (h > availableHeightPx) { 
  163.             float delta = h - availableHeightPx; 
  164.             int deltaPx = (int) Math.ceil(delta / numRows); 
  165.             iconSizePx -= deltaPx; 
  166.             iconSize = DynamicGrid.dpiFromPx(iconSizePx, dm); 
  167.             cellWidthPx = iconSizePx; 
  168.             cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top); 
  169.         } 
  170.         */  
  171.   
  172.         // 热键  
  173.         hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;  
  174.         hotseatCellWidthPx = iconSizePx;  
  175.         hotseatCellHeightPx = iconSizePx;  
  176.   
  177.         // 应用文件夹  
  178.         folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;  
  179.         folderCellHeightPx = cellHeightPx + (int) ((3f/2f) * edgeMarginPx);  
  180.         folderBackgroundOffset = -edgeMarginPx;  
  181.         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;  
  182.     }  
  183.   
  184.     void updateFromConfiguration(Resources resources, int wPx, int hPx,  
  185.                                  int awPx, int ahPx) {  
  186.         isLandscape = (resources.getConfiguration().orientation ==  
  187.                 Configuration.ORIENTATION_LANDSCAPE);  
  188.         isTablet = resources.getBoolean(R.bool.is_tablet);  
  189.         isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);  
  190.         widthPx = wPx;  
  191.         heightPx = hPx;  
  192.         availableWidthPx = awPx;  
  193.         availableHeightPx = ahPx;  
  194.   
  195.         Rect padding = getWorkspacePadding(isLandscape ?  
  196.                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);  
  197.         int pageIndicatorOffset =  
  198.             resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);  
  199.       
  200.     if (IS_CONFIG && IS_AVAILABLY) {   
  201.         if (isLandscape) {   
  202.             allAppsNumRows = Integer.parseInt(APP_COUNTX);  
  203.             allAppsNumCols = Integer.parseInt(APP_COUNTY);  
  204.         } else {  
  205.             allAppsNumRows = Integer.parseInt(APP_COUNTY);  
  206.             allAppsNumCols = Integer.parseInt(APP_COUNTX);  
  207.         }  
  208.     } else {  
  209.             if (isLandscape) {  
  210.                     allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /  
  211.                         (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);  
  212.             } else {  
  213.                     allAppsNumRows = (int) numRows + 1;  
  214.             }  
  215.             allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /  
  216.                     (iconSizePx + 2 * edgeMarginPx);  
  217.     }  
  218.     }  
  219.   
  220.     private float dist(PointF p0, PointF p1) {  
  221.         return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +  
  222.                 (p1.y-p0.y)*(p1.y-p0.y));  
  223.     }  
  224.   
  225.     private float weight(PointF a, PointF b,  
  226.                         float pow) {  
  227.         float d = dist(a, b);  
  228.         if (d == 0f) {  
  229.             return Float.POSITIVE_INFINITY;  
  230.         }  
  231.         return (float) (1f / Math.pow(d, pow));  
  232.     }  
  233.   
  234.     private float invDistWeightedInterpolate(float width, float height,  
  235.                 ArrayList<DeviceProfileQuery> points) {  
  236.         float sum = 0;  
  237.         float weights = 0;  
  238.         float pow = 5;  
  239.         float kNearestNeighbors = 3;  
  240.         final PointF xy = new PointF(width, height);  
  241.   
  242.         ArrayList<DeviceProfileQuery> pointsByNearness = points;  
  243.         Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {  
  244.             public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {  
  245.                 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));  
  246.             }  
  247.         });  
  248.   
  249.         for (int i = 0; i < pointsByNearness.size(); ++i) {  
  250.             DeviceProfileQuery p = pointsByNearness.get(i);  
  251.             if (i < kNearestNeighbors) {  
  252.                 float w = weight(xy, p.dimens, pow);  
  253.                 if (w == Float.POSITIVE_INFINITY) {  
  254.                     return p.value;  
  255.                 }  
  256.                 weights += w;  
  257.             }  
  258.         }  
  259.   
  260.         for (int i = 0; i < pointsByNearness.size(); ++i) {  
  261.             DeviceProfileQuery p = pointsByNearness.get(i);  
  262.             if (i < kNearestNeighbors) {  
  263.                 float w = weight(xy, p.dimens, pow);  
  264.                 sum += w * p.value / weights;  
  265.             }  
  266.         }  
  267.   
  268.         return sum;  
  269.     }  
  270.   
  271.     Rect getWorkspacePadding(int orientation) {  
  272.         Rect padding = new Rect();  
  273.         if (orientation == CellLayout.LANDSCAPE &&  
  274.                 transposeLayoutWithOrientation) {  
  275.             // Pad the left and right of the workspace with search/hotseat bar sizes  
  276.             padding.set(searchBarSpaceHeightPx, edgeMarginPx,  
  277.                     hotseatBarHeightPx, edgeMarginPx);  
  278.         } else {  
  279.             if (isTablet()) {  
  280.                 // Pad the left and right of the workspace to ensure consistent spacing  
  281.                 // between all icons  
  282.                 int width = (orientation == CellLayout.LANDSCAPE)  
  283.                         ? Math.max(widthPx, heightPx)  
  284.                         : Math.min(widthPx, heightPx);  
  285.                 // XXX: If the icon size changes across orientations, we will have to take  
  286.                 //      that into account here too.  
  287.                 int gap = (int) ((width - 2 * edgeMarginPx -  
  288.                         (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));  
  289.                 padding.set(edgeMarginPx + gap,  
  290.                         searchBarSpaceHeightPx,  
  291.                         edgeMarginPx + gap,  
  292.                         hotseatBarHeightPx + pageIndicatorHeightPx);  
  293.             } else {  
  294.                 // Pad the top and bottom of the workspace with search/hotseat bar sizes  
  295.                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,  
  296.                         searchBarSpaceHeightPx,  
  297.                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,  
  298.                         hotseatBarHeightPx + pageIndicatorHeightPx);  
  299.             }  
  300.         }  
  301.         return padding;  
  302.     }  
  303.   
  304.     // The rect returned will be extended to below the system ui that covers the workspace  
  305.     Rect getHotseatRect() {  
  306.         if (isVerticalBarLayout()) {  
  307.             return new Rect(availableWidthPx - hotseatBarHeightPx, 0,  
  308.                     Integer.MAX_VALUE, availableHeightPx);  
  309.         } else {  
  310.             return new Rect(0, availableHeightPx - hotseatBarHeightPx,  
  311.                     availableWidthPx, Integer.MAX_VALUE);  
  312.         }  
  313.     }  
  314.   
  315.     int calculateCellWidth(int width, int countX) {  
  316.         return width / countX;  
  317.     }  
  318.     int calculateCellHeight(int height, int countY) {  
  319.         return height / countY;  
  320.     }  
  321.   
  322.     boolean isPhone() {  
  323.         return !isTablet && !isLargeTablet;  
  324.     }  
  325.     boolean isTablet() {  
  326.         return isTablet;  
  327.     }  
  328.     boolean isLargeTablet() {  
  329.         return isLargeTablet;  
  330.     }  
  331.   
  332.     boolean isVerticalBarLayout() {  
  333.         return isLandscape && transposeLayoutWithOrientation;  
  334.     }  
  335.   
  336.     public void layout(Launcher launcher) {  
  337.         FrameLayout.LayoutParams lp;  
  338.         Resources res = launcher.getResources();  
  339.         boolean hasVerticalBarLayout = isVerticalBarLayout();  
  340.   
  341.         // Layout the search bar space  
  342.         View searchBar = launcher.getSearchBar();  
  343.         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();  
  344.         if (hasVerticalBarLayout) {  
  345.             // Vertical search bar  
  346.             lp.gravity = Gravity.TOP | Gravity.LEFT;  
  347.             lp.width = searchBarSpaceHeightPx;  
  348.             lp.height = LayoutParams.MATCH_PARENT;  
  349.             searchBar.setPadding(  
  350.                     02 * edgeMarginPx, 0,  
  351.                     2 * edgeMarginPx);  
  352.         } else {  
  353.             // Horizontal search bar  
  354.             lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;  
  355.             lp.width = searchBarSpaceWidthPx;  
  356.             lp.height = searchBarSpaceHeightPx;  
  357.             searchBar.setPadding(  
  358.                     2 * edgeMarginPx,  
  359.                     2 * edgeMarginPx,  
  360.                     2 * edgeMarginPx, 0);  
  361.         }  
  362.         searchBar.setLayoutParams(lp);  
  363.   
  364.         // Layout the search bar  
  365.         View qsbBar = launcher.getQsbBar();  
  366.         LayoutParams vglp = qsbBar.getLayoutParams();  
  367.         vglp.width = LayoutParams.MATCH_PARENT;  
  368.         vglp.height = LayoutParams.MATCH_PARENT;  
  369.         qsbBar.setLayoutParams(vglp);  
  370.   
  371.         // Layout the voice proxy  
  372.         View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);  
  373.         if (voiceButtonProxy != null) {  
  374.             if (hasVerticalBarLayout) {  
  375.                 // TODO: MOVE THIS INTO SEARCH BAR MEASURE  
  376.             } else {  
  377.                 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();  
  378.                 lp.gravity = Gravity.TOP | Gravity.END;  
  379.                 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +  
  380.                         2 * iconSizePx;  
  381.                 lp.height = searchBarSpaceHeightPx;  
  382.             }  
  383.         }  
  384.   
  385.         // Layout the workspace  
  386.         View workspace = launcher.findViewById(R.id.workspace);  
  387.         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();  
  388.         lp.gravity = Gravity.CENTER;  
  389.         Rect padding = getWorkspacePadding(isLandscape  
  390.                 ? CellLayout.LANDSCAPE  
  391.                 : CellLayout.PORTRAIT);  
  392.         workspace.setPadding(padding.left, padding.top,  
  393.                 padding.right, padding.bottom);  
  394.         workspace.setLayoutParams(lp);  
  395.   
  396.         // Layout the hotseat  
  397.         View hotseat = launcher.findViewById(R.id.hotseat);  
  398.         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();  
  399.         if (hasVerticalBarLayout) {  
  400.             // Vertical hotseat  
  401.             lp.gravity = Gravity.RIGHT;  
  402.             lp.width = hotseatBarHeightPx;  
  403.             lp.height = LayoutParams.MATCH_PARENT;  
  404.             hotseat.setPadding(02 * edgeMarginPx,  
  405.                     2 * edgeMarginPx, 2 * edgeMarginPx);  
  406.         } else if (isTablet()) {  
  407.             // Pad the hotseat with the grid gap calculated above  
  408.             int gridGap = (int) ((widthPx - 2 * edgeMarginPx -  
  409.                     (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));  
  410.             int gridWidth = (int) ((numColumns * cellWidthPx) +  
  411.                     ((numColumns - 1) * gridGap));  
  412.             int hotseatGap = (int) Math.max(0,  
  413.                     (gridWidth - (numHotseatIcons * hotseatCellWidthPx))  
  414.                             / (numHotseatIcons - 1));  
  415.             lp.gravity = Gravity.BOTTOM;  
  416.             lp.width = LayoutParams.MATCH_PARENT;  
  417.             lp.height = hotseatBarHeightPx;  
  418.             hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,  
  419.                     2 * edgeMarginPx + gridGap + hotseatGap,  
  420.                     2 * edgeMarginPx);  
  421.         } else {  
  422.             // For phones, layout the hotseat without any bottom margin  
  423.             // to ensure that we have space for the folders  
  424.             lp.gravity = Gravity.BOTTOM;  
  425.             lp.width = LayoutParams.MATCH_PARENT;  
  426.             lp.height = hotseatBarHeightPx;  
  427.             hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,  
  428.                     2 * edgeMarginPx, 0);  
  429.         }  
  430.         hotseat.setLayoutParams(lp);  
  431.   
  432.         // 页面指示器(就是热键上端的小圆点)  
  433.         View pageIndicator = launcher.findViewById(R.id.page_indicator);  
  434.         if (pageIndicator != null) {  
  435.             if (hasVerticalBarLayout) {  
  436.                 // Hide the page indicators when we have vertical search/hotseat  
  437.                 pageIndicator.setVisibility(View.GONE);  
  438.             } else {  
  439.                 // Put the page indicators above the hotseat  
  440.                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();  
  441.                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;  
  442.                 lp.width = LayoutParams.WRAP_CONTENT;  
  443.                 lp.height = LayoutParams.WRAP_CONTENT;  
  444.                 lp.bottomMargin = hotseatBarHeightPx;  
  445.                 pageIndicator.setLayoutParams(lp);  
  446.             }  
  447.         }  
  448.     }  
  449. }  

最后终于到我们的主类了:

[java]  view plain copy
  1. public class DynamicGrid {  
  2.     @SuppressWarnings("unused")<span style="white-space:pre"> </span>// 该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。   
  3.     private static final String TAG = "DynamicGrid";  
  4.   
  5.     private DeviceProfile mProfile;  
  6.     private float mMinWidth;  
  7.     private float mMinHeight;  
  8.   
  9.     public static float dpiFromPx(int size, DisplayMetrics metrics){  
  10.         float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;  
  11.         return (size / densityRatio);  
  12.     }  
  13.     public static int pxFromDp(float size, DisplayMetrics metrics) {  
  14.         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  15.                 size, metrics));  
  16.     }  
  17.     public static int pxFromSp(float size, DisplayMetrics metrics) {  
  18.         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,  
  19.                 size, metrics));  
  20.     }  
  21.   
  22.     public DynamicGrid(Context context, Resources resources,  
  23.                        int minWidthPx, int minHeightPx,  
  24.                        int widthPx, int heightPx,  
  25.                        int awPx, int ahPx) {  
  26.         DisplayMetrics dm = resources.getDisplayMetrics();  
  27.         ArrayList<DeviceProfile> deviceProfiles =  
  28.                 new ArrayList<DeviceProfile>();  
  29.         boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;  
  30.         // Our phone profiles include the bar sizes in each orientation /* zhoumushui:下面定义了一些设备的信息,里面的9个参数分别是:设备名,最小宽度Dps,最小高度Dps,行数,列数,图标大小,图标中字体大小,固定热键(通常放拨号,短信等几个常用应用)数目,固定热键图标大小 */  
  31.         deviceProfiles.add(new DeviceProfile("Super Short Stubby",  
  32.                 255300,  23,  4813, (hasAA ? 5 : 4), 48));  
  33.         deviceProfiles.add(new DeviceProfile("Shorter Stubby",  
  34.                 255400,  33,  4813, (hasAA ? 5 : 4), 48));  
  35.         deviceProfiles.add(new DeviceProfile("Short Stubby",  
  36.                 275420,  34,  4813, (hasAA ? 5 : 4), 48));  
  37.         deviceProfiles.add(new DeviceProfile("Stubby",  
  38.                 255450,  34,  4813, (hasAA ? 5 : 4), 48));  
  39.         deviceProfiles.add(new DeviceProfile("Nexus S",  
  40.                 296491.33f,  44,  4813, (hasAA ? 5 : 4), 48));  
  41.         /// M: add 294 X 460 profile  
  42.         deviceProfiles.add(new DeviceProfile("294 X 460",  
  43.                 294460,  44,  50.34943f, 13, (hasAA ? 5 : 4), 49.566288f));  
  44.         deviceProfiles.add(new DeviceProfile("Nexus 4",<span style="white-space:pre">     </span>  
  45.                 359518,  44,  6013, (hasAA ? 5 : 4), 56));  
  46.         // The tablet profile is odd in that the landscape orientation  
  47.         // also includes the nav bar on the side  
  48.         deviceProfiles.add(new DeviceProfile("WVGA",  
  49.                 407727,  45,  6813, (hasAA ? 5 : 4), 56));  
  50.         deviceProfiles.add(new DeviceProfile("1024x600",  
  51.                 527951,  56,  7214.4f,  760));  
  52.         deviceProfiles.add(new DeviceProfile("Nexus 7",  
  53.              575904,  66,  7214.4f,  760));  
  54.         // Larger tablet profiles always have system bars on the top & bottom  
  55.         deviceProfiles.add(new DeviceProfile("Nexus 10",  
  56.                 7271207,  58,  8014.4f,  964));  
  57.         deviceProfiles.add(new DeviceProfile("andows",  
  58.         6471280,  58,  5614.4f,  960));  
  59.         deviceProfiles.add(new DeviceProfile("Nexus 7",  
  60.                 600960,  55,  7214.4f,  560));  
  61.         deviceProfiles.add(new DeviceProfile("Nexus 10",  
  62.                 8001280,  55,  8014.4f, (hasAA ? 7 : 6), 64));  
  63.            
  64.         deviceProfiles.add(new DeviceProfile("20-inch Tablet",  
  65.                 15272527,  77,  10020,  772));  
  66.         mMinWidth = dpiFromPx(minWidthPx, dm);  
  67.         mMinHeight = dpiFromPx(minHeightPx, dm);  
  68.         mProfile = new DeviceProfile(context, deviceProfiles,  
  69.                 mMinWidth, mMinHeight,  
  70.                 widthPx, heightPx,  
  71.                 awPx, ahPx,  
  72.                 resources);  
  73.     }  
  74.   
  75.     DeviceProfile getDeviceProfile() {  
  76.         return mProfile;  
  77.     }  
  78.   
  79.     public String toString() {  
  80.         return "-------- DYNAMIC GRID ------- \n" +  
  81.                 "Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +  
  82.                 ", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +  
  83.                 " [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +  
  84.                 ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSize +  
  85.                 ", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +  
  86.  ", hc: "  
  87.                 + mProfile.numHotseatIcons + ", his: "  
  88.                 + mProfile.hotseatIconSizePx + "]";  
  89.     }  
  90. }  


WallpaperCropActivity:

最近两天改一个客户需求改得挺耗时,多数都用在了WallpaperCropActivity这个文件中。今天就顺带分析一下这个文件。


路径:packages/apps/Launcher3/src/com/android/launcher3/WallpaperCropActivity.java


这个Activity是Launcher设置壁纸的Activity,完成剪切壁纸大小,设置壁纸等操作。

就在代码里面加注释分析吧,方便一些。

因为我这边的代码是mediaTek定制过的,有些地方和Google官方不一样,所以就没用。以下代码是从网上找的Google官方的代码:

(其实前4篇都是mediaTek定制过的代码,当时也没有考虑到和Google的差异)


/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.android.launcher3;

import android.app.ActionBar;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.FloatMath;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.photos.BitmapRegionTileSource;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class WallpaperCropActivity extends Activity {
private static final String LOGTAG = "Launcher3.CropActivity";

protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
private static final int DEFAULT_COMPRESS_QUALITY = 90;<span style="white-space:pre"> </span>// 默认的图片压缩质量
/**
* The maximum bitmap size we allow to be returned through the intent.
* Intents have a maximum of 1MB in total size. However, the Bitmap seems to
* have some overhead to hit so that we go way below the limit here to make
* sure the intent stays below 1MB.We should consider just returning a byte
* array instead of a Bitmap instance to avoid overhead.
*/
public static final int MAX_BMAP_IN_INTENT = 750000;
private static final float WALLPAPER_SCREENS_SPAN = 2f;

protected CropView mCropView;
protected Uri mUri;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
if (!enableRotation()) {
setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
}
}

protected void init() {
setContentView(R.layout.wallpaper_cropper);

mCropView = (CropView) findViewById(R.id.cropView);

Intent cropIntent = getIntent();
final Uri imageUri = cropIntent.getData();

if (imageUri == null) {
Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
finish();
return;
}

int rotation = getRotationFromExif(this, imageUri);
mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
mCropView.setTouchEnabled(true);
// Action bar
// Show the custom action bar view
final ActionBar actionBar = getActionBar();
actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
actionBar.getCustomView().setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean finishActivityWhenDone = true;
cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
}
});
}

public boolean enableRotation() {
return getResources().getBoolean(R.bool.allow_rotation);
}

public static String getSharedPreferencesKey() {
return WallpaperCropActivity.class.getName();
}

// As a ratio of screen height, the total distance we want the parallax effect to span
// horizontally
private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
float aspectRatio = width / (float) height;

// At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
// At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
// We will use these two data points to extrapolate how much the wallpaper parallax effect
// to span (ie travel) at any aspect ratio:

final float ASPECT_RATIO_LANDSCAPE = 16/10f;
final float ASPECT_RATIO_PORTRAIT = 10/16f;
final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;

// To find out the desired width at different aspect ratios, we use the following two
// formulas, where the coefficient on x is the aspect ratio (width/height):
// (16/10)x + y = 1.5
// (10/16)x + y = 1.2
// We solve for x and y and end up with a final formula:
final float x =
(WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
(ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
return x * aspectRatio + y;
}

/*以下这个函数用来获取壁纸的长和宽,Google的源码逻辑还是要简单一些,没有做太多的定制。这个函数可以做很多扩展,比如说让横屏显示图片全部,让横屏和竖屏分别用两张分辨率不同长宽不同的图片等等。当然复杂了就容易有Bug,在锁屏界面进行转屏可能会出现闪屏的情况*/    static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {        Point minDims = new Point();        Point maxDims = new Point();        windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);        int maxDim = Math.max(maxDims.x, maxDims.y);        int minDim = Math.max(minDims.x, minDims.y);        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {            Point realSize = new Point();            windowManager.getDefaultDisplay().getRealSize(realSize);            maxDim = Math.max(realSize.x, realSize.y);            minDim = Math.min(realSize.x, realSize.y);        }        // We need to ensure that there is enough extra space in the wallpaper        // for the intended        // parallax effects        final int defaultWidth, defaultHeight;        if (isScreenLarge(res)) {// 屏幕最短边大于720则被判定为大屏幕设备            defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));            defaultHeight = maxDim;        } else {            defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);            defaultHeight = maxDim;        }        return new Point(defaultWidth, defaultHeight);    }    public static int getRotationFromExif(String path) {        return getRotationFromExifHelper(path, null, 0, null, null);    }    public static int getRotationFromExif(Context context, Uri uri) {        return getRotationFromExifHelper(null, null, 0, context, uri);    }    public static int getRotationFromExif(Resources res, int resId) {        return getRotationFromExifHelper(null, res, resId, null, null);    }    private static int getRotationFromExifHelper(            String path, Resources res, int resId, Context context, Uri uri) {        ExifInterface ei = new ExifInterface();        try {            if (path != null) {                ei.readExif(path);            } else if (uri != null) {                InputStream is = context.getContentResolver().openInputStream(uri);                BufferedInputStream bis = new BufferedInputStream(is);                ei.readExif(bis);            } else {                InputStream is = res.openRawResource(resId);                BufferedInputStream bis = new BufferedInputStream(is);                ei.readExif(bis);            }            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);            if (ori != null) {                return ExifInterface.getRotationForOrientationValue(ori.shortValue());            }        } catch (IOException e) {            Log.w(LOGTAG, "Getting exif data failed", e);        }        return 0;    }
// 根据文件路径filePath设置壁纸    protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {        int rotation = getRotationFromExif(filePath);        BitmapCropTask cropTask = new BitmapCropTask(                this, filePath, null, rotation, 0, 0, true, false, null);        final Point bounds = cropTask.getImageBounds();        Runnable onEndCrop = new Runnable() {            public void run() {                updateWallpaperDimensions(bounds.x, bounds.y);                if (finishActivityWhenDone) {                    setResult(Activity.RESULT_OK);                    finish();                }            }        };        cropTask.setOnEndRunnable(onEndCrop);        cropTask.setNoCrop(true);        cropTask.execute();    }
/*这个函数无疑是剪切图片,然后设置壁纸了,图片的outSize由上面的函数getDefaultWallpaperSize所返回,这个函数也可以进行扩充,根据resId可以对默认壁纸和壁纸库里的其他壁纸进行区分,然后设置不同的响应函数,虽然我觉的这个在实际应用中的意义不大,但是永远不能低估客户的想象力和创造力,我还就偏偏遇到了这样的客户需求。*/    protected void cropImageAndSetWallpaper(            Resources res, int resId, final boolean finishActivityWhenDone) {        // crop this image and scale it down to the default wallpaper size for        // this device        int rotation = getRotationFromExif(res, resId);        Point inSize = mCropView.getSourceDimensions();        Point outSize = getDefaultWallpaperSize(getResources(),                getWindowManager());        RectF crop = getMaxCropRect(                inSize.x, inSize.y, outSize.x, outSize.y, false);        Runnable onEndCrop = new Runnable() {            public void run() {                // Passing 0, 0 will cause launcher to revert to using the                // default wallpaper size                updateWallpaperDimensions(0, 0);                if (finishActivityWhenDone) {                    setResult(Activity.RESULT_OK);                    finish();                }            }        };        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);        cropTask.execute();    }
/*这个函数就是上面说到的判断是否属于大屏幕设备的函数了,可以看到Google的定义是屏幕最短边大于720就算是了。自然可以根据自己的需要来修改这个数值*/    private static boolean isScreenLarge(Resources res) {        Configuration config = res.getConfiguration();        return config.smallestScreenWidthDp >= 720;    }/*不同参数的cropImageAndSetWallpaper,可以看到参数相比上面的cropImageAndSetWallpaper由resId变成了<pre name="code" class="java">OnBitmapCroppedHandler,处理过程相对复杂一些。而且加入了是否是LTR布局的判断。*/
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">核心的部分也不是很多,其他不少地方都已经用英文标注了,相信看这篇文字的都是IT人,英语自然不在话下,就不充当翻译了。</span>


Launcher3去掉所有应用列表,横屏时左右两侧的留空:


Android Launcher3源码分析与修改

         // Layout AllApps
AppsCustomizeTabHost host = (AppsCustomizeTabHost)
launcher.findViewById(R.id.apps_customize_pane);
if (host != null) {
// Center the all apps page indicator
int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
(allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
if (pageIndicator != null) {
lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
lp.width = LayoutParams.WRAP_CONTENT;
lp.height = pageIndicatorHeight;
pageIndicator.setLayoutParams(lp);
}

AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
host.findViewById(R.id.apps_customize_pane_content);
padding = new Rect();
if (pagedView != null) {
// Constrain the dimensions of all apps so that it does not span the full width
// TChip ZJ Add START: 去掉所有应用列表,横屏时左右两侧的Margin
int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
(2 * (allAppsNumCols + 1));
int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
(2 * (allAppsNumRows + 1));
paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
paddingLR = (int)(paddingLR * 0.75f);
paddingTB = (int)(paddingTB * 0.75f);
// TChip ZJ Add END

// TChip ZJ Minus START: 去掉所有应用列表,横屏时左右两侧的Margin
/*
int paddingLR = 2;
int paddingTB = 2;
*/
// TChip ZJ Minus END
int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 4;
// Only adjust the side paddings on landscape phones, or tablets
if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
padding.left = padding.right = gridPaddingLR;
}
// The icons are centered, so we can't just offset by the page indicator height
// because the empty space will actually be pageIndicatorHeight + paddingTB
padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
pagedView.setAllAppsPadding(padding);
pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
}
}




Launcher3的DeviceProfile的改变

最近真忙,没时间细写blog了。
对比了两个平台的Launcher3项目,虽然都是3,但是代码也不是一成不变的,新版本的Launcher3,DeviceProfile也从DynamicGrid中独立出来了。
之前加的很多属性不再适用,需要重新阅读源码,进行修改。呃,工作量不小。


在Launcher的主菜单中隐藏某个应用removePackage

在package/apps/Launcher3/src/com/android/launcher3/LauncherModel.java中的private void loadAllApps() {}函数中的mBgAllAppsList.reorderApplist();之前添加如下:

     // ZJ Add START
mBgAllAppsList.removePackage("com.mediatek.filemanager");
mBgAllAppsList.removePackage("com.android.chrome");
mBgAllAppsList.removePackage("com.android.providers.downloads.ui");
mBgAllAppsList.removePackage("com.android.deskclock");
mBgAllAppsList.removePackage("com.android.email");
mBgAllAppsList.removePackage("com.google.android.apps.maps");
mBgAllAppsList.removePackage("com.google.android.gm");
mBgAllAppsList.removePackage("com.android.soundrecorder");
mBgAllAppsList.removePackage("com.mediatek.FMRadio");
mBgAllAppsList.removePackage("com.google.android.inputmethod.korean");
// ZJ Add END

mBgAllAppsList.reorderApplist();

mBgAllAppList是类AllAppList的对象,可以看下removePackage这个函数的实现:

    /**
* Remove the apps for the given apk identified by packageName.
*/
public void removePackage(String packageName) {
final List<AppInfo> data = this.data;
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "removePackage: packageName = " + packageName + ", data size = " + data.size());
}

for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
final ComponentName component = info.intent.getComponent();
if (packageName.equals(component.getPackageName())) {
removed.add(info);
data.remove(i);
}
}
// This is more aggressive than it needs to be.
mIconCache.flush();
}



转载请注明出处:周木水的CSDN博客 http://blog.csdn.net/zhoumushui