最近比较忙 有段时间没写博客了 趁着有空 把最近修改launcher的心得总结一下
一、 修改和替换特定应用图标
1 allApps界面
allApps界面就是所谓的二级菜单,抽屉。
Launcher3 生成二级菜单的图标分为初次加载和初次加载之后。
初次加载时 修改 IconCache.java 中的 updateCacheAndGetContentValues()方法
public synchronized void getTitleAndIcon(AppInfo application,
LauncherActivityInfoCompat info, boolean useLowResIcon) {
UserHandleCompat user = info == null ? application.user : info.getUser();
CacheEntry entry = cacheLocked(application.componentName, info, user,
false, useLowResIcon);
application.title = Utilities.trim(entry.title);
// Bitmap icon = null;
// icon = getNonNullIcon(entry, user);
application.iconBitmap = replaceFirstShownIcon(application,entry,user);
application.contentDescription = entry.contentDescription;
application.usingLowResIcon = entry.isLowResIcon;
}
其中 AllApps页面初次加载时替换系统图标的方法如下,就是读取包名,然后替换
private Bitmap replaceFirstShownIcon(AppInfo info,CacheEntry entry,UserHandleCompat user){
String pkgName = info.componentName.getPackageName();
String className = info.componentName.getClassName();
Resources resources = mContext.getResources();
if(pkgName.equals("com.android.soundrecorder")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_record);
}
if(pkgName.equals("com.android.deskclock")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_clock);
}
if(pkgName.equals("com.android.qworldclock")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_world);
}
if(pkgName.equals("com.android.gallery3d")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_gallery);
}
if(pkgName.equals("com.android.music")){
if(className.equals("com.android.music.VideoBrowserActivity")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_play);
}
if(className.equals("com.android.music.MusicBrowserActivity")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_music);
}
}
if(pkgName.equals("com.android.providers.downloads.ui")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_download);
}
if(pkgName.equals("com.android.email")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_email);
}
if(pkgName.equals("com.android.mms")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_sms);
}
return getNonNullIcon(entry, user);
}
文件中,下次再读取就是从数据库中读取
所以如果要修改AllApps界面图标,除了在replaceFirstShownIcon() 方法中做替换外,还要在写入数据库的方法中同样替换一次
该方法为IconCahce类的addIconToDb方法
private void addIconToDB(ContentValues values, ComponentName key,
PackageInfo info, long userSerial) {
values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
values.put(IconDB.COLUMN_USER, userSerial);
values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
values.put(IconDB.COLUMN_VERSION, info.versionCode);
replaceIcon(key,values);
mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
SQLiteDatabase.CONFLICT_REPLACE);
}
同样,替换方法为
private void replaceIcon(ComponentName name,ContentValues values){
if(name.getPackageName().equals("com.android.soundrecorder")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_record)));
}
if(name.getPackageName().equals("com.android.qworldclock")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_world)));
}
if(name.getPackageName().equals("com.android.gallery3d")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_gallery)));
}
if(name.getPackageName().equals("com.android.music")){
if(name.getClassName().equals("com.android.music.VideoBrowserActivity")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_play)));
}
if(name.getClassName().equals("com.android.music.MusicBrowserActivity")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_music)));
}
}
if(name.getPackageName().equals("com.android.providers.downloads.ui")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_download)));
}
if(name.getPackageName().equals("com.android.deskclock")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_clock)));
}
if(name.getPackageName().equals("com.android.email")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_email)));
}
if(name.getPackageName().equals("com.android.mms")){
values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_sms)));
}
}
2 workspace界面
为了达到替换图标的目的,在workspace同样需要做一些操作。
workspace生成图标的途径有很多,从allApps界面拖拽,从workspace中拖拽,或者从default_workspace.xml中读取,每一个途径,最终要在workspace界面生成图标,都必须会走Launcher.java中的creatShortCut()方法
public View createShortcut(ViewGroup parent, ShortcutInfo info) {
BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon,
parent, false);
favorite.applyFromShortcutInfo(info, mIconCache);
favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
favorite.setOnClickListener(this);
favorite.setOnFocusChangeListener(mFocusHandler);
return favorite;
}
该方法在BubbleTextView中,这个BubbleTextView是一个TextView,代表桌面上的每一个icon,一个icon 其实就是一个textView(应用名称)和imageview的组合。
public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
boolean promiseStateChanged) {
// Bitmap b = info.getIcon(iconCache);
Bitmap b= replaceIcon(info,iconCache);
FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
iconDrawable.setGhostModeEnabled(info.isDisabled != 0);
setIcon(iconDrawable, mIconSize);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
}
setText(info.title);
setTag(info);
if (promiseStateChanged || info.isPromise()) {
applyState(promiseStateChanged);
}
}
我们在这里面做替换系统图标的工作
private Bitmap replaceIcon(ShortcutInfo info,IconCache cache){
Resources resources = getContext().getResources();
String pkgName = info.getTargetComponent().getPackageName();
String className = info.getTargetComponent().getClassName();
if(pkgName.equals("com.android.soundrecorder")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_record);
}
if(pkgName.equals("com.android.deskclock")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_clock);
}
if(pkgName.equals("com.android.qworldclock")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_world);
}
if(pkgName.equals("com.android.gallery3d")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_gallery);
}
if(pkgName.equals("com.android.music")){
if(className.equals("com.android.music.VideoBrowserActivity")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_play);
}
if(className.equals("com.android.music.MusicBrowserActivity")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_music);
}
}
if(pkgName.equals("com.android.providers.downloads.ui")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_download);
}
if(pkgName.equals("com.android.email")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_email);
}
if(pkgName.equals("com.android.mms")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_sms);
}
return info.getIcon(cache);
}
3 更换系统语言之后
当更换系统语言之后,图标和标签会重新加载生成一次,而且在AllApps列表里,图标的排列次序也会发生变化,跟进到IconCache.java的updateCacheAndGetContentValues()方法中,在这里做替换图标工作
@Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
boolean replaceExisting) {
final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
CacheEntry entry = null;
if (!replaceExisting) {
entry = mCache.get(key);
// We can't reuse the entry if the high-res icon is not present.
if (entry == null || entry.isLowResIcon || entry.icon == null) {
entry = null;
}
}
if (entry == null) {
entry = new CacheEntry();
Bitmap icon = replaceIconAfterLanguageChanged(app);
entry.icon = icon;
}
entry.title = app.getLabel();
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
return newContentValues(entry.icon, entry.title.toString(), mActivityBgColor);
}
private Bitmap replaceIconAfterLanguageChanged(LauncherActivityInfoCompat app){
return IconReplaceUtil.replaceIcon(mContext,app,mIconDpi);
}
public static Bitmap replaceIcon(Context mContext,LauncherActivityInfoCompat app,int mIconDpi){
String pkgName = app.getComponentName().getPackageName();
String className = app.getComponentName().getClassName();
Resources resources = mContext.getResources();
Bitmap icon = replaceIcon(pkgName,className,resources);
if(icon!=null){
return icon;
}
else return Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
}
public static Bitmap replaceIcon(String pkgName, String className,Resources resources){
if(pkgName.equals("com.android.soundrecorder")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_record);
}
if(pkgName.equals("com.android.deskclock")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_clock);
}
if(pkgName.equals("com.android.qworldclock")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_world);
}
if(pkgName.equals("com.android.gallery3d")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_gallery);
}
if(pkgName.equals("com.android.music")){
if(className.equals("com.android.music.VideoBrowserActivity")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_play);
}
if(className.equals("com.android.music.MusicBrowserActivity")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_music);
}
}
if(pkgName.equals("com.android.providers.downloads.ui")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_download);
}
if(pkgName.equals("com.android.email")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_email);
}
if(pkgName.equals("com.android.calendar")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_calendar);
}
if(pkgName.equals("com.android.mms")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_sms);
}
if(pkgName.equals("com.android.calculator2")){
return BitmapFactory.decodeResource(resources,R.drawable.icon_calculator2);
}
到这里,基本上替换图标的工作就完成了 完成AllApps界面,拖拽到workspace,更改系统语言都不会失效。
二 、修改主页默认布局
1 主页布局包括 行列数, 默认桌面图标和widget,hotseat 默认图标。
行列数 修改InvariantDeviceProfile.java中 几个属性值就好
这里大概说明一下
public int numRows; // workspace 行
public int numColumns; //workspace 列
public int numFolderRows; // 文件夹中图标行
public int numFolderColumns; //文件件中图标的列
float iconSize; 图标大小
float iconTextSize; 图标标题大小
float numHotseatIcons; 热区图表数
float hotseatIconSize; 热区图表大小
int defaultLayoutId; 默认加载的桌面布局属性xml文件的资源id
修改相应属性值即可生效
比如如果要让系统强制加载默认的5*5布局,就在 defaultLayoutId赋值的两个方法中,将id写死为R.xml.default_workspace5*5即可。
其他配置方法同上。
id写死为该xml文件,就要在该xml中配置默认的一些属性,比如你想默认放置哪些图标和插件(4.4之后默认放置插件的权限被谷歌提高,普通应用设置该属性无效,必须打包进system/priv-app中才可以设置默认小插件)在桌面上,就配置工程中的res/xml/default_workspace_4*4 5*5 5*6 这几个xml文件。
配置方法如下所示
//这是hotseat的默认配置 我放在下面
三 过滤特定图标不在Allapps界面显示
这个主要在LaunchModel.java的 loadAllApps方法中
private void loadAllApps() {
final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Callbacks oldCallbacks = mCallbacks.get();
....
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
if("com.android.dialer".equals(apps.get(i).getApplicationInfo().packageName)
||"com.caf.fmradio".equals(apps.get(i).getApplicationInfo().packageName)){
continue; //过滤操作就在这里 所有app信息会加载后存入mBgAllAppsList 中
}
// replaceIcon(apps.get(i));
LauncherActivityInfoCompat app = apps.get(i);
// This builds the icon bitmaps.
mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
}
final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
if (heuristic != null) {
final Runnable r = new Runnable() {
@Override
public void run() {
heuristic.processUserApps(apps);
}
};
runOnMainThread(new Runnable() {
@Override
public void run() {
......
}
}
Launcher3的修改基本都只是一些小修改,整体难度不高