最近比較忙 有段時間沒寫博客了 趁着有空 把最近修改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);
}
從IconCache.java的類名就可以看出 這個類是用作緩存系統已安裝軟件的圖標的,在第一次加載成功後會將Icons緩存在一個app_icons.db的數據庫
文件中,下次再讀取就是從數據庫中讀取
所以如果要修改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;
}
跟進favorite.applyFromShortcutInfo(info, mIconCache)中,
該方法在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);
}
其中 replaceIconAfterLanguageChanged()如下
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文件。
配置方法如下所示
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
<!-- Hotseat -->
<include launcher:workspace="@xml/dw_tablet_hotseat" /> //這是hotseat的默認配置 我放在下面
<resolve
launcher:screen="0"
launcher:x="0"
launcher:y="3" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
<favorite launcher:uri="mailto:" />
</resolve>
<resolve
launcher:screen="0" 默認爲第0屏
launcher:x="1" 1列 4行
launcher:y="4" >
<favorite
launcher:packageName="com.android.gallery3d" 包名
launcher:className="com.android.gallery3d.app.GalleryActivity" 類名(也可以用intent的方式配置 如上 )
/>
</resolve>
<resolve
launcher:screen="0"
launcher:x="2"
launcher:y="4" >
<favorite
launcher:packageName="com.android.calendar"
launcher:className="com.android.calendar.AllInOneActivity"
/>
</resolve>
<resolve
launcher:screen="0"
launcher:x="3"
launcher:y="4" >
<favorite
launcher:packageName="com.android.soundrecorder"
launcher:className="com.android.soundrecorder.SoundRecorder"
/>
</resolve>
<resolve
launcher:screen="0"
launcher:x="4"
launcher:y="4" >
<favorite
launcher:packageName="com.android.email"
launcher:className="com.android.email.activity.ComposeActivityEmail"
/>
</resolve>
<appwidget 桌面小圖標
launcher:packageName="com.android.deskclock"
launcher:className="com.android.alarmclock.DigitalAppWidgetProvider"
launcher:screen="0"
launcher:x="1"
launcher:y="0"
launcher:spanX="3" 小圖標占位大小
launcher:spanY="2" />
</favorites>
hotseat的配置和上面基本是一樣的,注意有兩個xml文件,代表手機和平板的。
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
<!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
<!-- Dialer, Messaging, [All Apps], Browser, Camera -->
<resolve
launcher:container="-101"
launcher:screen="1"
launcher:x="1"
launcher:y="0" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
<favorite launcher:uri="sms:" />
<favorite launcher:uri="smsto:" />
<favorite launcher:uri="mms:" />
<favorite launcher:uri="mmsto:" />
</resolve>
<!-- All Apps -->
<!--<resolve-->
<!--launcher:container="-101"-->
<!--launcher:screen="4"-->
<!--launcher:x="4"-->
<!--launcher:y="0" >-->
<!--<favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />-->
<!--<favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />-->
<!--</resolve>-->
</favorites>
三 過濾特定圖標不在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的修改基本都只是一些小修改,整體難度不高