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中添加标签),安装时不需要用户确认; dangerous:高风险权限,安装时需要用户的确认才可使用; signature:只有当申请权限的应用程序的数字签名与声明此权限的应用程序的数字签名相同时(如果是申请系统权限,则需要与系统签名相同),才能将权限授给它; signatureOrSystem:签名相同,或者申请权限的应用为系统应用(在system image中)
其他的android:label, android:name, android:description是描述性信息
< permission
android:name = "com.android.launcher3.permission.PRELOAD_WORKSPACE"
android:permissionGroup = "android.permission-group.SYSTEM_TOOLS"
android:protectionLevel = "system|signature" />
< permission
android:name = "com.android.launcher.permission.INSTALL_SHORTCUT"
android:permissionGroup = "android.permission-group.SYSTEM_TOOLS"
android:protectionLevel = "dangerous"
android:label = "@string/permlab_install_shortcut"
android:description = "@string/permdesc_install_shortcut" />
< permission
android:name = "com.android.launcher.permission.UNINSTALL_SHORTCUT"
android:permissionGroup = "android.permission-group.SYSTEM_TOOLS"
android:protectionLevel = "dangerous"
android:label = "@string/permlab_uninstall_shortcut"
android:description = "@string/permdesc_uninstall_shortcut" />
< permission
android:name = "com.android.launcher3.permission.READ_SETTINGS"
android:permissionGroup = "android.permission-group.SYSTEM_TOOLS"
android:protectionLevel = "normal"
android:label = "@string/permlab_read_settings"
android:description = "@string/permdesc_read_settings" />
< permission
android:name = "com.android.launcher3.permission.WRITE_SETTINGS"
android:permissionGroup = "android.permission-group.SYSTEM_TOOLS"
android:protectionLevel = "normal"
android:label = "@string/permlab_write_settings"
android:description = "@string/permdesc_write_settings" />
< permission
android:name = "com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS"
android:protectionLevel = "signature"
/>
< uses-permission android:name = "android.permission.CALL_PHONE" />
< uses-permission android:name = "android.permission.SET_WALLPAPER" />
< uses-permission android:name = "android.permission.SET_WALLPAPER_HINTS" />
< uses-permission android:name = "android.permission.VIBRATE" />
< uses-permission android:name = "android.permission.BIND_APPWIDGET" />
< uses-permission android:name = "android.permission.GET_ACCOUNTS" />
< uses-permission android:name = "android.permission.READ_EXTERNAL_STORAGE" />
< uses-permission android:name = "android.permission.READ_MEDIA_STORAGE" />
< uses-permission android:name = "android.permission.ADVANCED_WIDGET_API" />
< uses-permission android:name = "com.android.launcher.permission.READ_SETTINGS" />
< uses-permission android:name = "com.android.launcher.permission.WRITE_SETTINGS" />
< uses-permission android:name = "com.android.launcher3.permission.READ_SETTINGS" />
< uses-permission android:name = "com.android.launcher3.permission.WRITE_SETTINGS" />
< uses-permission android:name = "com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS" />
< 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文件中的部分:
这里面声明的都是显式Activity,Service,Provider之类的。
先上源码:
下面先分析一下各个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是所有应用的清单:
public ArrayList data =
new ArrayList(DEFAULT_APPLICATIONS_NUMBER);
added是自上次
更新(notify()广播)后新
增加的应用清单:
public ArrayList added =
new ArrayList(DEFAULT_APPLICATIONS_NUMBER);
removed是自上次更新以来,所移除的应用清单:
public ArrayList removed = new ArrayList();
modified是自上次更新以来,所修改的应用清单:
public ArrayList modified = new ArrayList();
mIconCache 图标缓存
mAppFilter 应用过滤器
private IconCache mIconCache;
private AppFilter mAppFilter;
新添加一个应用及其信息到list中,如果已存在则不添加:
public void add(AppInfo info) {
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "Add application in app list: app = " + info.componentName
+ ", title = " + info.title);
}
if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
return ;
}
if (findActivity(data, info.componentName)) {
LauncherLog.i(TAG, "Application " + info + " already exists in app list, app = " + info);
return ;
}
data.add(info);
added.add(info);
}
清空列表中的应用信息,包括data,added,removed,modified:
public void clear() {
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "clear all data in app list: app size = " + data.size());
}
data.clear();
added.clear();
removed.clear();
modified.clear();
}
为指定包名添加Icon信息到matches 表单:
public void addPackage(Context context, String packageName) {
final List matches = findActivitiesForPackage(context, packageName);
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "addPackage: packageName = " + packageName + ", matches = " + matches.size());
}
if (matches.size() > 0 ) {
for (ResolveInfo info : matches) {
add(new AppInfo(context.getPackageManager(), info, mIconCache, null ));
}
}
}
从data表单中删除指定包名的应用的包信息:
public void removePackage(String packageName) {
final List 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);
}
}
更新包信息,若不存在,直接添加应用信息;若已存在,则先删除旧信息,然后添加新信息。
public void updatePackage(Context context, String packageName) {
final List matches = findActivitiesForPackage(context, packageName);
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "updatePackage: packageName = " + packageName + ", matches = " + matches.size());
}
if (matches.size() > 0 ) {
for ( int i = data.size() - 1 ; i >= 0 ; i--) {
final AppInfo applicationInfo = data.get(i);
final ComponentName component = applicationInfo.intent.getComponent();
if (packageName.equals(component.getPackageName())) {
if (!findActivity(matches, component)) {
removed.add(applicationInfo);
mIconCache.remove(component);
data.remove(i);
}
}
}
int count = matches.size();
for ( int i = 0 ; i < count; i++) {
final ResolveInfo info = matches.get(i);
final String pkgName = info.activityInfo.applicationInfo.packageName;
final String className = info.activityInfo.name;
AppInfo applicationInfo = findApplicationInfoLocked(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
if (applicationInfo == null ) {
add(new AppInfo(context.getPackageManager(), info, mIconCache, null ));
} else {
mIconCache.remove(applicationInfo.componentName);
mIconCache.getTitleAndIcon(applicationInfo, info, null );
modified.add(applicationInfo);
}
}
} else {
for ( int i = data.size() - 1 ; i >= 0 ; i--) {
final AppInfo applicationInfo = data.get(i);
final ComponentName component = applicationInfo.intent.getComponent();
if (packageName.equals(component.getPackageName())) {
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "Remove application from launcher: component = " + component);
}
removed.add(applicationInfo);
mIconCache.remove(component);
data.remove(i);
}
}
}
}
供MAIN/LAUNCHER通过包名packageName查询包管理器:
static List findActivitiesForPackage(Context context, String packageName) {
final PackageManager packageManager = context.getPackageManager();
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null );
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mainIntent.setPackage(packageName);
final List apps = packageManager.queryIntentActivities(mainIntent, 0 );
return apps != null ? apps : new ArrayList();
}
根据component是否找到对应的应用:
private static boolean findActivity(List apps, ComponentName component) {
final String className = component.getClassName();
for (ResolveInfo info : apps) {
final ActivityInfo activityInfo = info.activityInfo;
if (activityInfo.name.equals(className)) {
return true ;
}
}
return false ;
}
private static boolean findActivity(ArrayList apps, ComponentName component) {
final int N = apps.size();
for ( int i= 0 ; i
final AppInfo info = apps.get(i);
if (info.componentName.equals(component)) {
return true ;
}
}
return false ;
}
根据指定的包名packageName,类明className返回一个Component对象:
private AppInfo findApplicationInfoLocked(String packageName, String className) {
for (AppInfo info: data) {
final ComponentName component = info.intent.getComponent();
if (packageName.equals(component.getPackageName())
&& className.equals(component.getClassName())) {
return info;
}
}
return null ;
}
从default_toppackage.xml文件中加载默认顶部应用,加载成功返回true:
static boolean loadTopPackage( final Context context) {
boolean bRet = false ;
if (sTopPackages != null ) {
return bRet;
}
sTopPackages = new ArrayList();
try {
XmlResourceParser parser = context.getResources().getXml(R.xml.default_toppackage);
AttributeSet attrs = Xml.asAttributeSet(parser);
XmlUtils.beginDocument(parser, TAG_TOPPACKAGES);
final int depth = parser.getDepth();
int type = - 1 ;
while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue ;
}
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopPackage);
sTopPackages.add(new TopPackage(a.getString(R.styleable.TopPackage_topPackageName),
a.getString(R.styleable.TopPackage_topClassName), a.getInt(
R.styleable.TopPackage_topOrder, 0 )));
LauncherLog.d(TAG, "loadTopPackage: packageName = "
+ a.getString(R.styleable.TopPackage_topPackageName)
+ ", className = "
+ a.getString(R.styleable.TopPackage_topClassName));
a.recycle();
}
} catch (XmlPullParserException e) {
LauncherLog.w(TAG, "Got XmlPullParserException while parsing toppackage." , e);
} catch (IOException e) {
LauncherLog.w(TAG, "Got IOException while parsing toppackage." , e);
}
return bRet;
}
根据应用信息appInfo匹配默认顶部应用的对应应用,返回该应用下标:
static int getTopPackageIndex( final AppInfo appInfo) {
int retIndex = - 1 ;
if (sTopPackages == null || sTopPackages.isEmpty() || appInfo == null ) {
return retIndex;
}
for (TopPackage tp : sTopPackages) {
if (appInfo.componentName.getPackageName().equals(tp.packageName)
&& appInfo.componentName.getClassName().equals(tp.className)) {
retIndex = tp.order;
break ;
}
}
return retIndex;
}
重排默认顶部应用所有应用的下标:
void reorderApplist() {
final long sortTime = DEBUG_LOADERS_REORDER ? SystemClock.uptimeMillis() : 0 ;
if (sTopPackages == null || sTopPackages.isEmpty()) {
return ;
}
ensureTopPackageOrdered();
final ArrayList dataReorder = new ArrayList(
DEFAULT_APPLICATIONS_NUMBER);
for (TopPackage tp : sTopPackages) {
int loop = 0 ;
for (AppInfo ai : added) {
if (DEBUG_LOADERS_REORDER) {
LauncherLog.d(TAG, "reorderApplist: remove loop = " + loop);
}
if (ai.componentName.getPackageName().equals(tp.packageName)
&& ai.componentName.getClassName().equals(tp.className)) {
if (DEBUG_LOADERS_REORDER) {
LauncherLog.d(TAG, "reorderApplist: remove packageName = "
+ ai.componentName.getPackageName());
}
data.remove(ai);
dataReorder.add(ai);
dumpData();
break ;
}
loop++;
}
}
for (TopPackage tp : sTopPackages) {
int loop = 0 ;
int newIndex = 0 ;
for (AppInfo ai : dataReorder) {
if (DEBUG_LOADERS_REORDER) {
LauncherLog.d(TAG, "reorderApplist: added loop = " + loop + ", packageName = "
+ ai.componentName.getPackageName());
}
if (ai.componentName.getPackageName().equals(tp.packageName)
&& ai.componentName.getClassName().equals(tp.className)) {
newIndex = Math.min(Math.max(tp.order, 0 ), added.size());
if (DEBUG_LOADERS_REORDER) {
LauncherLog.d(TAG, "reorderApplist: added newIndex = " + newIndex);
}
if (newIndex < data.size()) {
data.add(newIndex, ai);
} else {
data.add(ai);
}
dumpData();
break ;
}
loop++;
}
}
if (added.size() == data.size()) {
added = (ArrayList) data.clone();
LauncherLog.d(TAG, "reorderApplist added.size() == data.size()" );
}
if (DEBUG_LOADERS_REORDER) {
LauncherLog.d(TAG, "sort and reorder took " + (SystemClock.uptimeMillis() - sortTime) + "ms" );
}
}
Dump掉data列表中的应用信息:
void dumpData() {
int loop2 = 0 ;
for (AppInfo ai : data) {
if (DEBUG_LOADERS_REORDER) {
LauncherLog.d(TAG, "reorderApplist data loop2 = " + loop2);
LauncherLog.d(TAG, "reorderApplist data packageName = "
+ ai.componentName.getPackageName());
}
loop2++;
}
}
确保top_package.xml中条目有序,以防特殊情况下top_package.xml会使数组列表越界:
static void ensureTopPackageOrdered() {
ArrayList tpOrderList = new ArrayList(DEFAULT_APPLICATIONS_NUMBER);
boolean bFirst = true ;
for (TopPackage tp : sTopPackages) {
if (bFirst) {
tpOrderList.add(tp);
bFirst = false ;
} else {
for ( int i = tpOrderList.size() - 1 ; i >= 0 ; i--) {
TopPackage tpItor = tpOrderList.get(i);
if ( 0 == i) {
if (tp.order < tpOrderList.get( 0 ).order) {
tpOrderList.add(0 , tp);
} else {
tpOrderList.add(1 , tp);
}
break ;
}
if ((tp.order < tpOrderList.get(i).order)
&& (tp.order >= tpOrderList.get(i - 1 ).order)) {
tpOrderList.add(i, tp);
break ;
} else if (tp.order > tpOrderList.get(i).order) {
tpOrderList.add(i + 1 , tp);
break ;
}
}
}
}
if (sTopPackages.size() == tpOrderList.size()) {
sTopPackages = (ArrayList) tpOrderList.clone();
tpOrderList = null ;
LauncherLog.d(TAG, "ensureTopPackageOrdered done" );
} else {
LauncherLog.d(TAG, "some mistake may occur when ensureTopPackageOrdered" );
}
}
添加应用信息到add列表,注意不是added列表:
public void addApp( final AppInfo info) {
if (LauncherLog.DEBUG_EDIT) {
LauncherLog.d(TAG, "Add application to data list: app = " + info.componentName);
}
if (findActivity(data, info.componentName)) {
LauncherLog.i(TAG, "The app " + info + " is already exist in data list." );
return ;
}
data.add(info);
}
AppInfo
这个类也很关键,不过相对AllAppList来讲要简单很多,从代码量上就可以看出来。主要是App的详细信息,包括图标,初次安装时间之类的,下面细看AppInfo的成员变量和函数:
图标:
应用的初次安装时间:
应用图标是否显示,true为显示。
boolean isVisible = true ;
无参初始化,为应用创建快捷方式:
AppInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
}
得到应用的图标和标题:
public AppInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
HashMap labelCache) {
final String packageName = info.activityInfo.applicationInfo.packageName;
this .componentName = new ComponentName(packageName, info.activityInfo.name);
this .container = ItemInfo.NO_ID;
this .setActivity(componentName,
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
this .setFlagAndInstallTime(pm, packageName);
iconCache.getTitleAndIcon(this , info, labelCache);
}
初始化标志信息,指明应用是系统应用,还是用户下载:
public static int initFlags(PackageInfo pi) {
int appFlags = pi.applicationInfo.flags;
int flags = 0 ;
if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0 ) {
flags |= DOWNLOADED_FLAG;
if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ) {
flags |= UPDATED_SYSTEM_APP_FLAG;
}
}
return flags;
}
AppInfp的成员接口,可以获取组件名componentName,标题title,intent,标志flags,初次安装时间firstInstallTime等信息:
public AppInfo(AppInfo info) {
super (info);
componentName = info.componentName;
title = info.title.toString();
intent = new Intent(info.intent);
flags = info.flags;
firstInstallTime = info.firstInstallTime;
pos = info.pos;
isVisible = info.isVisible;
stateChanged = info.stateChanged;
}
根据类名和launchFlags创建应用Intent:
final void setActivity(ComponentName className, int launchFlags) {
intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(className);
intent.setFlags(launchFlags);
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
}
设置标志,记录初次安装时间:
public void setFlagAndInstallTime( final PackageManager pm, String packageName) {
try {
PackageInfo pi = pm.getPackageInfo(packageName, 0 );
flags = initFlags(pi);
firstInstallTime = initFirstInstallTime(pi);
} catch (NameNotFoundException e) {
Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
}
}
public void setFlagAndInstallTime( final PackageManager pm) {
String packageName = getPackageName();
setFlagAndInstallTime(pm, packageName);
}
返回快捷方式要启动应用的包名,如不存在则返回一个空字符串:
String getPackageName() {
String packageName = "" ;
if (intent != null ) {
packageName = intent.getPackage();
if (packageName == null && intent.getComponent() != null ) {
packageName = intent.getComponent().getPackageName();
}
}
return packageName;
}
今天就先这两个类吧,已经不短了。不过源码真心长,真心多,真心眼花缭乱,慢慢啃吧。
今天来看一下CropView和DynamicGrid两部分。
CropView是设置壁纸时通过缩放手势截取图片,DynamicGrid则是根据设备来指定图标显示的行列,下面细看一下:
CropView
为了便于查看,吸取前两篇的教训,直接把注释写在代码里,成员变量:
private ScaleGestureDetector mScaleGestureDetector;
private long mTouchDownTime;
private float mFirstX, mFirstY;"white-space:pre" >
private float mLastX, mLastY;"white-space:pre" >
private float mCenterX, mCenterY;"white-space:pre" >
private float mMinScale;
private boolean mTouchEnabled = true ;
private RectF mTempEdges = new RectF();
private float [] mTempPoint = new float [] { 0 , 0 };
private float [] mTempCoef = new float [] { 0 , 0 };
private float [] mTempAdjustment = new float [] { 0 , 0 };
private float [] mTempImageDims = new float [] { 0 , 0 };
private float [] mTempRendererCenter = new float [] { 0 , 0 };
TouchCallback mTouchCallback;
Matrix mRotateMatrix;
Matrix mInverseRotateMatrix;
成员函数:
CropView的构造函数:
public CropView(Context context) {
this (context, null );
}
public CropView(Context context, AttributeSet attrs) {
super (context, attrs);
mScaleGestureDetector = new ScaleGestureDetector(context, this );
mRotateMatrix = new Matrix();
mInverseRotateMatrix = new Matrix();
}
得到图像的分辨率:
private float [] getImageDims() {
final float imageWidth = mRenderer.source.getImageWidth();"white-space:pre" >
final float imageHeight = mRenderer.source.getImageHeight();"white-space:pre" >
float [] imageDims = mTempImageDims;
imageDims[0 ] = imageWidth;
imageDims[1 ] = imageHeight;
mRotateMatrix.mapPoints(imageDims);
imageDims[0 ] = Math.abs(imageDims[ 0 ]);
imageDims[1 ] = Math.abs(imageDims[ 1 ]);
return imageDims;
}
得到上下左右的边界:
private void getEdgesHelper(RectF edgesOut) {
final float width = getWidth();
final float height = getHeight();
final float [] imageDims = getImageDims();
final float imageWidth = imageDims[ 0 ];
final float imageHeight = imageDims[ 1 ];
float initialCenterX = mRenderer.source.getImageWidth() / 2f;
float initialCenterY = mRenderer.source.getImageHeight() / 2f;
float [] rendererCenter = mTempRendererCenter;
rendererCenter[0 ] = mCenterX - initialCenterX;
rendererCenter[1 ] = mCenterY - initialCenterY;
mRotateMatrix.mapPoints(rendererCenter);
rendererCenter[0 ] += imageWidth / 2 ;
rendererCenter[1 ] += imageHeight / 2 ;
final float scale = mRenderer.scale;
float centerX = (width / 2f - rendererCenter[ 0 ] + (imageWidth - width) / 2f)
* scale + width / 2f;
float centerY = (height / 2f - rendererCenter[ 1 ] + (imageHeight - height) / 2f)
* scale + height / 2f;
float leftEdge = centerX - imageWidth / 2f * scale;
float rightEdge = centerX + imageWidth / 2f * scale;
float topEdge = centerY - imageHeight / 2f * scale;
float bottomEdge = centerY + imageHeight / 2f * scale;
edgesOut.left = leftEdge;
edgesOut.right = rightEdge;
edgesOut.top = topEdge;
edgesOut.bottom = bottomEdge;
}
得到截取的形状:
public RectF getCrop() {
final RectF edges = mTempEdges;
getEdgesHelper(edges);
final float scale = mRenderer.scale;
float cropLeft = -edges.left / scale;
float cropTop = -edges.top / scale;
float cropRight = cropLeft + getWidth() / scale;
float cropBottom = cropTop + getHeight() / scale;
return new RectF(cropLeft, cropTop, cropRight, cropBottom);
}
设置TileSource,根据TileSource更新Scale:
public void setTileSource(TileSource source, Runnable isReadyCallback) {
super .setTileSource(source, isReadyCallback);
mCenterX = mRenderer.centerX;
mCenterY = mRenderer.centerY;
mRotateMatrix.reset();
mRotateMatrix.setRotate(mRenderer.rotation);
mInverseRotateMatrix.reset();
mInverseRotateMatrix.setRotate(-mRenderer.rotation);
updateMinScale(getWidth(), getHeight(), source, true );
}
宽和高改变后,重新设置Scale:
protected void onSizeChanged( int w, int h, int oldw, int oldh) {
updateMinScale(w, h, mRenderer.source, false );
}
public void setScale( float scale) {
synchronized (mLock) {
mRenderer.scale = scale;
}
}
private void updateMinScale( int w, int h, TileSource source,
boolean resetScale) {
synchronized (mLock) {
if (resetScale) {
mRenderer.scale = 1 ;
}
if (source != null ) {
final float [] imageDims = getImageDims();
final float imageWidth = imageDims[ 0 ];
final float imageHeight = imageDims[ 1 ];
mMinScale = Math.max(w / imageWidth, h / imageHeight);
mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
}
}
}
核心的来了,触摸响应:
DynamicGrid
动态网格?先看两个核心类:
查询获取设备信息,宽和高以及分辨率:
class DeviceProfileQuery {
float widthDps;
float heightDps;
float value;
PointF dimens;
DeviceProfileQuery(float w, float h, float v) {
widthDps = w;
heightDps = h;
value = v;
dimens = new PointF(w, h);
}
}
下面这个就长了,洋洋洒洒几百行:
class DeviceProfile {
String name;
float minWidthDps;
float minHeightDps;
float numRows;
float numColumns;
float iconSize;
float iconTextSize;
float numHotseatIcons;
float hotseatIconSize;
boolean isLandscape;
boolean isTablet;
boolean isLargeTablet;
boolean transposeLayoutWithOrientation;
int desiredWorkspaceLeftRightMarginPx;
int edgeMarginPx;
Rect defaultWidgetPadding;
int widthPx;
int heightPx;
int availableWidthPx;
int availableHeightPx;
int iconSizePx;"white-space:pre" >
int iconTextSizePx;"white-space:pre" >
int cellWidthPx;
int cellHeightPx;
int folderBackgroundOffset;"white-space:pre" >
int folderIconSizePx;"white-space:pre" >
int folderCellWidthPx;
int folderCellHeightPx;
int hotseatCellWidthPx;"white-space:pre" >
int hotseatCellHeightPx;"white-space:pre" >
int hotseatIconSizePx;
int hotseatBarHeightPx;"white-space:pre" >
int hotseatAllAppsRank;
int allAppsNumRows;"white-space:pre" >
int allAppsNumCols;"white-space:pre" >
int searchBarSpaceWidthPx;"white-space:pre" >
int searchBarSpaceMaxWidthPx;
int searchBarSpaceHeightPx;
int searchBarHeightPx;
int pageIndicatorHeightPx;
private static String APP_COUNTX = SystemProperties.get( "ro.appscellcountx.value" );
private static String APP_COUNTY = SystemProperties.get( "ro.appscellcounty.value" );
private static int MAX_ROW = 12 ;"white-space:pre" >
private static int MAX_COLS = 12 ;"white-space:pre" >
private static int MIN_ROW = 4 ;"white-space:pre" >
private static int MIN_COLS = 4 ;"white-space:pre" >
private static boolean IS_CONFIG = !TextUtils.isEmpty(APP_COUNTX) & !TextUtils.isEmpty(APP_COUNTY);
private static boolean IS_AVAILABLY = (Integer.parseInt(APP_COUNTX) > MIN_ROW) &
(Integer.parseInt(APP_COUNTX) < MAX_ROW) &
(Integer.parseInt(APP_COUNTY) > MIN_COLS) &
(Integer.parseInt(APP_COUNTY) < MAX_COLS);
DeviceProfile(String n, float w, float h, float r, float c,
float is, float its, float hs, float his) {
if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0 ) {"white-space:pre" >
throw new RuntimeException( "All Device Profiles must have an odd number of hotseat spaces" );
}
name = n;
minWidthDps = w;
minHeightDps = h;
numRows = r;
numColumns = c;
iconSize = is;
iconTextSize = its;
numHotseatIcons = hs;
hotseatIconSize = his;
}
DeviceProfile(Context context,
ArrayList profiles,
float minWidth, float minHeight,
int wPx, int hPx,
int awPx, int ahPx,
Resources resources) {
DisplayMetrics dm = resources.getDisplayMetrics();
ArrayList points =
new ArrayList();
transposeLayoutWithOrientation =
resources.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
minWidthDps = minWidth;
minHeightDps = minHeight;
ComponentName cn = new ComponentName(context.getPackageName(),
this .getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null );
edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
}
numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
points.clear();
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
}
numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
points.clear();
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
}
iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
points.clear();
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
}
iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
iconTextSizePx = DynamicGrid.pxFromSp(iconTextSize, dm);
points.clear();
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
}
numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
points.clear();
for (DeviceProfile p : profiles) {
points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
}
hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
hotseatIconSizePx = DynamicGrid.pxFromDp(hotseatIconSize, dm);
hotseatAllAppsRank = (int ) (numColumns / 2 );
updateFromConfiguration(resources, wPx, hPx, awPx, ahPx);
searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
Paint textPaint = new Paint();
textPaint.setTextSize(iconTextSizePx);
FontMetrics fm = textPaint.getFontMetrics();
cellWidthPx = iconSizePx;
cellHeightPx = iconSizePx + (int ) Math.ceil(fm.bottom - fm.top);
hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
hotseatCellWidthPx = iconSizePx;
hotseatCellHeightPx = iconSizePx;
folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
folderCellHeightPx = cellHeightPx + (int ) ((3f/2f) * edgeMarginPx);
folderBackgroundOffset = -edgeMarginPx;
folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
}
void updateFromConfiguration(Resources resources, int wPx, int hPx,
int awPx, int ahPx) {
isLandscape = (resources.getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE);
isTablet = resources.getBoolean(R.bool.is_tablet);
isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
widthPx = wPx;
heightPx = hPx;
availableWidthPx = awPx;
availableHeightPx = ahPx;
Rect padding = getWorkspacePadding(isLandscape ?
CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
int pageIndicatorOffset =
resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
if (IS_CONFIG && IS_AVAILABLY) {
if (isLandscape) {
allAppsNumRows = Integer.parseInt(APP_COUNTX);
allAppsNumCols = Integer.parseInt(APP_COUNTY);
} else {
allAppsNumRows = Integer.parseInt(APP_COUNTY);
allAppsNumCols = Integer.parseInt(APP_COUNTX);
}
} else {
if (isLandscape) {
allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
(iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
} else {
allAppsNumRows = (int ) numRows + 1 ;
}
allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
(iconSizePx + 2 * edgeMarginPx);
}
}
private float dist(PointF p0, PointF p1) {
return ( float ) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
(p1.y-p0.y)*(p1.y-p0.y));
}
private float weight(PointF a, PointF b,
float pow) {
float d = dist(a, b);
if (d == 0f) {
return Float.POSITIVE_INFINITY;
}
return ( float ) (1f / Math.pow(d, pow));
}
private float invDistWeightedInterpolate( float width, float height,
ArrayList points) {
float sum = 0 ;
float weights = 0 ;
float pow = 5 ;
float kNearestNeighbors = 3 ;
final PointF xy = new PointF(width, height);
ArrayList pointsByNearness = points;
Collections.sort(pointsByNearness, new Comparator() {
public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
return ( int ) (dist(xy, a.dimens) - dist(xy, b.dimens));
}
});
for ( int i = 0 ; i < pointsByNearness.size(); ++i) {
DeviceProfileQuery p = pointsByNearness.get(i);
if (i < kNearestNeighbors) {
float w = weight(xy, p.dimens, pow);
if (w == Float.POSITIVE_INFINITY) {
return p.value;
}
weights += w;
}
}
for ( int i = 0 ; i < pointsByNearness.size(); ++i) {
DeviceProfileQuery p = pointsByNearness.get(i);
if (i < kNearestNeighbors) {
float w = weight(xy, p.dimens, pow);
sum += w * p.value / weights;
}
}
return sum;
}
Rect getWorkspacePadding(int orientation) {
Rect padding = new Rect();
if (orientation == CellLayout.LANDSCAPE &&
transposeLayoutWithOrientation) {
padding.set(searchBarSpaceHeightPx, edgeMarginPx,
hotseatBarHeightPx, edgeMarginPx);
} else {
if (isTablet()) {
int width = (orientation == CellLayout.LANDSCAPE)
? Math.max(widthPx, heightPx)
: Math.min(widthPx, heightPx);
int gap = ( int ) ((width - 2 * edgeMarginPx -
(numColumns * cellWidthPx)) / (2 * (numColumns + 1 )));