OverviewMode其实就是长按桌面空白处进去(或按menu键进入)的那个界面,一般也称之编辑模式页面,里面一般包含了壁纸的设置和插件的设置。
首先说下布局,其布局还是在launcher.xml里,然后上部分显示的是workspace的布局,下面布局则是overview_panel.xml。overview_panel里包含了wallpaper_button,widget_button和settings_button这三个TextView。然后在Launcher.java的setupViews()里初始化这三个view,并设置其对应点击事件,从点击事件的处理方法可以知道,settings_button的事件其实并没有实际的实现,所以这个view一般不显示。
进入/退出OverviewMode分别对应Workspace里的enterOverviewMode和exitOverviewMode方法,这两个方法最终都调用了enableOverviewMode:
private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
State finalState = Workspace.State.OVERVIEW;
if (!enable) {
finalState = Workspace.State.NORMAL;
}
Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
if (workspaceAnim != null) {
onTransitionPrepare();
workspaceAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator arg0) {
onTransitionEnd();
}
});
workspaceAnim.start();
}
}
这里通过finalState等参数调用getChangeStateAnimation来获取状态改变的动画对象,然后开始动画。这里Workspace.State.OVERVIEW状态就表示显示的是OverviewMode页面,Workspace.State.NORMAL则显示的是Workspace页面。
点击widget_button,调用onClickWallpaperPicker方法启动设置壁纸的activity,如下:
protected void onClickWallpaperPicker(View v) {
final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
pickWallpaper.setComponent(getWallpaperPickerComponent());
startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onClickWallpaperPicker(v);
}
}
通过Intent.ACTION_SET_WALLPAPER,查看AndroidManifest.xml,可以知道是启动了LauncherWallpaperPickerActivity这个Activity,LauncherWallpaperPickerActivity继承WallpaperPickerActivity(WallpaperPickerActivity是在WallpaperPicker工程里面),WallpaperPickerActivity又继承WallpaperCropActivity,那就从WallpaperCropActivity的onCreate开始吧。onCreate里主要就调用了init(),然后WallpaperPickerActivity里又重写了init()方法。
那简单地看看WallpaperPickerActivity里的init():
protected void init() {
setContentView(R.layout.wallpaper_cropper);
mCropView = (CropView) findViewById(R.id.cropView);
......
// Populate the built-in wallpapers
ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(this, wallpapers);
......
// 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) {
if (mSelectedTile != null) {
WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
info.onSave(WallpaperPickerActivity.this);
} else {
// no tile was selected, so we just finish the activity and go back
setResult(Activity.RESULT_OK);
finish();
}
}
});
......
}
init()主要是初始化布局和一些变量,设置一些动画,事件监听等。其中调用findBundledWallpapers来加载内置的壁纸,findBundledWallpapers里调用getWallpaperArrayResourceId来加载wallpapers.xml里配置的string-array数据源。所以想设置一些内置的壁纸可以在这数据源里面设置。
actionBar整个标题栏的响应事件其实是实现设置壁纸这一功能,可以看到调用了WallpaperTileInfo的onSave,WallpaperTileInfo是一个抽象类,系统根据图片来选择使用哪一个子类,再调用其onSave方法,可以看下ResourceWallpaperInfo:
@Override
public void onSave(WallpaperPickerActivity a) {
boolean finishActivityWhenDone = true;
a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
}
最终是调用了cropImageAndSetWallpaper方法。cropImageAndSetWallpaper的实现在WallpaperCropActivity里:
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();
}
这里启动了一个异步任务BitmapCropTask来处理,execute()之后会调用doInBackground。
protected Boolean doInBackground(Void... params) {
return cropBitmap();
}
可以看到doInBackground里只是调用了cropBitmap:
public boolean cropBitmap() {
boolean failure = false;
WallpaperManager wallpaperManager = null;
if (mSetWallpaper) {
wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
}
if (mSetWallpaper && mNoCrop) {
try {
InputStream is = regenerateInputStream();
if (is != null) {
wallpaperManager.setStream(is);
Utils.closeSilently(is);
}
} catch (IOException e) {
Log.w(LOGTAG, "cannot write stream to wallpaper", e);
failure = true;
}
return !failure;
} else {
.....
}
.....
}
这里先是实例化了wallpaperManager对象,然后调用其setStream方法实现壁纸的设置。
WallpaperPicker里其实还有很多操作,比如滑动,点击不同区域动画效果等,具体也没细看,就不说了。
Widget是Launcher里的小部件,也叫桌面插件。apps_customize_widget.xml就是一个Widget的布局,里面包括了Widget预览图和Widget的名称尺寸。每个Widget的初始化是在AppsCustomizePagedView的syncWidgetPageItems里,看下syncWidgetPageItems的实现:
public void syncWidgetPageItems(final int page, final boolean immediate) {
int numItemsPerPage = mWidgetCountX * mWidgetCountY;
final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
......
for (int i = 0; i < items.size(); ++i) {
Object rawInfo = items.get(i);
PendingAddItemInfo createItemInfo = null;
PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(R.layout.apps_customize_widget, layout, false);
.....
GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
GridLayout.spec(iy, GridLayout.START),
GridLayout.spec(ix, GridLayout.TOP));
lp.width = cellWidth;
lp.height = cellHeight;
lp.setGravity(Gravity.TOP | Gravity.START);
layout.addView(widget, lp);
}
......
}
mWidgetCountX和mWidgetCountY是对应Widget里面的排列是几行几列的,在AppsCustomizePagedView.java构造函数里初始化。
PagedViewGridLayout是一个网格布局,从代码可以看到把一个个PagedViewWidget对应new了一个GridLayout.LayoutParams(这里就可以修改Widget显示的宽高了),然后都放入到了这个网格布局里去。
1.预览图的加载
Widget里显示的预览图的加载,在WidgetPreviewLoader.java的generateWidgetPreview里实现:
public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, int cellHSpan, int cellVSpan,int maxPreviewWidth, int maxPreviewHeight, Bitmap preview, int[] preScaledWidthOut) {
// Load the preview image if possible
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE;
Drawable drawable = null;
if (info.previewImage != 0) {
drawable = mManager.loadPreview(info);
if (drawable != null) {
drawable = mutateOnMainThread(drawable);
} else {
Log.w(TAG, "Can't load widget preview drawable 0x" +
Integer.toHexString(info.previewImage) + " for provider: " + info.provider);
}
}
int previewWidth;
int previewHeight;
Bitmap defaultPreview = null;
boolean widgetPreviewExists = (drawable != null);
if (widgetPreviewExists) {
previewWidth = drawable.getIntrinsicWidth();
previewHeight = drawable.getIntrinsicHeight();
}
.....
if (scale != 1f) {
previewWidth = (int) (scale * previewWidth);
previewHeight = (int) (scale * previewHeight);
}
// If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size
if (preview == null) {
preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
}
// Draw the scaled preview into the final bitmap
int x = (preview.getWidth() - previewWidth) / 2;
if (widgetPreviewExists) {
renderDrawableToBitmap(drawable, preview, x, 0, previewWidth,previewHeight);
}
......
return mManager.getBadgeBitmap(info, preview);
}
代码里的drawable就是加载的预览图,一般加载的是从插件apk里默认配置的预览图(这里也可以修改成我们想要的图),然后加载不到图片,那就只能生成一个了。
previewWidth和previewHeight对应的是Widget显示的预览图的宽高。
最终的preview就是我们的预览图了。
2.其他
另外,PagedViewWidget.java的onFinishInflate里可以设置widget_preview的背景,即预览图后面的背景;AppsCustomizePagedView.java的onPackagesUpdated里可以过滤一些不想让其显示的Widget。