最近工作都在修改Launcher,所以打算把分析源码和修改源码的过程记录下来,最近会写一些关于Launcher的分析和修改博文。因为我是修改4.0.3的Launcher,所以后面文章里面的Launcher都是基于Android4.0.3的Launcher2修改。Launcher源码比较多,而且里面应用了很多设计模式,要把它分析清楚要花不少精力,网上也有一些零碎的分析文章,不过关于修改的文章不多。所以打算写一些分析和修改Launcher结合的文章。
原创博文,转载请标明出处:http://www.cnblogs.com/mythou/p/3153880.html
今天主要是分析修改Launcher的默认界面如何配置和修改。Launcher修改是最近才开始,下面两张图片是最近修改后的结果。因为程序是用于车载导航仪的,所以界面和一般的手机界面差别较大。改动也比较大,不过对于Launcher的分析修改都是通用的。
这是基于Android4.0.3修改后的Launcher界面,因为程序是用在汽车导航上,所以图标做了放大操作。删除了一些不需要的东西。
下面针对界面修改的地方做分析。
1、界面默认配置文件
机器刚升级的时候,Launcher的界面是默认读取一个xml配置文件,完成配置工作。这个配置文件在Launcher目录下,
路径是:\Launcher\res\xml\default_workspace.xml 。这个XML文件就是刚升级,Launcher第一次显示的时候,会读取的配置文件。
default_workspace。xml里面可以配置APP快捷方式、Widget、Search搜索栏等。下面就常用的这几个属性进行解析:
//Edited by mythou
//http://www.cnblogs.com/mythou/
快捷方式说明
<favorite //程序快捷键属性标签
launcher:className="com.apical.radio.radioMainActivity" //该应用的类,点击图标时,需要启动的类
launcher:packageName="com.apical.radio" //该应用的包名
launcher:screen="1" //第1屏,0-4屏共5屏
launcher:x="0" //图标X位置,左上角第一个为0,向左递增,0-4共5个
l0auncher:y="0" //图标Y位置,左上角第一个为0,向下递增,0-2共3个
/>
Launcher默认是有5个分屏,不过这个可以配置。同样,每行每列有多少图标也是可以配置的,这个在后面会说在哪里可以修改。这里按我修改的是3行5列的界面排布(对应上面的效果图)。一般配置APP的快捷方式,使用上面的属性标签就可以。
//Edited by mythou //http://www.cnblogs.com/mythou/
//桌面Widget的标签
<appwidget //插件
launcher:className="de.dnsproject.clock_widget_main.Clock1AppWidgetProvider" //该应用的类
launcher:packageName="de.dnsproject.clock_widget_main" //该应用的包名
launcher:screen="1" //第1屏,0-4屏共5屏
launcher:x="2" //图标X位置,左上角第一个为0,向左递增,0-4共5个
launcher:y="1" //图标Y位置,左上角第一个为0,向下递增,0-2共3个
launcher:spanX="3" //在x方向上所占格数
launcher:spanY="2" /> //在y方向上所占格数
桌面Widget跟桌面快捷方式属性类型,不过这里需要注意launcher:spanX和launcher:spanY 这两个属性是说明Widget多大的,这个和Widget的最小宽高配置有关。我们在编写桌面Widget的时候,需要在XML配置文件里面指定Widget最小的宽和高,一般最小宽高计算公式是(minWidth = 72*占用格数-2) 计算出来,最小高度也是一样。(上面那个模拟时钟是MIUI的时钟)
minWidth = 72*占用格数-2里面的占用格数就是上面launcher:spanX和launcher:spanY配置的数目。针对上面的效果图,就是占用了3个横向的格子,2个竖向的格子。minWidth应该等于214。
<search //搜索栏 launcher:screen="1" //第2屏 launcher:x="0" //图标X位置 launcher:y="1"/> //图标Y位置
这个是搜索栏的配置,因为我这里不需要用到搜索栏,所以把它去掉了,如果需要配置可以使用上面的属性标签。
至于文件夹,在4.0的Launcher里面是支持的,分析加载函数里面,可以找到解析文件夹标签的方法。
上面界面默认配置就是通过使用上面的标签修改default_workspace.xml配置的。
下面列出default_workspace支持的标签和属性:
//Edited by mythou
//http://www.cnblogs.com/mythou/
//default_workspace.xml中,支持的标签有:
favorite:应用程序快捷方式。
shortcut:链接,如网址,本地磁盘路径等。
search:搜索框。
clock:桌面上的钟表Widget
//支持的属性有:
launcher:title:图标下面的文字,目前只支持引用,不能直接书写字符串;
launcher:icon:图标引用;
launcher:uri:链接地址,链接网址用的,使用shortcut标签就可以定义一个超链接,打开某个网址。
launcher:packageName:应用程序的包名;
launcher:className:应用程序的启动类名;
launcher:screen:图标所在的屏幕编号;
launcher:x:图标在横向排列上的序号;
launcher:y:图标在纵向排列上的序号;
Launcher里面负责解析default_workspace.xml文件的方法是 LauncherProvider.java里面的loadFavorites方法。
2、LauncherProvider.java的loadFavorites分析:
//Edited by mythou //http://www.cnblogs.com/mythou/ //传入default_workspace文件的资源ID和数据库实力,把xml里面数据解析,保存到Launcher数据库。返回总共解析了多少个标签。 private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { //......... int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } boolean added = false; final String name = parser.getName(); TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite); long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; if (a.hasValue(R.styleable.Favorite_container)) { container = Long.valueOf(a.getString(R.styleable.Favorite_container)); } String screen = a.getString(R.styleable.Favorite_screen); String x = a.getString(R.styleable.Favorite_x); String y = a.getString(R.styleable.Favorite_y); // If we are adding to the hotseat, the screen is used as the position in the // hotseat. This screen can't be at position 0 because AllApps is in the // zeroth position. if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && Integer.valueOf(screen) == allAppsButtonRank) { throw new RuntimeException("Invalid screen position for hotseat item"); } values.clear(); values.put(LauncherSettings.Favorites.CONTAINER, container); values.put(LauncherSettings.Favorites.SCREEN, screen); values.put(LauncherSettings.Favorites.CELLX, x); values.put(LauncherSettings.Favorites.CELLY, y); //解析xml里面的标签,从这里可以找到支持的标签类型和相关属性参数。 if (TAG_FAVORITE.equals(name)) { long id = addAppShortcut(db, values, a, packageManager, intent); added = id >= 0; } else if (TAG_SEARCH.equals(name)) { added = addSearchWidget(db, values); } else if (TAG_CLOCK.equals(name)) { added = addClockWidget(db, values); } else if (TAG_APPWIDGET.equals(name)) { added = addAppWidget(parser, attrs, type, db, values, a, packageManager); } else if (TAG_SHORTCUT.equals(name)) { long id = addUriShortcut(db, values, a); added = id >= 0; } else if (TAG_FOLDER.equals(name)) { //......... //folder属性里面的参数要多于2个,才能形成文件夹。 if (folderItems.size() < 2 && folderId >= 0) { // We just delete the folder and any items that made it deleteId(db, folderId); if (folderItems.size() > 0) { deleteId(db, folderItems.get(0)); } added = false; } } if (added) i++; a.recycle(); } //......... return i; }
其实就是一个分析XML和写入数据库的过程,LauncherProvider.java是整个Launcher的数据来源,十分重要,后面我再具体分析数据加载和适配显示方面的逻辑。
另外还有一个问题补充一下,就是有关Android截图问题,因为我开发的机器不能使用USB调试,而且没有摇动之类的传感器,很多手机上截图方法都用不了,查了一下,可以使用screencap命令来截图,具体方法可以参考我另外一篇文章:http://www.cnblogs.com/mythou/p/3152627.html
至于图标加入默认背景或者强制转换APP快捷方式图标,修改图标大小和行列数,以及如何配置默认背景,明天再写另外文章说明。
上一篇文章说了如何修改Android自带Launcher2的默认界面设置(http://www.cnblogs.com/mythou/p/3153880.html)。
今天主要是说说Launcher里面图标、布局、壁纸等的设置问题。毕竟我们一般修改Launcher,这些都是需要修改的地方,也是比较容易修改的部分。按照效果图(效果图在上一篇文章),分开说明如何修改,以及里面涉及的逻辑分析。
原创博文,转载请标明出处:http://www.cnblogs.com/mythou/p/3155692.html
1、图标大小和标题大小
Android原生图标大小都是通过配置文件设置,配置文件路径是/res/values/dimens.xml 。需要注意的是,values对应的文件夹很多,因为是多国语言支持。而dimens.xml在默认的values文件夹下面或者values-land和values-port(针对不同分辨率和平板类型,需要根据你运行情况找对应的dimens修改,如果需要使用多种分辨率,最好所有dimens都修改好)。下面是配置workspace的按钮属性配置。下面是针对values-land修改的cell大小。
//Edited by mythou
//http://www.cnblogs.com/mythou/
<!-- Workspace cell size -->
<dimen name="workspace_cell_width_land">88dp</dimen>
<dimen name="workspace_cell_width_port">96dp</dimen>
<dimen name="workspace_cell_height_land">88dp</dimen>
<dimen name="workspace_cell_height_port">96dp</dimen>
<dimen name="workspace_width_gap_land">32dp</dimen>
<dimen name="workspace_width_gap_port">0dp</dimen>
<dimen name="workspace_height_gap_land">0dp</dimen>
<dimen name="workspace_height_gap_port">24dp</dimen>
<!-- Folders -->
<dimen name="folder_preview_size">68dp</dimen>
<dimen name="folder_cell_width">86dp</dimen>
<dimen name="folder_cell_height">90dp</dimen>
<dimen name="folder_width_gap">3dp</dimen>
<dimen name="folder_height_gap">3dp</dimen>
<dimen name="folder_padding">6dp</dimen>
2、Launcher 图标加入默认背景。
Launcher默认图标是各自应用程序设计的,这也导致了,界面图标看上去大小不同意,有点凌乱的感觉。如果整个系统是自己修改的,内置应用图标可以设置统一风格大小。如果是第三方程序就很难保证,所以一个折中办法是给所有图标加入一个背景,看上去风格一致。
Launcher图标的获取处理是在Utilities.java类里面,我们可以从里面找到Bitmap createIconBitmap(Drawable icon, Context context) 方法。这个方法就是返回应用图标的。默认createIconBitmap里面有个加入五颜六色背景的方法,不过是屏蔽了,我们可以参考这个方法,加入一个默认背景图。
//Edited by mythou
//http://www.cnblogs.com/mythou/
static Bitmap createIconBitmap(Drawable icon, Context context) {
//...............
final int left = (textureWidth-width) / 2;
final int top = (textureHeight-height) / 2;
//测试用,加入彩色背景边框
if (false)
{
// draw a big box for the icon for debugging
canvas.drawColor(sColors[sColorIndex]);
if (++sColorIndex >= sColors.length) sColorIndex = 0;
Paint debugPaint = new Paint();
debugPaint.setColor(0xffcccc00);
canvas.drawRect(left, top, left+width, top+height, debugPaint);
}
//增加图标背景图片 OWL
if (true)
{
Bitmap backBitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.apical_icon_bg);
int backWidth = backBitmap.getWidth();
int backHeight = backBitmap.getHeight();
if(backWidth != sIconWidth || backHeight != sIconHeight)
{
Matrix matrix = new Matrix();
matrix.postScale((float)sIconWidth/backWidth, (float)sIconHeight/backHeight);
canvas.drawBitmap(Bitmap.createBitmap(backBitmap, 0, 0, backWidth, backHeight, matrix, true),
0.0f, 0.0f, null);
}else
{
canvas.drawBitmap(backBitmap, 0.0f, 0.0f, null);
}
}
//................
return bitmap;
}
}
如图上面代码,加入了 R.drawable.apical_icon_bg 一张默认的背景图作为背景,这样所有图标看上去大小都是一致的。而且风格也比较接近。对于做系统应用的人来说体验比较好。如果需要一些比较炫的效果,其实可以在这里给图标做倒影或者其他效果。
3、更换Launcher默认壁纸
Launcher默认的壁纸配置是放在framework下的res下面配置的,图片也是放在framework下面。对于需要做独立Launcher的项目,这个不能离开framework,非常不方便。所以就在Launcher自身实现一个默认壁纸设置的功能。默认壁纸,只能在Launcher第一次运行或者恢复默认设置时才需要设置显示。
因此我把默认壁纸反正设置放在Launcher.java类的onCreate()方法下的showFirstRunWorkspaceCling()执行。
showFirstRunWorkspaceCling()方法,只有Launcher第一次启动或者清除数据,恢复设置。才会运行。在showFirstRunWorkspaceCling()里面调用下面的方法
//Edited by mythou
//http://www.cnblogs.com/mythou/
private void setDefaultWallPaper()
{
//修改默认背景 OWL test,可以在Framework替换默认静态图default_wallpaper
WallpaperManager mwallpaerManager;
mwallpaerManager = WallpaperManager.getInstance(this);
try
{
mwallpaerManager.setResource(R.drawable.launcher_default_bg);
}
catch (IOException e)
{
Log.e(TAG, "set default wallpaper error");
e.printStackTrace();
}
}
Launcher的壁纸都是调用系统WallpaperManager操作的,具体有兴趣可以查看源码。
android的静态壁纸,默认是frameworks/base/core/res/res/drawable/default_wallpaper.jpg。如果是做系统的,可以替换这张jpg图片也可以达到替换默认壁纸功能。
至于默认动态壁纸,这个要修改/framework/base/core/res/res/values/config.xml 。
<string name="default_wallpaper_component">@null</string>
//把null修改为具体程序名,如下
<string name="default_wallpaper_component" translatable="false">包名/动态壁纸服务名</string>
另外Launcher自身自带了一些默认壁纸,反正Launcher的res/drawable文件夹下,不同分辨率文件夹放了大小不一样。
这些默认壁纸在Launcher下values/wallpapers.xml下有配置。
//Edited by mythou
//http://www.cnblogs.com/mythou/
<resources>
<string-array name="wallpapers" translatable="false">
<item>wallpaper_01</item>
<item>wallpaper_02</item>
<item>wallpaper_03</item>
<item>wallpaper_04</item>
<item>wallpaper_05</item>
<item>wallpaper_06</item>
<item>wallpaper_07</item>
<item>wallpaper_08</item>
<item>wallpaper_09</item>
<item>wallpaper_10</item>
<item>wallpaper_11</item>
<item>wallpaper_12</item>
</string-array>
</resources>
不过系统默认最多是支持24张图片。可以修改壁纸名字或者新增壁纸都是可以的。壁纸名字跟图片名字一一对应。
4、壁纸设置过程:
具体壁纸操作其实都是在WallpaperChooserDialogFragment这个Fragment里面实现,
查找默认壁纸:
//Edited by mythou
//http://www.cnblogs.com/mythou/
private void findWallpapers() {
mThumbs = new ArrayList<Integer>(24);
mImages = new ArrayList<Integer>(24);
final Resources resources = getResources();
// Context.getPackageName() may return the "original" package name,
// com.android.launcher2; Resources needs the real package name,
// com.android.launcher. So we ask Resources for what it thinks the
// package name should be.
final String packageName = resources.getResourcePackageName(R.array.wallpapers);
addWallpapers(resources, packageName, R.array.wallpapers);
addWallpapers(resources, packageName, R.array.extra_wallpapers);
}
其中R.array.wallpapers 就是上面说的默认壁纸名字的xml配置文件。通过这个文件加载所有默认壁纸。除了wallpapers 还有一个extra_wallpapers。这两个功能原理是一样的。
设置壁纸比较简单,跟我们设置默认壁纸一样
//Edited by mythou
//http://www.cnblogs.com/mythou/
private void selectWallpaper(int position) {
try {
WallpaperManager wpm = (WallpaperManager) getActivity().getSystemService(
Context.WALLPAPER_SERVICE);
wpm.setResource(mImages.get(position)); //设置壁纸
Activity activity = getActivity();
activity.setResult(Activity.RESULT_OK);
activity.finish();
} catch (IOException e) {
Log.e(TAG, "Failed to set wallpaper: " + e);
}
}
主要还是调用WallpaperManager的setResource方法。
今天就写到这里,如果有发现哪里写错了请留言,谢谢!