前兩天想實現一個圓角圖片的效果,通過網絡搜索后找到一些答案。這里自己再記錄一下,加深一下自己的認識和知識理解。
實現圓角圖片的思路是自定義一個ImageView,然后通過Ondraw()重繪的功能,將drawable和一個圓形進行重疊繪制,這樣就可以達到圓角的效果了。
下面開始具體實現圓角圖片的過程。
第一步:寫自定義屬性文件
首先我們需要定義一個屬性。在values目錄下面新建一個xml文件,這個文件用來自定義一些屬性,這樣我們就可以寫出自己的控件了。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RoundImageAttrs"> <attr name="BorderRadius" format="dimension"/> <attr name="RoundType"> <enum name="circle" value="0"/> <enum name="round" value="1"/> </attr> </declare-styleable> </resources>
我來簡單解釋一下,declare-styleable這個標簽就是用來自定義屬性的,attr標簽用來定義具體的屬性,format可以指定很多種格式,具體有哪些屬性,這里不做過多介紹了,請看博客中的介紹http://blog.csdn.net/mayingcai1987/article/details/6216655。在本例子中dimension表示尺寸的意思,這個應該在平常的開發中也用到。
同時還有一個enum的枚舉類型,我們之前在用系統控件的時候,比如 android:orientation="vertical"就是比較典型的枚舉類型,只不過在本例中我們自己實現了這個枚舉類型。
定義好這個之后,我們就可以開始寫我們的自定義view的代碼了。最后注意的一點,這里的這個xml的名字可以隨便命名(不過最好命名的比較有意義),android系統會自動找到的。
第二步,自定義View
這一步是本文中最重要的一步,也是實現自定義view的核心。那么我們從目的出發來探討實現圓角image的方式。那么我們的目的是將圖片變圓,在這里一般人可能有想到兩種方式將圖片變圓 1.把本來圖片修改為圓形,其他地方都是透明2.只是讓顯示的時候,動態的裁剪到一些部分,然后讓圖片變圓。
這兩種方法的優劣我想大家一眼就能看明白。直接修改圖片,帶來的后果是,我如果換了一種方式了,不再是圓角,而是星型,那么我們的圖片已經毀掉了,沒辦法在重用了。而第二種方式就可以,無論我們換什么樣的表現方式,我們是需要換一種裁剪的算法,就可以實現不同的形狀的圖片了,那么很顯然,第二種方法是以不變應萬變的。
那么現在,我們選定了第二種方式,那怎么實現呢?有人說我們自己寫一個類,直接繼承自view,然后所有的繪圖和大小的計算我們都自己來搞,這樣行嗎?當然可行,但是我們可能是在重復造輪子,因為我們已經有了一個ImageView,而且它里面給我們做了很多關於圖片的操作,我們何不繼承自ImageView,然后做少量的工作,就可以實現圓角效果呢。方案確定,好,那我們就開始實現自己的類。
public class RoundImageView extends ImageView {
我們定義一個RoundImageView 繼承自ImageView
private int mBorderRadius; private int mType;
然后兩個成員變量,分別對應於自定義屬性文件中的BorderRadius和RoundType。
接下來我們重寫構造函數
public RoundImageView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setAntiAlias(true); TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.RoundImageAttrs); mBorderRadius = a.getDimensionPixelSize(R.styleable.RoundImageAttrs_BorderRadius, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics())); mType = a.getInt(R.styleable.RoundImageAttrs_RoundType,0); a.recycle(); Log.i("Log","mBorderRadius:"+mBorderRadius+"type:"+mType); }
首先是mPaint的初始化,這是一個畫刷,用來畫圖形的,后面會說。
接下來是最為關鍵的代碼, context.obtainStyledAttributes(attrs,R.styleable.RoundImageAttrs),這句代碼用來獲取控件上的自定義屬性。
然后下面兩句
mBorderRadius = a.getDimensionPixelSize(R.styleable.RoundImageAttrs_BorderRadius, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
mType = a.getInt(R.styleable.RoundImageAttrs_RoundType,0);
分別獲取這兩個屬性的值,第一個比較復雜,涉及到默認值單位的轉換,(這里10的代表默認值)
第二個就是獲取RoundImageAttrs_RoundType,獲取完畢后,記得一定要調用a.recycle();對資源進行釋放。以便后面的其他代碼可以訪問這些屬性資源。(理解的不透徹,但記住釋放就ok)
到現在為止我們完成了萬里長征第一步,獲取到了我們自定義控件的屬性了。
接下來就是我們的重頭戲,重繪圖片。下面我們重寫了OnDraw函數
@Override protected void onDraw(Canvas canvas) { //super.onDraw(canvas); Bitmap bitmap = mWeakBitmap == null?null:mWeakBitmap.get(); if(bitmap == null || bitmap.isRecycled()) { Drawable drable = getDrawable(); int width = drable.getIntrinsicWidth(); int height = drable.getIntrinsicHeight(); if(drable!=null) { bitmap = Bitmap.createBitmap(getWidth(),getHeight(),Config.ARGB_8888); Canvas dcanvas = new Canvas(bitmap); drable.draw(dcanvas); if(mMashBitmap == null || mMashBitmap.isRecycled()) { mMashBitmap = getShapeBitmap(); } mPaint.reset(); mPaint.setFilterBitmap(false); mPaint.setXfermode(mXfermode); dcanvas.drawBitmap(mMashBitmap, 0,0, mPaint); mPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0,0, null); mWeakBitmap = new WeakReference<Bitmap>(bitmap); } } else { mPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0,0, null); return; } }
上面的代碼可能第一次看比較迷惑,各種paint還有canvas,drawable,各種區分不清。下面我結合代碼都說說。
Bitmap bitmap = mWeakBitmap == null?null:mWeakBitmap.get();這句話是從一個弱引用中取得Bitmap圖像,我們在成功創建圓形圖像后,會保存起來,以供后面刷新使用。
接下來我們判斷bitmap,如果為空說明還沒有創建過。接下來我們通過getDrawable();獲取當前ImageView的drawable,里面包含了原本的圖像。
下面我們創建了一個臨時的Bitmap對象,這個對象將保存經過我們處理之后的圖像
bitmap = Bitmap.createBitmap(getWidth(),getHeight(),Config.ARGB_8888);
然后我們創建一個Canvas dcanvas = new Canvas(bitmap); drable.draw(dcanvas); 通過drawable的draw方法將原來ImageView的圖像繪制到dcanvas上(其實也是畫到bitmap上)。
接下來我們獲取圖形(這里是圓形,后面大家可以自己定義形狀)mMashBitmap = getShapeBitmap();(這個函數我們后面介紹),然后我們設置了paint的屬性
private Xfermode mXfermode = new PorterDuffXfermode(Mode.DST_IN);
mPaint.setXfermode(mXfermode);
這個mXfermode代表的意思是,當用paint畫圖時,新繪制的圖像與原圖像的關系。給大家一張圖,就很容易理解各種繪制方式了。
,在本例子中用的就是DST_IN,想必這張圖一看就明白。paint配置完后,我們就開始將新的形狀繪制到原來的圖像上
dcanvas.drawBitmap(mMashBitmap, 0,0, mPaint); 此時,bitmap中保存的就是疊加之后的圖片了,也就是我們最終需要的圓角圖片了。最后我們將這個bitmap繪制到OnDraw函數給我們傳遞進來的
canvas上,所有工作就基本做完了。canvas.drawBitmap(bitmap, 0,0, null);
最后將繪制好的圖片保存起來。mWeakBitmap = new WeakReference<Bitmap>(bitmap);
下一次執行ondraw 的時候,我們就直接用保存好的bitmap進行繪制了,也就是我們代碼中else的部分。
最艱難的部分說完了,哈哈,如果不理解還是得多看幾遍。接下來的工作就輕松了很多,對了,我們還沒有實現之前那個繪制形狀的函數呢。我們來繪制把。很容易的。
private Bitmap getShapeBitmap() { Bitmap bit = Bitmap.createBitmap(getWidth(),getHeight(),Config.ARGB_8888); Canvas can = new Canvas(bit); Paint pa = new Paint(Paint.ANTI_ALIAS_FLAG); pa.setColor(Color.BLACK); if(mType == 0) { can.drawCircle(getWidth()/2, getHeight()/2, mBorderRadius, pa); } return bit; }
看看上面的代碼,是不是很熟悉,我們之前已經接觸過基本的畫圖方法了。想必,不用解釋了,一眼都能看明白。這里我只實現了畫圓的,大家可以各自發揮想想,畫出各種各樣的形狀,哈哈,是不是很容易,我們自己實現了圓角圖像,同時對於android自定義view的繪制也有了大致了解。
對了,這里面還有一個問題,如果用戶想動態修改圖片怎么辦,我們在內存里面保存了一個舊的圖片,該怎么更新呢。其實好辦,我們只需要做下面的操作就行
public void invalidate() { mWeakBitmap = null; if (mMashBitmap != null) { mMashBitmap.recycle(); mMashBitmap = null; } super.invalidate(); }
這個函數是當view視圖重繪的時候執行的,於是乎,當我們更換圖片后,讓view重繪,就可以將之前保留的舊的圖片信息清空啦。
真不容易啊,終於寫完了,竟然寫了三個小時,看來以后自己還是得多多些博客了,不過通過寫文章, 更加加深了認識,值得。
最后如果轉載,別忘了注明源地址噢,謝謝各位看官。
轉載請注明出處http://www.cnblogs.com/gaoteng/p/4222207.html www.gaotenglife.com