自定義view之圓形ImageView

當初剛開始搞android時都不怎麼注意細節,一些組件隨便放上去加個監聽器什麼的就行了,最多也是把background弄一些顏色罷了。但後來從新回去看一些作品和項目,發現真的醜爆了。一個好的APP我覺得細節上的東西是很重要的,比如組件的圓角化,或者添加selector增加交互感等。在公司裏,一些同事從來不用selector,一些按鈕,按下去毫無交互感,都不知道按中了沒有,給整個APP的用戶體驗感很差。

廢話了那麼多,實際上也就說明了細節對APP的重要性。但這裏並不打算介紹selector,因爲那玩意實在太簡單了,相信用心的人都會用。最近公司裏的美工無論畫什麼,感覺都愛加上圓角,當然包括一些圓形頭像圖片等,所以這裏主要寫關於ImageView的圓角處理。


圓角ImageView原生控件裏並沒有提供,所以需要我們去自定義,自定義的話其實也不難,直接繼承ImageView,在它的基礎上重畫一下就OK了。


1.  因爲是直接繼承ImageView,所以省去自定義View的不少工作,這裏就可以不用聲明定義attrs屬性,也不用初始化變量,只需要把父構造體加上去super就可以了。


2. 重寫onMeasure()(實際上也可以不重寫,因爲繼承了ImageView),但這裏因爲應對圖片的寬高不一致造成圖片沒能完整被圓形罩住的情況還是重寫處理了一下。

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		
		//若沒有圖片則返回
		if(getDrawable()==null){
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
			return;
		}
	
		int widthMode=MeasureSpec.getMode(widthMeasureSpec);
		int width=MeasureSpec.getSize(widthMeasureSpec);
		//若對應wrap_content,則寬度取圖片的寬度,否則取viewgroup傳過來的值
		if(widthMode==MeasureSpec.AT_MOST){
			//取圖片和viewgroupk分配給子view的最大值之中的最小值
			width=Math.min(getDrawable().getIntrinsicWidth(), width);
		}
		
		//同寬度處理
		int heightMode=MeasureSpec.getMode(heightMeasureSpec);
		int height=MeasureSpec.getSize(heightMeasureSpec);
		if(heightMode==MeasureSpec.AT_MOST){
			height=Math.min(getDrawable().getIntrinsicHeight(), height);
		}
		
		//最後取寬度和高度兩者的最小,使view的寬高相等
		setMeasuredDimension(Math.min(width, height), Math.min(width, height));
		
	}

        


左邊爲沒重寫onMeasure的效果,圖片鋪滿了整個屏幕,但圓形截取到中間一部分而已;而右邊重寫了onMeasure,將圖片的尺寸縮放了,所以圓形能截取到整張圖片,但還是建議放寬高相等的圖片,不然拉伸的圖片看起來也是挺彆扭的。


3. 重寫onDraw(), 在canvas之上構建一個圖層,並利用Xfermode規則,在該圖層上先後繪製原始圖片和圓形覆蓋層,獲取其交集部分,最終把圖層內容提交到canvas。

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		
		Drawable drawable=getDrawable();		
		if(drawable==null) return;
		
		//拉伸圖片,使寬高相等,以適應view的大小
		drawable.setBounds(0, 0, getWidth(), getHeight());				
		
		//創建圖層,將圖層壓入棧,canvas的各種後續繪圖操作將在該圖層實現
		int layer=canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
		
		//先將原始圖片畫到圖層
		drawable.draw(canvas);
		
		//創建一個bitmap,並在bitmap上繪製一個圓形,其實不止圓形,你可以在上面畫各種圖形都可以,反正和原始圖片有交集就可以
		Bitmap bitmap=Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);		
		Canvas bitmapCanvas=new Canvas(bitmap); 		
		bitmapCanvas.drawCircle(getWidth()/2, getHeight()/2, getWidth()/2, paint);
		
		//設置xfermode,該模式是取兩次畫圖的交集部分,即圓形部分,且顯示第一次畫圖的內容,即原始圖片
		paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
		
		//將帶有圓形的bitmap覆蓋上去,實際上相當於面具
		canvas.drawBitmap(bitmap, 0, 0, paint);
		
		//將圖層彈出棧,並將圖層的內容提交到最終的canvas上
		canvas.restoreToCount(layer);		
		
	}
當初我沒有在canvas之上創建圖層,而是直接在canvas上利用Xfermode規則畫原始圖和覆蓋圖,得到的結果並不是我想要的,就和上面那兩張圖片一樣,除了圓圈中的內容是我想要的,但圓圈之外卻還有黑色的填充物,可能是圓圈之外是非交集而被剔除了原有的白色背景而導致。

效果圖:


總之,實現該View的核心就是Xfermode這個類,它提供了16種繪圖規則,不清楚的可以百度一下Xfermode, 有許多關於該類介紹的博客,在此就不貼上鍊接了。

最後貼上完整代碼:

public class RoundImageView extends ImageView {

	private Paint paint=new Paint();;
	
	public RoundImageView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public RoundImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	public RoundImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		
		//若沒有圖片則返回
		if(getDrawable()==null){
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
			return;
		}
	
		int widthMode=MeasureSpec.getMode(widthMeasureSpec);
		int width=MeasureSpec.getSize(widthMeasureSpec);
		//若對應wrap_content,則寬度取圖片的寬度,否則取viewgroup傳過來的值
		if(widthMode==MeasureSpec.AT_MOST){
			//取圖片和viewgroupk分配給子view的最大值之中的最小值
			width=Math.min(getDrawable().getIntrinsicWidth(), width);
		}
		
		//同寬度處理
		int heightMode=MeasureSpec.getMode(heightMeasureSpec);
		int height=MeasureSpec.getSize(heightMeasureSpec);
		if(heightMode==MeasureSpec.AT_MOST){
			height=Math.min(getDrawable().getIntrinsicHeight(), height);
		}
		
		//最後取寬度和高度兩者的最小,使view的寬高相等
		setMeasuredDimension(Math.min(width, height), Math.min(width, height));
		
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		
		Drawable drawable=getDrawable();		
		if(drawable==null) return;
		
		//拉伸圖片,使寬高相等,以適應view的大小
		drawable.setBounds(0, 0, getWidth(), getHeight());				
		
		//創建圖層,將圖層壓入棧,canvas的各種繪圖操作將在該圖層實現
		int layer=canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
		
		//先將圖片畫到圖層
		drawable.draw(canvas);
		
		//創建一個bitmap,並在bitmap上繪製一個圓形,其實不止圓形,你可以在上面畫各種圖形都可以,反正和原始圖片有交集就可以
		Bitmap bitmap=Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);		
		Canvas bitmapCanvas=new Canvas(bitmap); 		
		bitmapCanvas.drawCircle(getWidth()/2, getHeight()/2, getWidth()/2, paint);
		
		//設置xfermode,該模式是取兩次畫圖的交集部分,即圓形部分,且顯示第一次畫圖的內容,即原始圖片
		paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
		
		//將帶有圓形的bitmap覆蓋上去,實際上相當於面具
		canvas.drawBitmap(bitmap, 0, 0, paint);
		
		//將圖層彈出棧,並將圖層的內容提交到最終的canvas上
		canvas.restoreToCount(layer);		
		
	}

}

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.samuelzhan.roundimageview.RoundImageView 
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/doge"/>
    
    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_below="@id/iv"
        android:layout_centerHorizontal="true"
        android:src="@drawable/doge"/>

</RelativeLayout>

代碼下載:github


------------------------------------------- 程序員小記-------------------------------------------------------

這是第二篇博客,其實在寫第一篇的時候就想寫點小記了。自從工作後,時間流逝似乎又達到了一個新的高度,不知不覺工作了大半年,四年的大學生涯也即將結束,現在大四下學期也過了三分之一,要是六月底畢業的話,現在也只剩兩個月的時間。過去的半年經歷了許多,錯過了秋招,大四開學就進了一家初創的外包公司,因爲從大二暑假就開始自學android開發,所以公司的項目基本沒什麼的壓力,甚至效率比公司的一些老餅還高,但遺憾的是工資卻是可憐的2K,想想當初也是挺無知的,這工資在深圳南山是個什麼概念?發傳單都比這高!呵呵,實習被宰也只能怪自己太嫩了。不過,當初也是抱着漲經驗的心態工作的,也沒太計較工資,反正公司也就在學校旁邊。所以所以~盡人事,待天命。這也是我工作後的感想吧,只要努力,上天應該不會辜負你太多,即使現實很殘酷。今晚就先寫到這吧,這只是作爲一個程序員憋的一堆心裏話的開頭而已。寫完博客,還要寫畢設論文,好忙~


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章