Launcher3--壁纸

    在说Launcher上设置壁纸时,首先需要弄清楚的是,壁纸的设置属于系统行为,而不是Launcher的应用特性。在Launcher中,壁纸的设置最终也是通过调用系统壁纸设置接口来完成的,所有,不仅仅是Launcher,很多第三方应用也是可以设置壁纸的。     Android中,可以使用WallpaperManager这一壁纸管理类来设置壁纸,有如下几种方法,
    我们可以根据壁纸资源的不同,选择合适的方法,其中,最后一个可以用来设置动态壁纸。

    下面就来说说Launcher3中是如何设置壁纸的,我们直接从壁纸设置界面的入口说起,

    /*** 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;@Overridepublic 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();}@Overridepublic void onTouchUp() {mIgnoreNextTap = false;}@Overridepublic 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 insteadif (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 viewfinal ActionBar actionBar = getActionBar();actionBar.setCustomView(R.layout.actionbar_set_wallpaper);actionBar.getCustomView().setOnClickListener(new View.OnClickListener() {@Overridepublic 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 someViewpublic boolean onLongClick(View view) {CheckableFrameLayout c = (CheckableFrameLayout) view;c.toggle();if (mActionMode != null) {mActionMode.invalidate();} else {// Start the CAB using the ActionMode.Callback defined belowmActionMode = 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 wallpapersFile 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 thumbnailcontinue;}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_wallpaperzzz_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;}@Overridepublic 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;}@Overridepublic 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 intentsIntent 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 pickerif (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 intentfor (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {if (itemPackageName.equals(imagePickerActivityInfo.activityInfo.packageName)) {continue outerLoop;}}mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));}}
    根据 "android.intent.action.SET_WALLPAPER" 来查找的,然后做一些过滤,添加到列表中。
    5)添加图库
        // Add a tile for the Gallery// 列表开头放置图库选择器LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);setWallpaperItemPaddingToZero(pickImageTile);masterWallpaperList.addView(pickImageTile, 0);// Make its background the last photo taken on external storageBitmap lastPhoto = getThumbnailOfLastPhoto();if (lastPhoto != null) {ImageView galleryThumbnailBg =(ImageView) pickImageTile.findViewById(R.id.wallpaper_image);galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);}PickImageInfo pickImageInfo = new PickImageInfo();pickImageTile.setTag(pickImageInfo);pickImageInfo.setView(pickImageTile);pickImageTile.setOnClickListener(mThumbnailOnClickListener);
    在列表开头添加图库入口,这样用户就可以选择任一图片了。
         其他的初始化设置就不一一赘述了。
三、壁纸预览和设置     之前说到不同类型的壁纸对象时,会重写父类的方法,实现具体的功能,这里我们已ResourceWallpaperInfo为例,来说明壁纸的预览和设置的。
        @Overridepublic 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);}@Overridepublic void onSave(WallpaperPickerActivity a) {Log.d("dingfeng","ResourceWallpaperInfo onSave...");boolean finishActivityWhenDone = true;a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);}@Overridepublic boolean isSelectable() {return true;}@Overridepublic 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 deviceint 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 sizeupdateWallpaperDimensions(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();}
    设置裁剪大小,将其作为参数传递给异步任务执行,
        @Overrideprotected Boolean doInBackground(Void... params) {return cropBitmap();}
    最终就是 cropBitmap 方法来做最后的裁剪和壁纸设置操作。     其他几种类型的壁纸也是类似的,根据壁纸来源做出相应的操作,比如第三方壁纸时,点击缩略图就是打开第三方应用;如果是图库,就打开图库,总之都是在这几个重写方法中实现的。如果以后有什么不同于目前几种类型的,也可以依此来扩展。



本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部