1. 程式人生 > >一個可以動態繪製任何形狀的自定義ImageView控制元件

一個可以動態繪製任何形狀的自定義ImageView控制元件

         公司專案需求是類似於美甲類app的開發,使用者選擇了不同的甲型影象後,要把介面上存放手指的imageview控制元件變換為使用者選擇好的形狀,這個需求當時感覺挺頭大的,之前確實沒有坐過類似這樣的自定義控制元件,於是Goole搜尋了一番,最後瀏覽了幾乎所有能搜到的相關部落格文章後,找到了下面這一篇文章,看題目似乎正好能滿足我的需求,此處附上部落格連結(https://blog.csdn.net/u013015161/article/details/50993199),感興趣的小夥伴可以學習研究一下。

        部落格裡面講的確實挺好的,整個自定義的過程,步驟都非常清晰。正好部落格裡面有demo可以下載,於是下載了一個進行試用,執行demo後感覺挺好的,正在高興之餘發現問題來了,這個自定義控制元件使用必須將想要遮罩的圖片的drawable以resid的形式靜態放到xml佈局中實現,並不靈活,沒法滿足我這樣的在使用者靈活操作下改變各種形狀的需求,然而這已經是找尋了好久才找到的解決方案,沒有更好的解決方案了,怎麼辦?

        別總想吃現成的了,自己動腦筋自定義吧,於是在原先博主的思路基礎上尋找進行再次開發的思路,博主原來的方法是用自定義view的三個引數的建構函式裡面將自定義形狀圖片的資源id傳遞進去,並在onmeasure方法的時候根據圖片資源id生成bitmap獲取對應的path物件並記錄下來,我要想動態能夠設定這個imageview的遮罩形狀有兩種途徑,一種是圖片的資源id,另一種是直接給一個圖片的bitmap物件過來,都能達到遮罩出給的圖片形狀的效果才行,那就好辦了,我們平時使用imageview的時候都是用Android系統開放給我們的setImageresouse()方法和setImageBitamp()方法實現動態就能在程式碼裡面改變imageview控制元件顯示物件的需求,那我直接重寫imageView的這兩個方法不就行了嗎,這樣就可以動態的改變遮罩影象的資源了,無論id還是bitamp物件都可以,說幹就幹,自定義嘗試後真的成功了,下面附上這兩個方法的程式碼:

@Override
public void setImageResource(@DrawableRes int resId) {
        super.setImageResource(resId);
        if( lastResId!=0){
            if( lastResId == resId){
                isReplayRes = false ;
}else {
                isReplayRes = true ;
}
        }
        BitmapFactory.Options options = new 
BitmapFactory.Options(); options.inJustDecodeBounds = false; shapeLayerBitmap = getBitmapFromDrawable(shapeLayerDrawable); showLayerBitmap = getYsSuoBitmap(resId); resouseBitmap = showLayerBitmap; // showLayerBitmap = BitmapFactory.decodeResource(context.getResources(),resId,options); // resouseBitmap = BitmapFactory.decodeResource(context.getResources(),resId,options); // bitmap = createImage(); lastResId = resId; postInvalidate(); } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); this.showLayerBitmap = bm; isReplayRes =false; this.resouseBitmap = bm; // bitmap = createImage(); }

這樣動態設定遮罩圖片資源的問題解決了,但是緊接著又有一個新的問題接踵而來,發現當遮罩比較大的圖片的時候會出現頂部和底部顯示不全的情況,小的時候不怎麼明顯,可是我們的專案偏偏有一個手指微調的功能,當測試妹子點選微調的時候馬上大叫了起來,這個沒有顯示全怎麼,而且中間有條紋,我看了看確實是有,好吧,又遇上燒腦的問題了,這到底是什麼原因造成的呢,請教了很多高人後,一個經驗比較多的杭州的前端哥們兒跟我說,有可能是因為這個遮罩的方案是通過讀取bitmap物件的path並記錄然後繪製出來的,剛開始的時候對寬高進行了縮放,所以不太精確了,建議我用apth.setStrokeWidth方法將線寬設定的細一些再試試看,於是我這樣嘗試了下,也又些效果,但是還是不能非常圓潤的將指甲圖片的頂部和底部部分都繪製的非常圓,依然是平的,影象中間還是會有些難看的白色線條出現,並沒有根本上解決問題,看著ios哥們兒做好的應用遮罩效果非常好看,再看看自己做的,不禁有些失落,堅持嘗試了好幾天依然沒有能解決問題,老闆時不時過來看做的怎麼樣了,每當看到這裡的時候都提我這個地方不完美,弄的我都有些尷尬了,好吧,拼命也要把它給實現出來,也許就是為了自己一個Android開發者的尊嚴。想法是好的,也就精神拼搏了,但是思路在哪裡?換什麼方案把當前的問題給解決掉,苦思冥想一番後,決定換遮罩繪製過程中的方案,不用讀取和繪製path方案了,在Goole上搜羅了一大圈有了思路感覺,Android是自帶影象遮罩的API的,但是之前沒有嘗試過,這一次也許可以解決這個頭疼的問題,看這裡的一段程式碼:

// 利用PorterDuff.Mode的 SRC_IN 或 DST_IN 取形狀圖層和顯示圖層交集,從而得到自定義形狀的圖片
private Bitmap createImage() {
        if(shapeLayerBitmap==null){
            return null;
}
        if(showLayerBitmap==null){
            return null;
}
        mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
//         shapeLayerBitmap = getBitmapFromDrawable(shapeLayerDrawable);
shapeLayerBitmap = BitmapFactory.decodeResource(context.getResources(),maskResId,options);
//         showLayerBitmap = getBitmapFromDrawable(getDrawable());
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setXfermode(null);
        if (mWidth==0||mHeight==0) {
            return null;
}
        Bitmap result = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
//        Bitmap finalBmp = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
RectF rectf=new RectF(0,0,mWidth,mHeight);
canvas.saveLayer(rectf, null, Canvas.ALL_SAVE_FLAG);
        if (null != showLayerBitmap&&mWidth!=0&&mHeight!=0) {
            showLayerBitmap = getCenterCropBitmap(showLayerBitmap, mWidth, mHeight);
canvas.drawBitmap(showLayerBitmap, 0, 0, paint);
}
        if (null != shapeLayerBitmap&&mWidth!=0&&mHeight!=0) {
//            shapeLayerBitmap = getCenterInsideBitmap(shapeLayerBitmap, mWidth, mHeight);
shapeLayerBitmap = getFillXYBitmap(shapeLayerBitmap, mWidth, mHeight);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(shapeLayerBitmap, 0, 0, paint);
}
        canvas.restore();
        return result;
}

我貼上去的是一個可以直接使用的方法,其實我剛開始用的時候,這個方法總是出問題,除錯了好幾次後才能正常使用,深深感到程式設計師搬磚不易,調bug不易啊!!!這樣一來就實現了完美的遮罩出任何你想要的形狀的自定義的imageview控制元件了,附上幾個效果圖:




本以為一切問題都解決了,可以高枕無憂了,然而bug又來了,由於這個自定義imageView使用的太頻繁了,每一個介面有好幾個手型,每一個手型的每一個指甲位置又放了6到7個這個自定義的imageview控制元件,oom來了,這個頭痛的bug,優化吧,不優化使用著直接崩潰了老闆不把我罵死,於是認真的檢查自定義控制元件中任何一個可以優化釋放記憶體的地方,當然很頭疼的這個,因為稍微該動一個地方,本以為不會有耦合影響的,結果執行起來直接崩潰了,這樣反覆折騰了好多次,我的神經線都快緊張的崩潰了,最終定義了一個壓縮圖片資源的方法,解決了這個問題,因為程式碼裡面動態每一次設定遮罩圖片資源和要進行遮罩的圖片資源,但是並沒有進行壓縮,所以那麼多的地方呼叫,肯定會oom了,果然這個方法很奏效,不再出現oom異常了,而且由於改變了初始圖片資源的大小,使用者操作的時候載入速度明顯提高了,體驗流暢了許多,貼下壓縮圖片的程式碼

public Bitmap getCompressBitmap(int resid) {
    BitmapFactory.Options opts = new BitmapFactory.Options();     //new出物件
opts.inJustDecodeBounds = true;                            //設定為true只返回寬 、高
BitmapFactory.decodeResource(context.getResources(),resid,opts);     //設定出資源
int w,h;
w = opts.outWidth;          //獲得到寬、高
h = opts.outHeight;
    if (w>h){
        opts.inSampleSize = w/200;     //如果是橫圖 就以寬為比例設定inSampleSize
}else {
        opts.inSampleSize = h/200;     //同上咯
}

    opts.inJustDecodeBounds = false;  //記得還是要設定會false這樣才能得到圖,不然返回的還是寬高
Bitmap bm = BitmapFactory.decodeResource(context.getResources(),resid,opts); //這樣就獲取到了影象咯
return bm;
}

          最後在values包的attrs.xml中新增自定義的控制元件屬性,如果values包下沒有這個xml檔案的話右鍵新建xml就行了

<!-- AnyShapeImageView -->
<declare-styleable name="AnyShapeImageView">
    <attr name="anyshapeMask" format="reference"/>
    <attr name="anyshapeBackColor" format="color"/>
</declare-styleable>

         使用起來和一般的imageview控制元件一樣基本,如果想靜態遮罩的話,只需要在佈局裡面加上那個自定義的屬性就行了!

<com.wjj.views.AnyshapeImageView
android:id="@+id/iv_anyshapMask_background_fivefinger_experience1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
app:anyshapeMask="@mipmap/na2"
/>

          如此一來效果確實還可以,看著自己自定義的一個控制元件,心裡美滋滋的,哈哈。

最後附上Github上的下載地址:

https://github.com/15115134369/WJJ-CodeStore    喜歡的朋友記得點亮小星星,謝謝!