Android系統中Launcher案頭表徵圖簡答來講就是通過PackageManager擷取對應APP的Icon即可,所擷取的ICON就是簡單地標準表徵圖,對於Android來說,其標準的ICON為72*72簡單案頭實現可以直接根據自身ICON標準,對該icon進行放大縮小後繪製即可。不過就目前來說,由於Android並未規定表徵圖的標準樣式,所以各家所製作的ICON各式各樣,有的就是簡單表徵圖,有的是在方形背板基礎上繪製ICON。所以各家案頭在自己繪製ICON時,都會對其增加背板,以統一各廠家APP表徵圖,以標準的樣式進行展示。如果背板是標準的,那問題也很好辦,例如背板80*80的方形,表徵圖縮放為72*72,直接繪製在(4,4)的位置即可。而隨著各家案頭的不斷進化,對表徵圖繪製的要求也越來越高,各家允許使用自訂的背板,簡單來說就是背板可以使方的、圓的、甚至是不規則的圖形。這就使得ICON繪製時不能採用預定義配置的方式,需要根據背板的實際樣式對ICON進行裁剪後繪製。簡單來說就是按照背板的樣式裁剪ICON,使ICON在背板中留出一定寬度的邊沿後,在背板正中心位置進行繪製。
初拿到這個需求後,稍稍一考慮,很簡單!從實現角度分析需求就是按照比背板小的邊緣對ICON進行裁剪。分解來說就是:1.如何識別背板的邊緣。2.如何進行裁剪。背板的邊緣很好識別,通過getAlpha擷取背板的Alpha即可。如何裁剪那,有點影像處理知識的就知道,只需要通過簡單地4點採樣,判斷當前點是否需要切掉即可。說幹就幹,第一版的代碼如下:
/**/ /*背板留出5像素邊*/ private static final int EDGE_WIDTH = 5; /*alpha值的最低值,低於該值則認為是透明*/ private static final int ALPHA_BLUR = 200; /** * 返回表徵圖按背板裁剪後得Bitmap * @param background 背板的Bitmap * @param icon 表徵圖的Bitmap * */ public static Bitmap getBitmapWithNoScale(Drawable background, Bitmap icon){ /*首先調整icon大小與背板一致,通過縮放或置中顯示*/ if(icon.getWidth() > background.getIntrinsicWidth()){ icon = Bitmap.createScaledBitmap(icon, background.getIntrinsicWidth(), background.getIntrinsicHeight(), true); } if(icon.getWidth() < background.getIntrinsicWidth() ){ Bitmap tmp = null; try { tmp = Bitmap.createBitmap(background.getIntrinsicWidth(), background.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); } catch (OutOfMemoryError e) { // 如果發生了OOM問題, 重新申請一次 tmp = Bitmap.createBitmap(background.getIntrinsicWidth(), background.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); e.printStackTrace(); } Canvas mCanvas = new Canvas(tmp); mCanvas.drawBitmap(icon,(background.getIntrinsicWidth()-icon.getWidth())/2,(background.getIntrinsicHeight()-icon.getHeight())/2, null); icon = tmp; } return getBitmapAlpha(background, icon); } private static ImageView mGarbage ; /*obj 背板,res 表徵圖*/ private static Bitmap getBitmapAlpha(Drawable backgroundDrawable, Bitmap icon){// Bitmap myAlpha = obj.extractAlpha(); Bitmap background = ((BitmapDrawable)backgroundDrawable).getBitmap(); Bitmap alpha2 = background.extractAlpha(); int back_width = background.getWidth(); int back_height = background.getHeight(); int icon_width = icon.getWidth(); int icon_height = icon.getHeight(); int alpha_arr[] = new int[back_width*back_height]; alpha2 = alpha2.copy(Config.ARGB_8888, true); alpha2.getPixels(alpha_arr, 0, back_width, 0, 0, back_width, back_height); int icon_arr[] = new int[icon_width*icon_height]; icon.getPixels(icon_arr, 0, icon_width, 0, 0, icon_width, icon_height); int back_startx = 0, back_starty = 0, back_endx = 0, back_endy = 0; /*在表徵圖比背板小的情況下,表徵圖在背板中心位置,相對於背板的位置*/ int icon_startx,icon_starty,icon_endx,icon_endy; int icon_offset = 0; if(icon_width < back_width){ /*表徵圖比較小哈,將其置中顯示裁剪*/ Log.e("pluszhang","get icon smaller"); back_startx = EDGE_WIDTH; back_endx = back_width-EDGE_WIDTH-1; back_starty = EDGE_WIDTH; back_endy = back_height-EDGE_WIDTH-1; icon_offset = back_width-icon_width > EDGE_WIDTH*2?0:EDGE_WIDTH-(back_width-icon_width)/2; } else if(icon_width == back_width){ /*直接裁剪即可*/ back_startx = EDGE_WIDTH; back_endx = back_width-EDGE_WIDTH-1; back_starty = EDGE_WIDTH; back_endy = back_height-EDGE_WIDTH-1; } for(int i=0; i<icon_height;i++){ for(int j=0; j<icon_width;j++){ if(i<back_starty||i>back_endy||j<back_startx||j>back_endx){ icon_arr[i*icon_width+j] = 0; } else{ if((alpha_arr[(i-EDGE_WIDTH)*back_width+j] >> 24 &0xff) < ALPHA_BLUR || (alpha_arr[(i+EDGE_WIDTH)*back_width+j] >> 24&0xff)< ALPHA_BLUR || (alpha_arr[(j-EDGE_WIDTH)+i*back_width] >> 24&0xff)< ALPHA_BLUR || (alpha_arr[(j+EDGE_WIDTH)+i*back_width] >> 24&0xff)< ALPHA_BLUR){ icon_arr[i*icon_width+j] = 0; } } } } mGarbage.setBackgroundDrawable(backgroundDrawable); mGarbage.setImageBitmap(Bitmap.createBitmap(icon_arr, icon_width, icon_height, Config.ARGB_8888)); return mGarbage.getDrawingCache(); }
以上這段代碼是採用切的方法實現對表徵圖處理方法,主要來講就是首先進行標準化,將ICON與背板處理到相同大小,只縮不放大,防止ICON變形,通過背板的Alpha視圖,採樣繪製ICON,最終實現對ICON的繪製。通過對上述程式碼分析,由於對表徵圖採用的是掃描方式進行處理,也就是說80X80的表徵圖,要計算6400次,一個表徵圖還好,要是有2,3百個表徵圖,效率確實有些低。關鍵是效果不太好,由於該方法採用的是採樣的方式,對於背板特別不規則的會導致表徵圖邊緣切割有許多毛刺,這個問題是該演算法自身的問題,採用切的演算法,不會有更好的效果,這是該演算法本身決定的,如果要達到最優效果,只有更換演算法換一種思路。
既然用切的方法不好,就只能更換一種思路,通過對Canvas,Paint等繪製類的仔細分析,發現另外一種方法,縮,簡單來說就是把表徵圖先按照背板邊緣繪製,之後對表徵圖進行縮小後,繪製到背板上。利用Canvas,Paint的簡單組合,很容易實現表徵圖按背板邊緣的繪製,這裡及一定要介紹Paint的setXfermode方法,該方法就是根據Canvas背板圖片與繪製圖片的分層及相與、或關係,繪製不同的部分。具體模式對應關係如下:
需要注意的是dst與src的關係,Canvas上原有的Bitmap是dst,而後繪製的Bitmap為src通過不同的Paint可以取得不同的二者剪下、層疊效果。我們簡單實用剪下覆蓋效果就可以,也就是mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
跨過這個坎後,就是最後需要注意的就是,先縮後縮的問題了,就是在使用ICON按背板裁剪後,縮小,會對ICON進行壓縮,導致內部有毛邊,效果不好,採用縮的思路繼續向下走,進一步分析各步驟,發現先對背板進行縮放,後對ICON按照縮小後的背板進行裁剪,合成,這樣既使用的演算法的優勢,又避免了後期進行ICON縮放的弊端。廢話不多說,代碼奉上:
public static void init(Context context) { mCanvas = new Canvas(); mPaint = new Paint(); mPaint.setFilterBitmap(false); mPaint.setAntiAlias(true); mPaint.setDither(true); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); }/**//* 背板留出5像素邊 */private static final int EDGE_WIDTH = 5;/* alpha值的最低值,低於該值則認為是透明 */private static final int ALPHA_BLUR = 200;/** * 返回表徵圖按背板裁剪後得Bitmap * * @param background * 背板的Bitmap * @param icon * 表徵圖的Bitmap * */ public static Bitmap getBitmapWithNoScale(Drawable background, Bitmap icon){ Bitmap background_bit = ((BitmapDrawable)background).getBitmap(); background_bit = background_bit.copy(Bitmap.Config.ARGB_8888,true); Bitmap copy = background_bit.createScaledBitmap(background_bit, background_bit.getWidth()-EDGE_WIDTH*2, background_bit.getHeight()-EDGE_WIDTH*2, true); if(icon.getWidth() < background_bit.getWidth() ){ Bitmap tmp = null; try { tmp = Bitmap.createBitmap(background_bit.getWidth(), background_bit.getHeight(), Bitmap.Config.ARGB_8888); } catch (OutOfMemoryError e) { // 如果發生了OOM問題, 重新申請一次 tmp = Bitmap.createBitmap(background_bit.getWidth(), background_bit.getHeight(), Bitmap.Config.ARGB_8888); e.printStackTrace(); } Canvas mCanvas = new Canvas(tmp); mCanvas.drawBitmap(icon,(background_bit.getWidth()-icon.getWidth())/2,(background_bit.getHeight()-icon.getHeight())/2, null); icon = tmp; } else{ if(icon.getWidth() > background.getIntrinsicWidth()){ icon = Bitmap.createScaledBitmap(icon, background.getIntrinsicWidth(), background.getIntrinsicHeight(), true); } else{ icon = icon.copy(Config.ARGB_8888, true); } } mCanvas.setBitmap(icon); mCanvas.drawBitmap(copy, EDGE_WIDTH, EDGE_WIDTH, mPaint); mCanvas.setBitmap(null); icon = icon.createBitmap(icon, EDGE_WIDTH, EDGE_WIDTH, background_bit.getWidth()-EDGE_WIDTH*2, background_bit.getWidth()-EDGE_WIDTH*2); mCanvas.setBitmap(background_bit); mCanvas.drawBitmap(icon, EDGE_WIDTH,EDGE_WIDTH,null); mCanvas.setBitmap(null); return background_bit; }
——歡迎轉載,請註明出處http://blog.csdn.net/zyplus——