Android画圆形图片,clippath方式/Xfermode方式
1.利用canvas.clipPath方法,按照自定义的Path图形去切割控件
ImageView显示图片,底层是通过Canvas将我们的图片资源画到View控件上实现的;
因此,要让其显示圆形图片,只需要对Canvas进行相应的变化,比如切割圆形、绘制圆形。
方法1:
描述:定义一个控件,包含ImageView,定义圆形Path路径,然后调用canvas.clipPath,将图片切割成圆形
缺点:锯齿
代码
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsoluteLayout;
public class CircleImage extends AbsoluteLayout { // 也可以继承其他的父类
private int mWidth = 0;
private int mHeight = 0;
private Paint mPaint;
private Path mPath;
public CircleImage(Context context) {
this(context, null);
}
public CircleImage(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public CircleImage(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView();
/*
ViewGroup默认情况下,出于性能考虑,会被设置成WILL_NOT_DROW,这样,ondraw就不会被执行了。
如果需要重写一个ViewGroup的onDraw方法,有两种方法:
1. 构造函数中,给ViewGroup设置一个颜色。
2. 构造函数中,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。
*/
setWillNotDraw(false);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mWidth = getWidth();
mHeight = getHeight();
}
private void initView() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.addCircle(mWidth / 2, mHeight / 2, mWidth / 2, Path.Direction.CCW);
canvas.clipPath(mPath);
}
}
xml中定义
<xxx.CircleImage
android:layout_width="500px"
android:layout_height="500px"
android:layout_x="0px"
android:layout_y="0px"
>
<ImageView
android:layout_x="0px"
android:layout_y="0px"
android:layout_width="500px"
android:layout_height="500px"
android:background="@drawable/xxxpic"
>
</ImageView>
</xxx.CircleImage>
// ImageView中还可以设置src
// android:src="@drawable/xxxpic",但需要设置android:scaleType="centerCrop"
// 不然图片不会跟着窗口大小变化
方法2:
继承AppCompatImageView/ImageView自定义圆形控件
* canvas.clipPath:不支持硬件加速,所以在使用前需要禁止硬件加速
* setLayerType(View.LAYER_TYPE_SOFTWARE, null)
* clipPath要在super.onDraw方法前,调用,否则无效(canvas已经被设置给View了)
* 在onSizeChanged方法中,获取宽高
* 该这方法剪裁的是Canvas图形,View的实际形状是不变的,因此只能对src属性有效,对background属性是无效的
代码:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.util.AttributeSet;
import android.content.res.TypedArray;
import android.support.v7.widget.AppCompatImageView;
import android.graphics.RectF;
public class CircleImage extends AppCompatImageView { // 也可以继承ImageView做
private RectF mRect;
private Path mPath;
private float mRoundRadius;
public CircleImage(Context context) {
this(context, null);
}
public CircleImage(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getAttributes(context, attrs);
initView(context);
}
private void initView(Context context) {
mRect = new RectF();
mPath = new Path();
setLayerType(LAYER_TYPE_SOFTWARE, null); // 禁用硬件加速
}
private void getAttributes(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleImage);
mRoundRadius = ta.getInteger(R.styleable.CircleImage_roundRadius, -1);
ta.recycle();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mRoundRadius < 0) {
clipCircle(w, h);
} else {
clipRoundRect(w, h);
}
}
/**
* 圆角
*/
private void clipRoundRect(int width, int height) {
mRect.left = 0;
mRect.top = 0;
mRect.right = width;
mRect.bottom = height;
mPath.addRoundRect(mRect, mRoundRadius, mRoundRadius, Path.Direction.CW);
}
/**
* 圆形
*/
private void clipCircle(int width, int height) {
int radius = Math.min(width, height)/2;
mPath.addCircle(width/2, height/2, radius, Path.Direction.CW);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.clipPath(mPath);
super.onDraw(canvas);
}
}
attrs.xml
<declare-styleable name="CircleImage">
<attr name="roundRadius" format="integer"/>
</declare-styleable>
画面xml
<CircleImage
android:layout_width="500px"
android:layout_height="500px"
android:layout_x="0px"
android:layout_y="0px"
android:src="@drawable/image_car"
android:scaleType="centerCrop"
app:roundRadius="10"
>
</CircleImage>
解决锯齿的方法
1、通过Paint设置
Paint mPaint = new Paint();
mPaint.setFilterBitmap(true);
2、通过Canvas设置
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG));
不过这种方式不能完全解决圆形图片的锯齿
2.通过Xfermode方式实现
方法1:
自定义控件,包含一个ImageView对象,只对src属性设置的图片有效,background无效
该方法的注意事项就是:控件的大小和子child(ImageView)大小必须一致,可以不是正方形,原因是由于画完圆后,mImageView.setImageBitmap(roundBitmap);也需要设置一个圆盖上去,如果大小不一致,两个圆位置不一样,该问题待解决
代码:
package xxx;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuffXfermode;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.widget.AbsoluteLayout;
import android.widget.ImageView;
public class CircleImage extends AbsoluteLayout {
private int mWidth = 100;
private int mHeight = 100;
private Paint mPaint;
private String TAG = "PKG_UICTRL_CircleImage";
private ImageView mImageView = null;
public CircleImage(Context context) {
this(context, null);
}
public CircleImage(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public CircleImage(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
mPaint.setDither(true);
setWillNotDraw(false);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
Log.d(TAG, "onFinishInflate: ");
getView();
}
private void getView() {
Log.d(TAG, " getView iCount="+getChildCount());
for (int iCount = 0; iCount < getChildCount(); ++iCount) {
View child = this.getChildAt(iCount);
if (child instanceof ImageView) {
Log.d(TAG, " ImageView");
mImageView=(ImageView)child;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
if (null == mImageView){
return;
}
Drawable drawable = mImageView.getDrawable();
if (null != drawable && (drawable instanceof BitmapDrawable)) {
Bitmap b = ((BitmapDrawable) drawable).getBitmap();
Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);
mWidth = getWidth();
mHeight = getHeight();
// 计算显示圆形的半径,为保证圆形,取图片的长宽小的一半作为圆形
int radius = (mWidth < mHeight ? mWidth : mHeight) / 2;
// 获取我们处理后的圆形图片
Bitmap roundBitmap = getCroppedCircleBitmap(bitmap, radius);
// 绘制图片进行显示
canvas.drawBitmap(roundBitmap, mWidth / 2 - radius, mHeight / 2 - radius, null);
mImageView.setImageBitmap(roundBitmap);
}
else {
super.onDraw(canvas);
}
}
public Bitmap getCroppedCircleBitmap(Bitmap bmp, int radius) {
Bitmap scaledSrcBmp;
int diameter = radius * 2;
// 对图片进行处理,获取我们需要的中央部分
Bitmap squareBitmap = getCenterBitmap(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(),
Bitmap.Config.ARGB_8888);
// 在output上进行绘画
Canvas canvas = new Canvas(output);
// 创建裁剪的矩形
Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(),scaledSrcBmp.getHeight());
// 绘制dest目标区域
canvas.drawCircle(scaledSrcBmp.getWidth() / 2,
scaledSrcBmp.getHeight() / 2,
scaledSrcBmp.getWidth() / 2,
mPaint);
// 设置xfermode模式
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 绘制src源区域
canvas.drawBitmap(scaledSrcBmp, rect, rect, mPaint);
bmp.recycle();
squareBitmap.recycle();
scaledSrcBmp.recycle();
return output;
}
// 注意这里必须只能用bitmap.getWidth的图片,去算尺寸,不然外面的框,大于bitmap的大小,超出的就变成了黑色的
private Bitmap getCenterBitmap(Bitmap bitmap){
// 为了防止图片宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片
int bmpWidth = bitmap.getWidth();
int bmpHeight = bitmap.getHeight();
Bitmap squareBitmap;
if (bmpHeight > bmpWidth) {
// 截取正方形图片 ,从(bmpHeight - bmpWidth) / 2处开始截取
squareBitmap = Bitmap.createBitmap(bitmap, 0, (bmpHeight - bmpWidth) / 2, bmpWidth, bmpWidth);
} else if (bmpHeight < bmpWidth) {
squareBitmap = Bitmap.createBitmap(bitmap, (bmpWidth - bmpHeight) / 2, 0, bmpHeight, bmpHeight);
} else {
squareBitmap = bitmap;
}
return squareBitmap;
}
}
画面xml:
<CircleImage
android:layout_width="500px"
android:layout_height="500px"
android:layout_x="0px"
android:layout_y="0px"
>
<ImageView
android:layout_x="0px"
android:layout_y="0px"
android:layout_width="500px"
android:layout_height="500px"
android:src="@drawable/image_car_n"
>
</ImageView>
</CircleImage>
方法2:
自定义控件,继承ImageView,该控件本身就可以获得Drawable(this.getDrawable()),不需要像方法1,拿到mImageView,利用mImageView拿到Drawable(mImageView.getDrawable())
注意:
这两种只适合src设置的图片,原因是src设置的图片getDrawable()才不为空,不是点9图片拿到的是BitmapDrawable可以获取Bitmap图片,((BitmapDrawable) drawable).getBitmap()
点9图片和背景图片想要达到圆形图片,只需在方法1的代码中改onDraw
@Override
protected void onDraw(Canvas canvas) {
if (null == mImageView){
return;
}
mWidth = getWidth();
mHeight = getHeight();
Bitmap bitmap = initBitmap();
if (null != bitmap) {
// Calculate radius of the circle. To ensure the circle, take half of the length or width as radius
int radius = (mWidth < mHeight ? mWidth : mHeight) / 2;
// get round Bitmap
Bitmap roundBitmap = getCroppedCircleBitmap(bitmap, radius);
// Draw Bitmap for display
canvas.drawBitmap(roundBitmap, mWidth / 2 - radius, mHeight / 2 - radius, null);
mImageView.setImageBitmap(roundBitmap);
}
else {
super.onDraw(canvas);
}
}
private Bitmap initBitmap()
{
Bitmap output = null;
Drawable drawable1 = mImageView.getDrawable();
Drawable drawable2 = mImageView.getBackground();
Drawable drawable = drawable1==null ? drawable2 : drawable1; // 不能在构造方法中获取drawable,为null
if (null != drawable && drawable instanceof BitmapDrawable) {
output = ((BitmapDrawable) drawable).getBitmap();
} else if (null != drawable && drawable instanceof NinePatchDrawable) { //处理点9图片,利用canvas
int width = drawable.getIntrinsicWidth(); // image original width
int height = drawable.getIntrinsicHeight(); // image original height
output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
drawable.draw(canvas);
} else if (null != drawable && drawable instanceof ColorDrawable){// 处理background设置的图片,没效果,待解决
output = Bitmap.createBitmap(mWidth,
mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
drawable.setBounds(0, 0, mWidth, mHeight);
drawable.draw(canvas);
}
return output;
}
方法3:
方法1,在ImageView是动态设置的情况下,调用完imageView的onDraw,圆形图片会失效
方法3,通过设置的bitmap画圆,自定义控件中不包含ImageView
代码示例:
package xxx;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuffXfermode;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.widget.AbsoluteLayout;
import android.view.ViewGroup;
public class CircleImage extends AbsoluteLayout { // 也可以继承其他的父类
private String TAG = "TAG_CircleImage";
private int mWidth = 100;
private int mHeight = 100;
private int mRadius = 0;
private Paint mPaint;
private Bitmap mCircleImage;
public CircleImage(Context context) {
this(context, null);
}
public CircleImage(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public CircleImage(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
mPaint.setDither(true);
setWillNotDraw(false);
// 关闭硬件加速
// 参考网站:https://juejin.im/post/6844903893189525512
// 参考网站:https://developer.android.com/guide/topics/graphics/hardware-accel
this.setLayerType(LAYER_TYPE_SOFTWARE, mPaint);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
Log.d(TAG, "onFinishInflate: ");
// getView();
if (null != mCircleImage) {
Log.d(TAG, "onFinishInflate: mCircleImage != null");
setCircleImageBitmap(mCircleImage);
}
}
public void setCircleImageBitmap(Bitmap bmp) {
Log.d(TAG, "setCircleImageBitmap");
mCircleImage = bmp;
/*刷新,会触发调用onDraw*/
invalidate();
}
private Bitmap updateImageBitmap(Bitmap bmp) {
Log.d(TAG, "updateImageBitmap bmp = " + bmp);
ViewGroup.LayoutParams layoutParams = this.getLayoutParams();
if (null != layoutParams && null != bmp) {
mWidth = layoutParams.width;
mHeight = layoutParams.height;
Log.d(TAG, "updateImageBitmap layoutParams != null, mWidth = " + mWidth + ", mHeight = " + mHeight);
// 计算显示圆形的半径,为保证圆形,取图片的长宽小的一半作为圆形
mRadius = (mWidth < mHeight ? mWidth : mHeight) / 2;
Log.d(TAG, "updateImageBitmap mRadius = " + mRadius);
// 获取我们处理后的圆形图片
if (mRadius > 0) {
Log.d(TAG, "updateImageBitmap mRadius > 0");
Bitmap bitmap = getCroppedCircleBitmap(bmp, mRadius);
return bitmap;
}
}
return null;
}
@Override
protected void onDraw(Canvas canvas) {
Log.d(TAG, "onDraw");
if (null != mCircleImage) {
Log.d(TAG, "onDraw, mCircleImage != null");
Bitmap bitmap = updateImageBitmap(mCircleImage);
/*
view.isHardwareAccelerated():
只会受到Application、Activity的影响,如果Activity本身不支持硬件加速,那么返回false,反之返回true
canvas.isHardwareAccelerated():
会受到Application、Activity的影响,如果设置了setLayerType,那么会被setLayerType直接影响
*/
Log.d(TAG, "onDraw, view HardwareAccelerated = " + this.isHardwareAccelerated() + ", Canvas HardwareAccelera = " + canvas.isHardwareAccelerated());
if (null != bitmap) {
Log.d(TAG, "onDraw bitmap != null, bitmap width = " + bitmap.getWidth() + ", height = " + bitmap.getHeight());
// 绘制图片进行显示
canvas.drawBitmap(bitmap, mWidth / 2 - mRadius, mHeight / 2 - mRadius, null);
/* 还原混合模式 */
mPaint.setXfermode(null);
}
}
}
public Bitmap getCroppedCircleBitmap(Bitmap bmp, int radius) {
Bitmap scaledSrcBmp;
int diameter = radius * 2;
// 对图片进行处理,获取我们需要的中央部分
Bitmap squareBitmap = getCenterBitmap(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(),
Bitmap.Config.ARGB_8888);
// 在output上进行绘画
Canvas canvas = new Canvas(output);
// 创建裁剪的矩形
Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(),scaledSrcBmp.getHeight());
// 绘制dest目标区域
canvas.drawCircle(scaledSrcBmp.getWidth() / 2,
scaledSrcBmp.getHeight() / 2,
scaledSrcBmp.getWidth() / 2,
mPaint);
// 设置xfermode模式
// 参考:https://www.jianshu.com/p/d11892bbe055
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 绘制src源区域
canvas.drawBitmap(scaledSrcBmp, rect, rect, mPaint);
/*注意:Bitmap.recycle 方法被弃用
在Android2.3之后,整个Bitmap,包括数据和引用,都放在了堆中,这样,整个Bitmap的回收就全部交给GC了,这个recycle方法就再也不需要使用了。
*/
// bmp.recycle();
// squareBitmap.recycle();
//scaledSrcBmp.recycle();
return output;
}
// 注意这里必须只能用bitmap.getWidth的图片,去算尺寸,不然外面的框,大于bitmap的大小,超出的就变成了黑色的
private Bitmap getCenterBitmap(Bitmap bitmap) {
// 为了防止图片宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片
int bmpWidth = bitmap.getWidth();
int bmpHeight = bitmap.getHeight();
Log.d(TAG, "getCenterBitmap bmpWidth = "+bmpWidth+", bmpHeight = "+bmpHeight);
Bitmap squareBitmap;
if (bmpHeight > bmpWidth) {
// 截取正方形图片 ,从(bmpHeight - bmpWidth) / 2处开始截取
squareBitmap = Bitmap.createBitmap(bitmap, 0, (bmpHeight - bmpWidth) / 2, bmpWidth, bmpWidth);
} else if (bmpHeight < bmpWidth) {
squareBitmap = Bitmap.createBitmap(bitmap, (bmpWidth - bmpHeight) / 2, 0, bmpHeight, bmpHeight);
} else {
squareBitmap = bitmap;
}
return squareBitmap;
}
}
其他方法参考
https://segmentfault.com/a/1190000012253911
https://blog.csdn.net/Mr_dsw/article/details/48629177
根据资源拿到bitmap图片的一个方法为
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.pic);