自定义View(1)--圆形图片、圆角图片的实现

之前说过会将项目中运用的东西抽离出来做一个总结,今天我主要想总结一下圆角和圆形头像问题。由于我们的应用涉及到很多用户头像,如果所有的图像都是方方正正的话,那显得不是很美观,所以设计湿强行要我将头像圆角化处理。好吧,so easy。项目截图我就不想贴了,还是一贯贴demo截图吧,如下:

自定义View(1)--圆形图片、圆角图片的实现_第1张图片

看着效果还行啊,下面我们就讲讲怎么去实现它。

一、自定义ImageView显示

1.自定义View属性

自定义view,经常会涉及到view的属性问题,很简单,按以下操作你就能轻松实现自定义属性了。首先你得在资源文件里的values目录下,新建一个文件attrs,声明你想自定义的属性。




    
        
        
        
    


至于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;
		}
	}
使用是不是很简单哈,哈哈,相信大家也都学会了。由于篇幅关系,布局文件就不贴了。

舒口气,自定义圆形、圆角图片就讲到这里了。有问题的可以提出来,大家一起探讨哦。我也是一只边学边记录的菜鸟,欢迎骚扰。

最后感谢一下鸿洋的博客,他写的更好,大伙可以去学学!

Android Xfermode 实战 实现圆形、圆角图片
Android Drawable 那些不为人知的高效用法


你可能感兴趣的:(学习总结)