編輯:關於Android編程
/** * 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這個類。
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的情況,跟虛擬鍵重合,就不大美觀了。
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,這個是在代碼中動態添加的,下面會說到。
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; } };
定義了縮略圖長按事件,並不是所有的壁紙類型都設置了長按事件,下面會講到。
// Populate the built-in wallpapers // 填充內置壁紙,資源文件配置的壁紙和系統默認壁紙 ArrayList通過findBundledWallpapers來查找壁紙,wallpapers = findBundledWallpapers(); mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list); SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(this, wallpapers); populateWallpapersFromAdapter(mWallpapersView, ia, false);
private ArrayList加載系統中有監聽特定廣播的應用中的資源,這個廣播是"com.android.launcher3.action.PARTNER_CUSTOMIZATION" 加載Launcher3中配置的壁紙,這些壁紙放在drawable-xxx目錄下,並在wallpapers.xml中配置(必須有原圖和縮略圖)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; }
加載默認壁紙,默認壁紙放在framework資源目錄下- zzz_wallpaper
- zzz_wallpaper_small
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這是構造方法,查詢action為"android.service.wallpaper.WallpaperService"的service,這是動態壁紙應用中必須配置的,如果我們自己想做一個動態壁紙也是要添加這個action的。list = mPackageManager.queryIntentServices( new Intent(WallpaperService.SERVICE_INTERFACE), PackageManager.GET_META_DATA); mWallpapers = new ArrayList (); new LiveWallpaperEnumerator(context).execute(list); }
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根據"android.intent.action.SET_WALLPAPER"來查找的,然後做一些過濾,添加到列表中。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)); } }
// 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 storage Bitmap 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為例,來說明壁紙的預覽和設置的。
@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\目錄下,自定義了圖片對象,實現了滾動、縮放等功能,這裡就不展開了,可以自己查看代碼 。
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方法來做最後的裁剪和壁紙設置操作。 其他幾種類型的壁紙也是類似的,根據壁紙來源做出相應的操作,比如第三方壁紙時,點擊縮略圖就是打開第三方應用;如果是圖庫,就打開圖庫,總之都是在這幾個重寫方法中實現的。如果以後有什麼不同於目前幾種類型的,也可以依此來擴展。
最近優化項目代碼時,發現一個比較詭異的現象:每當界面進入ActivityA時,cpu在不斷的消耗,內存在不斷的緩慢增長(雖然每次增長的量非常小)。如下圖:最後經過仔細排查
1.首先選中項目右擊→Android Tools→Export Signed Application Package 2.默認
Android 殺死進程:
效果圖: 平時我們要實現ListView數據顯示時的動畫效果,可以使用LayoutAnimationController為ListView設置動畫效果,並通過L