一.原理
1.下面的Xfermode子类可以改变这种行为:
AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素XOR操作。
PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
2.Porter-Duff 效果图:
二、实现
1.定义抽象类 AbsRoundImageView (继承ImageView)
private static final PorterDuffXfermode xFermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); private Paint mBitmapPaint; /** * 图片可视区 */ protected Path roundPath; /** * 图片边框 */ protected Path borderPath; /** * 边框宽度 */ protected float borderWidth; /** * 边框颜色 */ protected int borderColor; private Paint borderPaint;
2.自定义属性
<declare-styleable name="AbsRoundImageView"> <attr name="borderWidth" format="dimension|reference"/> <attr name="borderColor" format="color|reference"/> </declare-styleable>
protected void initAttrs(AttributeSet attrs){ if (attrs != null) { TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.AbsRoundImageView); borderWidth = ta.getDimension(R.styleable.AbsRoundImageView_borderWidth, 0); borderColor = ta.getColor(R.styleable.AbsRoundImageView_borderColor, 0); ta.recycle(); } }
3.onDraw,圆形、圆角图片绘制流程都是一样的,绘制图片,绘制边框。
@Override protected void onDraw(Canvas canvas) { drawImage(canvas); drawBorder(canvas); }
- 绘制图片
private void drawImage(Canvas canvas) { Drawable drawable = getDrawable(); if(!isInEditMode() && drawable != null) { try { Bitmap bitmap; if (drawable instanceof ColorDrawable) { bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888); } else { bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); } Canvas drawCanvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawCanvas.getWidth(), drawCanvas.getHeight()); drawable.draw(drawCanvas); Bitmap roundBm = getRoundBitmap(); mBitmapPaint.reset(); mBitmapPaint.setFilterBitmap(false); mBitmapPaint.setXfermode(xFermode); drawCanvas.drawBitmap(roundBm, 0, 0, mBitmapPaint); mBitmapPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0, 0, mBitmapPaint); } catch (Exception e) { e.printStackTrace(); } } }
- 绘制边框
private void drawBorder(Canvas canvas) { borderPaint.setStyle(Paint.Style.STROKE); borderPaint.setColor(borderColor); canvas.drawPath(borderPath, borderPaint); }
4.定义抽象方法
/** * 初始化边框Path */ protected abstract void initBorderPath(); /** * 初始化图片区域Path */ protected abstract void initRoundPath();
三、圆角图片CircleImageView,继承AbsRoundImageView并实现抽象方法
- 通过path的addCircle方法确定圆形区域
@Override protected void initRoundPath() { roundPath.reset(); final int width = getWidth(); final int height = getHeight(); final float cx = width * 0.5f; final float cy = height * 0.5f; final float radius = Math.min(width, height) * 0.5f; roundPath.addCircle(cx, cy, radius, Path.Direction.CW); }
@Override protected void initBorderPath() { borderPath.reset(); final float halfBorderWidth = borderWidth * 0.5f; final int width = getWidth(); final int height = getHeight(); final float cx = width * 0.5f; final float cy = height * 0.5f; final float radius = Math.min(width, height) * 0.5f; borderPath.addCircle(cx, cy, radius - halfBorderWidth, Path.Direction.CW); }
四、圆角图片RoundImageView继承AbsRoundImageView并实现抽象方法
1.自定义属性,因为要控制四个角的弧度,所以要再定义四个属性
<declare-styleable name="RoundImageView"> <attr name="leftTopRadius" format="dimension|reference"/> <attr name="rightTopRadius" format="dimension|reference"/> <attr name="rightBottomRadius" format="dimension|reference"/> <attr name="leftBottomRadius" format="dimension|reference"/> </declare-styleable>
protected void initAttrs(AttributeSet attrs) { super.initAttrs(attrs); if (attrs != null) { TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.RoundImageView); leftTopRadius = ta.getDimension(R.styleable.RoundImageView_leftTopRadius, 0); rightTopRadius = ta.getDimension(R.styleable.RoundImageView_rightTopRadius, 0); rightBottomRadius = ta.getDimension(R.styleable.RoundImageView_rightBottomRadius, 0); leftBottomRadius = ta.getDimension(R.styleable.RoundImageView_leftBottomRadius, 0); ta.recycle(); } }
2.关键是控制四个位置的圆角,path的addRoundRect有不同参数个数的,其中有一个三个参数如下,float数组类型的参数便是控制四个位置弧度的。
/** * Add a closed round-rectangle contour to the path. Each corner receives * two radius values [X, Y]. The corners are ordered top-left, top-right, * bottom-right, bottom-left * * @param rect The bounds of a round-rectangle to add to the path * @param radii Array of 8 values, 4 pairs of [X,Y] radii * @param dir The direction to wind the round-rectangle's contour */ public void addRoundRect(RectF rect, float[] radii, Direction dir) { if (rect == null) { throw new NullPointerException("need rect parameter"); } addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir); }
使用这个方法才可以控制圆角位置,radii 参数是一个含有8个值,4对坐标的数组, 每两个值控制一个方向的值,左上角开始,依次按照顺时针方向
0,1位置 - 左上角
2,3位置 - 右上角
4,5位置 - 右下角
6,7位置 - 左下角
- 所以区域可以这样写
@Override protected void initRoundPath() { roundPath.reset(); final int width = getWidth(); final int height = getHeight(); leftTopRadius = Math.min(leftTopRadius, Math.min(width, height) * 0.5f); rightTopRadius = Math.min(rightTopRadius, Math.min(width, height) * 0.5f); rightBottomRadius = Math.min(rightBottomRadius, Math.min(width, height) * 0.5f); leftBottomRadius = Math.min(leftBottomRadius, Math.min(width, height) * 0.5f); RectF rect = new RectF(0, 0, width, height); roundPath.addRoundRect(rect, new float[]{leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius}, Path.Direction.CW); }
@Override protected void initBorderPath() { borderPath.reset(); /** * 乘以0.5会导致border在圆角处不能包裹图片 */ final float halfBorderWidth = borderWidth * 0.35f; final int width = getWidth(); final int height = getHeight(); leftTopRadius = Math.min(leftTopRadius, Math.min(width, height) * 0.5f); rightTopRadius = Math.min(rightTopRadius, Math.min(width, height) * 0.5f); rightBottomRadius = Math.min(rightBottomRadius, Math.min(width, height) * 0.5f); leftBottomRadius = Math.min(leftBottomRadius, Math.min(width, height) * 0.5f); RectF rect = new RectF(halfBorderWidth, halfBorderWidth, width - halfBorderWidth, height - halfBorderWidth); borderPath.addRoundRect(rect, new float[]{leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius}, Path.Direction.CW); }
五、使用
布局
<com.beiing.roundimage.CircleImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="centerCrop" android:src=http://www.tuicool.com/articles/"@mipmap/head" app:borderWidth="2dp" app:borderColor="#8c9eff" />
<com.beiing.roundimage.RoundImageView android:layout_width="100dp" android:layout_height="100dp" android:scaleType="centerCrop" android:src=http://www.tuicool.com/articles/"@mipmap/test" app:borderWidth="2dp" app:borderColor="#8c9eff" app:leftTopRadius="10dp" app:rightTopRadius="30dp" app:rightBottomRadius="10dp" app:leftBottomRadius="30dp" />
测试常见的图片加载库加载
- Glide
circleImageViewGlide = (CircleImageView) findViewById(R.id.circle_image_glide); Glide.with(this).load("http://img2.imgtn.bdimg.com/it/u=1939271907,257307689&fm=21&gp=0.jpg").into(circleImageViewGlide);
- Picasso
roundImageViewPicasso = (RoundImageView) findViewById(R.id.round_image_picasso); Picasso.with(this).load("http://img0.imgtn.bdimg.com/it/u=2263418180,3668836868&fm=206&gp=0.jpg").fit().into(roundImageViewPicasso);
- xUtils3
roundImageViewXutils = (RoundImageView) findViewById(R.id.round_image_xutils); x.image().bind(roundImageViewXutils, "http://img0.imgtn.bdimg.com/it/u=2263418180,3668836868&fm=206&gp=0.jpg", new ImageOptions.Builder().setCrop(true).build());
扩展
继承AbsRoundImageView,实现抽象方法,绘制自定义形状的图片。
效果图