/**
* Event handler for the wallpaper picker button that appears after a long press
* on the home screen.
*/
protected void onClickWallpaperPicker(View v) {
if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
pickWallpaper.setComponent(getWallpaperPickerComponent());
startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onClickWallpaperPicker(v);
}
}
protected ComponentName getWallpaperPickerComponent() {
if (mLauncherCallbacks != null) {
return mLauncherCallbacks.getWallpaperPickerComponent();
}
return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
}
很显然,
LauncherWallpaperPickerActivity
就是壁纸设置界面了,
LauncherWallpaperPickerActivity继承于
WallpaperPickerActivity,
WallpaperPickerActivity又继承于
WallpaperCropActivity,这么多继承,看来这个界面还是比较复杂的。从命名来看的话,也是为了区分每个类的处理重点,WallpaperCropActivity用来进行壁纸的裁剪,将图片裁剪到合适的尺寸;WallpaperPickerActivity就是壁纸选择器,选择壁纸并设置;至于LauncherWallpaperPickerActivity,从代码中看到只是重写了父类的两个方法,没什么可分析的,这里我们重点分析WallpaperPickerActivity这个类。
一、壁纸类型对象
作为内部类,定义在WallpaperPickerActivity类中,
public static abstract class WallpaperTileInfo {
protected View mView;
public Drawable mThumb;
public void setView(View v) {
mView = v;
}
public void onClick(WallpaperPickerActivity a) {}// 缩略图点击事件
public void onSave(WallpaperPickerActivity a) {}// 设置壁纸,并做一些保存操作
public void onDelete(WallpaperPickerActivity a) {}// 删除壁纸
public boolean isSelectable() { return false; }// 是否可选
public boolean isNamelessWallpaper() { return false; }// 壁纸是否没有名字
public void onIndexUpdated(CharSequence label) {// 更新索引
if (isNamelessWallpaper()) {
mView.setContentDescription(label);
}
}
}
壁纸对象的一个抽象类,不直接使用,具体的壁纸继承该类并根据自身特点扩展。壁纸来源有多个途径,如应用内置的壁纸、图库、第三方等,另外设为壁纸的方式也不一定相同,需要对不同来源区分处理,所有就定义了以下几个壁纸类对象,
PickImageInfo--图片选择器,在Activity中添加属性,就可以隐式调用到,如图库
UriWallpaperInfo--通过图片的Uri来设置壁纸
FileWallpaperInfo--通过图片文件来设置壁纸
ResourceWallpaperInfo--Launcher3中内置的壁纸资源来设置
DefaultWallpaperInfo--系统默认壁纸,资源在framework中
这几个类实现其抽象父类中的方法,具体代码实现就不一一细说,后面说到具体方法时会举其中的例子来说明,这里对几个抽象方法已经做了注释。
二、加载壁纸列表
图1是壁纸设置界面,界面简单,包含了壁纸列表、设置壁纸按钮以及壁纸预览图等。
图1
WallpaperPickerActivity中没有重写onCreate方法,而是通过父类的onCreate的方法调用了重写的init方法,进行布局的加载和初始化。
1、布局
WallpaperRootView是根视图,继承RelativeLayout自定义的一个视图,重写了fitSystemWindows方法,
protected boolean fitSystemWindows(Rect insets) {
a.setWallpaperStripYOffset(insets.bottom);
return true;
}
这么做的目的是为了让视图内离底部一段距离,否则会出现如图2的情况,跟虚拟键重合,就不大美观了。
图2
setContentView(R.layout.wallpaper_picker);
mCropView = (CropView) findViewById(R.id.cropView);
mCropView.setVisibility(View.INVISIBLE);// 默认是不可见的
mWallpaperStrip = findViewById(R.id.wallpaper_strip);
1)CropView--裁剪视图,用于壁纸的裁剪、预览,还有手势操作(两个手指缩放)。
2)进度条--加载该界面时的进度条。
3)壁纸列表--LinearLayout布局块,其中的子视图HorizontalScrollView是一个横向的滑动视图,就是我们的壁纸列表,也是根据壁纸类型的分了多个布局块,分别加载。
好像还少了ActionBar,这个是在代码中动态添加的,下面会说到。
2、接口回调和监听事件
mCropView.setTouchCallback(new CropView.TouchCallback() {
ViewPropertyAnimator mAnim;
@Override
public void onTouchDown() {
if (mAnim != null) {
mAnim.cancel();
}
if (mWallpaperStrip.getAlpha() == 1f) {
mIgnoreNextTap = true;
}
mAnim = mWallpaperStrip.animate();
mAnim.alpha(0f)
.setDuration(150)
.withEndAction(new Runnable() {
public void run() {
mWallpaperStrip.setVisibility(View.INVISIBLE);
}
});
mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
mAnim.start();
}
@Override
public void onTouchUp() {
mIgnoreNextTap = false;
}
@Override
public void onTap() {
boolean ignoreTap = mIgnoreNextTap;
mIgnoreNextTap = false;
if (!ignoreTap) {
if (mAnim != null) {
mAnim.cancel();
}
mWallpaperStrip.setVisibility(View.VISIBLE);
mAnim = mWallpaperStrip.animate();
mAnim.alpha(1f)
.setDuration(150)
.setInterpolator(new DecelerateInterpolator(0.75f));
mAnim.start();
}
}
});
CropView的touch回调处理,这里只做了一些动画效果,具体裁剪的操作还是在CropView中实现的,这里就不详细说明了。
mThumbnailOnClickListener = new OnClickListener() {
public void onClick(View v) {
if (mActionMode != null) {
// When CAB is up, clicking toggles the item instead
if (v.isLongClickable()) {
mLongClickListener.onLongClick(v);
}
return;
}
mSetWallpaperButton.setEnabled(true);
WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
selectTile(v);
}
info.onClick(WallpaperPickerActivity.this);// 缩略图点击事件
}
};
缩略图点击事件,如果处于ActionMode(长按事件),处理长按事件,否则回调该壁纸所实现的onClick方法,启用mSetWallpaperButton,该控件定义在其父类WallpaperCropActivity中,
// Action bar
// 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) {
boolean finishActivityWhenDone = true;
cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
}
});
mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
mLongClickListener = new View.OnLongClickListener() {
// Called when the user long-clicks on someView
public boolean onLongClick(View view) {
CheckableFrameLayout c = (CheckableFrameLayout) view;
c.toggle();
if (mActionMode != null) {
mActionMode.invalidate();
} else {
// Start the CAB using the ActionMode.Callback defined below
mActionMode = startActionMode(mActionModeCallback);
int childCount = mWallpapersView.getChildCount();
for (int i = 0; i < childCount; i++) {
mWallpapersView.getChildAt(i).setSelected(false);
}
}
return true;
}
};
定义了缩略图长按事件,并不是所有的壁纸类型都设置了长按事件,下面会讲到。
3、获取壁纸资源,将缩略图加载到横向scrollview
1)添加Launcher3中内置的壁纸资源和系统默认壁纸
// Populate the built-in wallpapers
// 填充内置壁纸,资源文件配置的壁纸和系统默认壁纸
ArrayList wallpapers = findBundledWallpapers();
mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(this, wallpapers);
populateWallpapersFromAdapter(mWallpapersView, ia, false);
通过
findBundledWallpapers
来查找壁纸,
private ArrayList findBundledWallpapers() {
final PackageManager pm = getPackageManager();
final ArrayList bundled = new ArrayList(24);
Partner partner = Partner.get(pm);
if (partner != null) {
final Resources partnerRes = partner.getResources();
final int resId = partnerRes.getIdentifier(Partner.RES_WALLPAPERS, "array",
partner.getPackageName());
if (resId != 0) {
addWallpapers(bundled, partnerRes, partner.getPackageName(), resId);
}
// Add system wallpapers
File systemDir = partner.getWallpaperDirectory();
if (systemDir != null && systemDir.isDirectory()) {
for (File file : systemDir.listFiles()) {
if (!file.isFile()) {
continue;
}
String name = file.getName();
int dotPos = name.lastIndexOf('.');
String extension = "";
if (dotPos >= -1) {
extension = name.substring(dotPos);
name = name.substring(0, dotPos);
}
if (name.endsWith("_small")) {
// it is a thumbnail
continue;
}
File thumbnail = new File(systemDir, name + "_small" + extension);
Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath());
if (thumb != null) {
bundled.add(new FileWallpaperInfo(file, new BitmapDrawable(thumb)));
}
}
}
}
// 添加Launcher中配置的壁纸
Pair r = getWallpaperArrayResourceId();
if (r != null) {
try {
Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
} catch (PackageManager.NameNotFoundException e) {
}
}
// 创建一个空的实体,用于放置默认壁纸
if (partner == null || !partner.hideDefaultWallpaper()) {
// Add an entry for the default wallpaper (stored in system resources)
WallpaperTileInfo defaultWallpaperInfo =
(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
? getPreKKDefaultWallpaperInfo()
: getDefaultWallpaper();
if (defaultWallpaperInfo != null) {
bundled.add(0, defaultWallpaperInfo);
}
}
return bundled;
}
加载系统中有监听特定广播的应用中的资源,这个广播是"com.android.launcher3.action.PARTNER_CUSTOMIZATION"
加载Launcher3中配置的壁纸,这些壁纸放在drawable-xxx目录下,并在wallpapers.xml中配置(必须有原图和缩略图)
- zzz_wallpaper
- zzz_wallpaper_small
加载默认壁纸,默认壁纸放在framework资源目录下
这样就获取到壁纸列表,定义适配器,通过
populateWallpapersFromAdapter
方法将其显示,
private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
boolean addLongPressHandler) {
for (int i = 0; i < adapter.getCount(); i++) {
FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
parent.addView(thumbnail, i);
WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
thumbnail.setTag(info);
info.setView(thumbnail);
if (addLongPressHandler) {// 是否添加长按事件,只对数据库中保存的壁纸处理
addLongPressHandler(thumbnail);
}
thumbnail.setOnClickListener(mThumbnailOnClickListener);
}
}
这个方法比较好理解,需要注意的是第三个参数,这个布尔值用来确定该类型壁纸是否添加长按事件,这里是false,不添加;根据后面的分析来看,也只有保存在数据库中的壁纸添加该操作,这也好理解,因为其他几种类型都不是用户自己定义的,不允许删除壁纸,长按操作就是用来删除该壁纸的。
2)添加保存在数据库中的壁纸
// Populate the saved wallpapers
// 填充保存在数据库中的壁纸
mSavedImages = new SavedWallpaperImages(this);
mSavedImages.loadThumbnailsAndImageIdList();
populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
3)添加动态壁纸
// Populate the live wallpapers
// 填充动态壁纸
final LinearLayout liveWallpapersView =
(LinearLayout) findViewById(R.id.live_wallpaper_list);
final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
a.registerDataSetObserver(new DataSetObserver() {
public void onChanged() {
liveWallpapersView.removeAllViews();
populateWallpapersFromAdapter(liveWallpapersView, a, false);
initializeScrollForRtl();
updateTileIndices();
}
});
在Android中,除了可以显示静态壁纸外,也可以使用动态壁纸。当然,跟普通的壁纸不同的是,它是已apk的形式安装到手机中的(至于怎么制作一个动态壁纸的apk,不是我们这边所讲的,就不阐述了),加载动态壁纸就是要查找系统中已安装的动态壁纸应用。
动态壁纸也定义了一个适配器类LiveWallpaperListAdapter,定义动态壁纸对象,查找动态壁纸应用等。
public LiveWallpaperListAdapter(Context context) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPackageManager = context.getPackageManager();
List list = mPackageManager.queryIntentServices(
new Intent(WallpaperService.SERVICE_INTERFACE),
PackageManager.GET_META_DATA);
mWallpapers = new ArrayList();
new LiveWallpaperEnumerator(context).execute(list);
}
这是构造方法,查询action为
"android.service.wallpaper.WallpaperService"
的service,这是动态壁纸应用中必须配置的,如果我们自己想做一个动态壁纸也是要添加这个action的。
public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
private Drawable mThumbnail;
private WallpaperInfo mInfo;
public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
mThumbnail = thumbnail;
mInfo = info;
}
@Override
public void onClick(WallpaperPickerActivity a) {
Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
mInfo.getComponent());
a.onLiveWallpaperPickerLaunch(mInfo);
a.startActivityForResultSafely(preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
}
}
WallpaperTileInfo的子类,然后异步加载信息。
for (ResolveInfo resolveInfo : list) {
WallpaperInfo info = null;
try {
info = new WallpaperInfo(mContext, resolveInfo);
} catch (XmlPullParserException e) {
Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
continue;
} catch (IOException e) {
Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
continue;
}
// 获取动态壁纸信息
Drawable thumb = info.loadThumbnail(packageManager);
Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
launchIntent.setClassName(info.getPackageName(), info.getServiceName());
LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent);
publishProgress(wallpaper);
}
4)第三方壁纸
// Populate the third-party wallpaper pickers
// 填充第三方壁纸选择器
final LinearLayout thirdPartyWallpapersView =
(LinearLayout) findViewById(R.id.third_party_wallpaper_list);
final ThirdPartyWallpaperPickerListAdapter ta =
new ThirdPartyWallpaperPickerListAdapter(this);
populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false);
加载第三方壁纸选择器,这个还是很友好的,这样手机中如果装有其他的第三方壁纸设置的应用,也可以在此处显示出来。查询是在ThirdPartyWallpaperPickerListAdapter适配器类中进行的,这个适配器跟刚才说的动态壁纸适配器类类似。
定义了第三方壁纸对象ThirdPartyWallpaperTile,
public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
private ResolveInfo mResolveInfo;
public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
mResolveInfo = resolveInfo;
}
@Override
public void onClick(WallpaperPickerActivity a) {
final ComponentName itemComponentName = new ComponentName(
mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
launchIntent.setComponent(itemComponentName);
a.startActivityForResultSafely(launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);// 启动第三方壁纸选择器
}
}
在构造方法中查询第三方壁纸应用,
public ThirdPartyWallpaperPickerListAdapter(Context context) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPackageManager = context.getPackageManager();
mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
final PackageManager pm = mPackageManager;
final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
final List apps = pm.queryIntentActivities(pickWallpaperIntent, 0);
// Get list of image picker intents
Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
pickImageIntent.setType("image/*");
final List imagePickerActivities =
pm.queryIntentActivities(pickImageIntent, 0);
final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()];
for (int i = 0; i < imagePickerActivities.size(); i++) {
ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo;
imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name);
}
outerLoop:
for (ResolveInfo info : apps) {
final ComponentName itemComponentName =
new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
final String itemPackageName = itemComponentName.getPackageName();
// Exclude anything from our own package, and the old Launcher,
// and live wallpaper picker
if (itemPackageName.equals(context.getPackageName()) ||
itemPackageName.equals("com.android.launcher") ||
itemPackageName.equals("com.android.wallpaper.livepicker")) {
continue;
}
// Exclude any package that already responds to the image picker intent
for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {
if (itemPackageName.equals(
imagePickerActivityInfo.activityInfo.packageName)) {
continue outerLoop;
}
}
mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));
}
}
根据
"android.intent.action.SET_WALLPAPER"
来查找的,然后做一些过滤,添加到列表中。
5)添加图库
三、壁纸预览和设置
之前说到不同类型的壁纸对象时,会重写父类的方法,实现具体的功能,这里我们已ResourceWallpaperInfo为例,来说明壁纸的预览和设置的。
@Override
public void onClick(WallpaperPickerActivity a) {
Log.d("dingfeng","ResourceWallpaperInfo onClick...");
BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
new BitmapRegionTileSource.ResourceBitmapSource(
mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
bitmapSource.loadInBackground();
BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
CropView v = a.getCropView();
v.setTileSource(source, null);
Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
a.getResources(), a.getWindowManager());
RectF crop = WallpaperCropActivity.getMaxCropRect(
source.getImageWidth(), source.getImageHeight(),
wallpaperSize.x, wallpaperSize.y, false);
v.setScale(wallpaperSize.x / crop.width());
v.setTouchEnabled(false);
a.setSystemWallpaperVisiblity(false);
}
@Override
public void onSave(WallpaperPickerActivity a) {
Log.d("dingfeng","ResourceWallpaperInfo onSave...");
boolean finishActivityWhenDone = true;
a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
}
@Override
public boolean isSelectable() {
return true;
}
@Override
public boolean isNamelessWallpaper() {
return true;
}
实现了四个方法,后面两个返回bool值得含义之前已经说过,我们不细说。先看onClick,这个方法在点击缩略图列表是触发,看看它究竟做了什么。
这面用到了BitmapRegionTileSource及其内部类对象,这些类定义在src\main\java\com\android\photos\目录下,自定义了图片对象,实现了滚动、缩放等功能,这里就不展开了,可以自己查看代码 。
生成BitmapRegionTileSource对象后,设置到CropView上,然后做合适的缩放,再将系统壁纸设为不可见,这样就可以达到壁纸预览的目的。
再看onSave方法,这个方法在点击ActionBar时调用,该方法中调用WallpaperCropActivity的cropImageAndSetWallpaper来裁剪和设置壁纸,
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();
}
设置裁剪大小,将其作为参数传递给异步任务执行,
@Override
protected Boolean doInBackground(Void... params) {
return cropBitmap();
}
最终就是
cropBitmap
方法来做最后的裁剪和壁纸设置操作。