1. 程式人生 > >自定義View(1)--圓形圖片、圓角圖片的實現

自定義View(1)--圓形圖片、圓角圖片的實現

之前說過會將專案中運用的東西抽離出來做一個總結,今天我主要想總結一下圓角和圓形頭像問題。由於我們的應用涉及到很多使用者頭像,如果所有的影象都是方方正正的話,那顯得不是很美觀,所以設計溼強行要我將頭像圓角化處理。好吧,so easy。專案截圖我就不想貼了,還是一貫貼demo截圖吧,如下:


看著效果還行啊,下面我們就講講怎麼去實現它。

一、自定義ImageView顯示

1.自定義View屬性

自定義view,經常會涉及到view的屬性問題,很簡單,按以下操作你就能輕鬆實現自定義屬性了。首先你得在資原始檔裡的values目錄下,新建一個檔案attrs,宣告你想自定義的屬性。

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="customParams">
        <attr name="isCircle" format="boolean" />
        <attr name="isRoundCorner" format="boolean" />
        <attr name="corner_radius" format="dimension" />
    </declare-styleable>

</resources>

至於declare-styleable的format,需要了解的同學,可以百度一下,很多文章有介紹哦(點這裡檢視)。其次,你需要在建構函式去關聯這些屬性,才能在畫圖過程中使用到他們。
	public CustomImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context, attrs);
	}

	private void init(Context context, AttributeSet attrs) {
		if (attrs != null) {
			TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.customParams);
			isCircle = a.getBoolean(R.styleable.customParams_isCircle, false);
			isRoundCorner = a.getBoolean(R.styleable.customParams_isRoundCorner, false);
			corner_radius = a.getDimension(R.styleable.customParams_corner_radius, 0);
			// 回收TypedArray,以便後面重用
			a.recycle();
		}
	}
這裡有人會TypedArray為啥要recycle呢,說實話我也納悶了,特地去查了一下,官方的解釋是:回收TypedArray,以便後面重用。在呼叫這個函式後,你就不能再使用這個TypedArray。在TypedArray後呼叫recycle主要是為了快取。當recycle被呼叫後,這就說明這個物件從現在可以被重用了。TypedArray 內部持有部分陣列,它們快取在Resources類中的靜態欄位中,這樣就不用每次使用前都需要分配記憶體。你可以看看TypedArray.recycle()中的程式碼:
/**
 * Give back a previously retrieved StyledAttributes, for later re-use.
 */
public void recycle() {
    synchronized (mResources.mTmpValue) {
        TypedArray cached = mResources.mCachedStyledAttributes;
        if (cached == null || cached.mData.length < mData.length) {
            mXml = null;
            mResources.mCachedStyledAttributes = this;
        }
    }
}
想了解更多的話,可以點選這裡去看看哦

2.圖片繪畫過程onDraw

這是本篇文章的重點,自定義View你必須去重寫onDraw(Canvas canvas),在其中去實現你所需要的邏輯,程式碼如下:

	@Override
	protected void onDraw(Canvas canvas) {
		Drawable drawable = getDrawable();
		if (drawable == null) {
			return;
		}
		if (getWidth() == 0 || getHeight() == 0) {
			return;
		}
		Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
		if (defaultWidth == 0) {
			defaultWidth = getWidth();
		}
		if (defaultHeight == 0) {
			defaultHeight = getHeight();
		}
		if (isCircle || isRoundCorner) {// 圓形或圓角
			int radius = 0;
			radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2;
			Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius);
			canvas.drawBitmap(roundBitmap, defaultWidth / 2 - radius, defaultHeight / 2 - radius, null);
		} else {
			super.onDraw(canvas);
		}
	}
程式碼比較簡單,我就不一一去介紹了,當佈局檔案該view被宣告成了圓角或圓形view時,則重新畫圖。這裡有個最關鍵的方法getCroppedRoundBitmap,需要註釋下。
/**
	 * 獲取裁剪後的圓形圖片
	 * 
	 * @param bmp
	 *            原圖
	 * @param radius
	 *            半徑
	 */
	public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) {
		Bitmap scaledSrcBmp;
		int diameter = radius * 2;
		// 為了防止寬高不相等,造成圓形圖片變形,因此擷取長方形中處於中間位置最大的正方形圖片
		int bmpWidth = bmp.getWidth();
		int bmpHeight = bmp.getHeight();
		int squareWidth = 0, squareHeight = 0;
		int x = 0, y = 0;
		Bitmap squareBitmap;
		if (bmpHeight > bmpWidth) {// 高大於寬
			squareWidth = squareHeight = bmpWidth;
			x = 0;
			y = (bmpHeight - bmpWidth) / 2;
			// 擷取正方形圖片
			squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
		} else if (bmpHeight < bmpWidth) {// 寬大於高
			squareWidth = squareHeight = bmpHeight;
			x = (bmpWidth - bmpHeight) / 2;
			y = 0;
			squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
		} else {
			squareBitmap = bmp;
		}
		if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) {
			scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true);
		} else {
			scaledSrcBmp = squareBitmap;
		}
		Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Config.ARGB_8888);
		Canvas canvas = new Canvas(output);
		Paint paint = new Paint();
		Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight());
		paint.setAntiAlias(true);
		paint.setFilterBitmap(true);
		paint.setDither(true);
		canvas.drawARGB(0, 0, 0, 0);
		if (isRoundCorner) {
			// 畫圓角
			RectF rectF = new RectF(rect);
			float r = corner_radius;
			if (corner_radius == 0)
				r = radius / 4;// 設定圓角預設值
			canvas.drawRoundRect(rectF, r, r, paint);
		} else {
			// 畫圓形圖片
			canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint);
		}
		paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
		canvas.drawBitmap(scaledSrcBmp, rect, rect, paint);
		bmp = null;
		squareBitmap = null;
		scaledSrcBmp = null;
		return output;
	}
仔細讀下來,發現其實程式碼不是很難。主要是運用到了Xfermode的疊加顯示效果,先將獲得的方形圖片在畫布上顯示,在畫布上繼續畫上一個圓形或者圓角的圖片,new PorterDuffXfermode(Mode.SRC_IN)取兩層繪製交集,顯示上層。需要了解更多有關Xfermode的可以點選這裡去學學哦。

ok,自定義view顯示圓形圖片,到這裡就結束了。有問題的提出來,大家一起學習哈!

二、自定義Drawable來顯示圓形、圓角圖片

顯示圓形圖片還有其他方法,在此順便學習了別人的部落格並介紹下如何使用自定義Drawable。

相對於自定義view,使用Drawable 又有哪些優點呢?

1.自定義Drawable,相比View來說,Drawable屬於輕量級的、使用也很簡單

2.自定義drawableU效能更加

那麼怎麼用呢,不說了直接上程式碼

public class CustomDrawable extends Drawable {
	private Paint mPaint;
	private int mWidth;
	private Bitmap mBitmap;
	private RectF rectF;
	private int mType = 0;// 0表示正常,1表示圓形,其他表示圓角
	private float mRadius = 50;

	public CustomDrawable(Bitmap bitmap, int type) {
		mBitmap = bitmap;
		BitmapShader bitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setShader(bitmapShader);
		mWidth = Math.min(mBitmap.getWidth(), mBitmap.getHeight());
		mType = type;
	}

	public CustomDrawable setRadius(float radius) {
		this.mRadius = radius;
		return this;
	}

	@Override
	public void setBounds(int left, int top, int right, int bottom) {
		super.setBounds(left, top, right, bottom);
		rectF = new RectF(left, top, right, bottom);
	}

	@Override
	public void draw(Canvas canvas) {
		if (mType == 1)
			canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2, mPaint);
		else {
			canvas.drawRoundRect(rectF, mRadius, mRadius, mPaint);
		}
	}

	@Override
	public int getIntrinsicWidth() {
		return mWidth;
	}

	@Override
	public int getIntrinsicHeight() {
		return mWidth;
	}

	@Override
	public void setAlpha(int alpha) {
		mPaint.setAlpha(alpha);
	}

	@Override
	public void setColorFilter(ColorFilter cf) {
		mPaint.setColorFilter(cf);
	}

	@Override
	public int getOpacity() {
		return PixelFormat.TRANSLUCENT;
	}
讀下來發現程式碼不難哈,核心的思想和前面提到的自定View差不多,在此就不累贅了。直接看下如何使用它吧。
public class MainActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ImageView iv_circle = (ImageView) findViewById(R.id.iv_circle);
		ImageView iv_roundcorner = (ImageView) findViewById(R.id.iv_roundcorner);
		Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.girl1);
		iv_circle.setImageDrawable(new CustomDrawable(bitmap, 1));
		iv_roundcorner.setImageDrawable(new CustomDrawable(bitmap, 2).setRadius(dip2px(50)));
	}

	/**
	 * Dip轉Px
	 * 
	 * @param context
	 * @param dipValue
	 * @return
	 */
	public int dip2px(float dipValue) {
		try {
			final float scale = getResources().getDisplayMetrics().density;
			return (int) (dipValue * scale + 0.5f);
		} catch (Exception ex) {
			ex.printStackTrace();
			return 0;
		}
	}
使用是不是很簡單哈,哈哈,相信大家也都學會了。由於篇幅關係,佈局檔案就不貼了。

舒口氣,自定義圓形、圓角圖片就講到這裡了。有問題的可以提出來,大家一起探討哦。我也是一隻邊學邊記錄的菜鳥,歡迎騷擾。

最後感謝一下鴻洋的部落格,他寫的更好,大夥可以去學學!