android_基础_圆形imageView的实现

121 篇文章 1 订阅

四种自定义圆形ImageView的方法


  1. BitmapShader: 使用着色器
  2. Xfermode:使用图层叠加
  3. ClipPath:通过对画布裁剪的方式
  4. RoundedBitmapDrawable: 系统API圆角类

前面三种是通过继承ImageView重写onDraw()方法实现
最后一种是系统API直接使用。


每一种方式都能实现显示圆形图片, 我们主要从以下几个方面来比较各个方式的不同

  • 实现方式难易
  • 空白的背景
  • 抗锯齿的能力

第1种方式:BitmapShader实现


通过对Paint(画笔)设置着色器(Shader)来实现,这里的Shader是指BitmapShader,实际上有很多的Shader,根据需要进行使用。 在这里插入图片描述

也就是说,当BitmapShader设置在画笔上的时候,画笔画出来的内容就是一张bitmap图,既然画的内容是Bitmap,那么画的形状如果是圆形,这就是一个显示圆形的ImageView了,当然也可以画圆角,画正方形。

	//进行画笔初始化
	private void init() {
	    mPaint = new Paint();
	    mPaint.setAntiAlias(true);//给画笔设置抗锯齿
	    mMatrix = new Matrix();
	    //该方法千万别放到onDraw()方法里面调用,否则会不停的重绘的,因为该方法调用了invalidate() 方法
	    setLayerType(View.LAYER_TYPE_SOFTWARE, null);//禁用硬加速
	}
	
	//重写onDraw()方法获取BitmapDrawable进行处理
	@Override
	protected void onDraw(Canvas canvas) {
	    Drawable drawable = getDrawable();
	    if (drawable instanceof BitmapDrawable) {
	        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
	        Bitmap bitmap = bitmapDrawable.getBitmap();
	        //通过BitmapShader的方式实现
	        drawRoundByShaderMode(canvas, bitmap);
	    } else {
	        super.onDraw(canvas);
	    }
	}
	
	private void drawRoundByShaderMode(Canvas canvas, Bitmap bitmap) {
	    //获取到Bitmap的宽高
	    int bitmapWidth = bitmap.getWidth();
	    int bitmapHeight = bitmap.getHeight();
	    
	    //获取到ImageView的宽高
	    int viewWidth = getWidth();
	    int viewHeight = getHeight();
	    
	    //根据Bitmap生成一个BitmapShader,这里有个TileMode设置有三个参数可以选择:
	    //CLAMP, MIRROR, REPEAT 在后面会细说。
	    BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
	    mMatrix.reset();
	    
	    float minScale = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
	    mMatrix.setScale(minScale, minScale);//将Bitmap保持比列根据ImageView的大小进行缩放
	    bitmapShader.setLocalMatrix(mMatrix); //将矩阵变化设置到BitmapShader,其实就是作用到Bitmap
	    mPaint.setShader(bitmapShader);
	
	    //绘制圆形
	    canvas.drawCircle(bitmapWidth / 2, bitmapHeight / 2, Math.min(bitmapWidth / 2, bitmapHeight / 2), mPaint);
	}
	//RoundImageView在XML中的布局
	<com.jabo.douban.demo.test.RoundImageViewTest
	    android:layout_width="90dp"
	    android:layout_height="90dp"
	    android:src="@drawable/test_view"
	    app:layout_constraintBottom_toTopOf="@+id/guideline6"
	    app:layout_constraintEnd_toStartOf="@+id/guideline5"
	    app:layout_constraintStart_toStartOf="parent"
	    app:layout_constraintTop_toTopOf="parent" />
	
	//以上就是全部关键代码,即可实现一个简单的圆形ImageView


原图是这样的:

在这里插入图片描述

效果图是这样的:
在这里插入图片描述


上面第33行中BitmapTileMode,其实就是说如果Bitmap的内容没有铺满View时,如何填充形状里面剩余的部分,我们来看一下

  • ClAMPBitmap以其内容的最后一行像素填充剩余的高的空白或者最后一列像素填充剩余宽空白
  • MIRRORBitmap以其内容以镜像的方式填充剩余空白
  • REPEATBitmap以其内容以重复的方式填充剩余空白

在这里插入图片描述


现在来看一下抗锯齿的能力,上面的图都是设置了抗锯齿的,如果把尺寸设置小一点:
在这里插入图片描述

表现还不错,为了进行明显的对比,我们去掉抗锯齿,对比下

// 注释掉抗锯齿 // mPaint.setAntiAlias(true);//给画笔设置抗锯齿

在这里插入图片描述

// 加上抗锯齿 mPaint.setAntiAlias(true);//给画笔设置抗锯齿

在这里插入图片描述

这就很明显了,表现的比较平滑,说明设置抗锯齿是有用的

所以关于BitmapShader的实现方式的比较

  • 实现方式难易 -> 容易,代码简单
  • 空白的背景 -> 不能实现 抗锯齿的能力 ->
  • 可以设置,并且有明显效果

第2种方式:Xfermode实现


原理其实就是通过叠加显示的方式,设置不同的Xformode导致叠加的策略不同,最终显示不同,来一张图
在这里插入图片描述


PorterDuff.Mode

CLEAR清除图像(源覆盖的目标像素被清除为0)
SRC只显示源图像(源像素替换目标像素)
DST只显示目标图像(源像素被丢弃,使目标保持完整)
SRC_OVER将源图像放在目标图像上方
DST_OVER 将目标图像放在源图像上方(源像素是在目标像素后面绘制的。)
SRC_IN 只在源图像和目标图像相交的地方绘制【源图像】(保持源像素覆盖目标像素,丢弃剩余的源和目标像素)
DST_IN 只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
SRC_OUT 只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
DST_OUT 只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤(保持目标像素不被源像素所覆盖。丢弃由源像素覆盖的目标像素。丢弃所有源像素。)
SRC_ATOP 在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响
DST_ATOP 在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
XOR 在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制
DARKEN 变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
LIGHTEN 变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
MULTIPLY 正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
SCREEN 滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖(添加源和目标像素,然后减去源像素乘以目标。)
ADD 饱和相加,对图像饱和度进行相加,不常用
OVERLAY 叠加


现在来看代码

    private void drawRoundByXfermode(Canvas canvas, Bitmap bitmap) {
    
        int bitmapWidth = bitmap.getWidth();
        int bitmapHeight = bitmap.getHeight();
    
        int viewWidth = getWidth();
        int viewHeight = getHeight();
    
        float minScale = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
        mMatrix.reset();
        mMatrix.setScale(minScale, minScale);
    
        //经过矩阵变换后的新的bitmap
        Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, mMatrix, true);
    
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        canvas.drawCircle(viewWidth / 2, viewWidth / 2, Math.min(viewHeight / 2, viewHeight / 2), mPaint);
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    
        canvas.drawBitmap(newBitmap, (viewWidth - newBitmap.getWidth()) / 2, (viewHeight - newBitmap.getHeight()) / 2, mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }

效果图


在这里插入图片描述

很明显XferMode的方式能够留出空白,并且通过这种叠加的方式,能够实现任何形状的显示,扩展性可以说是极好的。
现在来看一下锯齿,

这是注释掉XforMode锯齿之后的效果

在这里插入图片描述

还是能够看到一些锯齿的印记

下面是Xformode加上去锯齿的效果

在这里插入图片描述

可以看到效果非常的平滑
所以关于Xfermode的实现方式的比较

  • 实现方式难易 -> 容易,代码简单
  • 空白的背景 -> 可以实现,并且可以实现背景颜色
  • 抗锯齿的能力 -> 可以设置,并且有明显效果,设置后效果平滑

第3种方式:Canvas.clipPath()实现


通过裁剪画布的方式实现,先将画布裁剪成圆形,在绘制bitmap

    private void drawRoundByClipPath(Canvas canvas, Bitmap bitmap) {
        int bitmapWidth = bitmap.getWidth();
        int bitmapHeight = bitmap.getHeight();
    
        int viewWidth = getWidth();
        int viewHeight = getHeight();
    
        float minScale = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
        mMatrix.reset();
        mMatrix.setScale(minScale, minScale);
    
        //经过矩阵变换后的新的bitmap
        Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, mMatrix, true);
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        //Path.Direction.CCW 这个参数表示path的方向,书顺时针还是逆时针,这里是逆时针
        mPath.addCircle(viewWidth / 2, viewWidth / 2, Math.min(viewHeight / 2, viewHeight / 2), Path.Direction.CCW);
    
        mPaint.reset();
        mPaint.setAntiAlias(true);//对画笔设置抗锯齿
        canvas.setDrawFilter(pdf);//对画布设置抗锯齿
        canvas.clipPath(mPath);
         //将Bitmap绘制到中心区域
        canvas.drawBitmap(newBitmap, (viewWidth - newBitmap.getWidth()) / 2, (viewHeight - newBitmap.getHeight()) / 2, mPaint);
    
        canvas.restoreToCount(layerId);
    }

效果展示

在这里插入图片描述


通过这种方式也能实现圆形裁剪,我们可以在背后设置draw一个circle实现白色背景,但是这不重要。
重要的是我们代码中设置了抗锯齿,但是还是有比较明显锯齿。
也就是说,代码中设置了抗锯齿和不设置都有明显的锯齿。

所以关于Xfermode的实现方式的比较

  • 实现方式难易 -> 容易,代码简单
  • 空白的背景 -> 可以实现,并且可以实现背景颜色
  • 抗锯齿的能力 -> 无法去除锯齿

第4种方式:RoundedBitmapDrawable实现(系统API)


系统为我们提供了一个类来实现圆角功能,这就不需要我们通过继承ImageView并且重新ondraw()来实现了。

    private void initRoundedDrawable() {
        ImageView view = findViewById(R.id.ivRoundedView);
        Bitmap src = BitmapFactory.decodeResource(getResources(), R.drawable.test_view); //获取Bitmap图片
        RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), src); //创建RoundedBitmapDrawable对象
        roundedBitmapDrawable.setCornerRadius(500); //设置圆角Radius(根据实际需求)
        roundedBitmapDrawable.setAntiAlias(true); //设置抗锯齿
        view.setImageDrawable(roundedBitmapDrawable); //显示圆角
    }

我们只需要将上面的代码在Activity中设置即可

在这里插入图片描述

这种方式的话,实际上使用的是BitmapShader的方式实现的,我们可以看一下他设置圆角的核心代码

    public void setCornerRadius(float cornerRadius) {
        if (mCornerRadius == cornerRadius) return;
    
        mIsCircular = false;
        if (isGreaterThanZero(cornerRadius)) {
            mPaint.setShader(mBitmapShader); //这里设置了BitmapShader
        } else {
            mPaint.setShader(null);
        }
    
        mCornerRadius = cornerRadius;
        invalidateSelf();
    }

所以锯齿能力我们就不再分析了,参照BitmapShader方式。
所以关于RoundedBitmapDrawable的实现方式的比较

  • 实现方式难易 -> 最容易
  • 空白的背景 -> 不可以实现
  • 抗锯齿的能力 -> 可以设置,并且有明显效果,设置后效果平滑

最后总结一下,其实每种方式都能实现圆形,但是需要我们根据自身的需求去选择。

BitmapShaderXfermodeclipPathRoundedBitmapDrawable
实现方式难易简单简单简单最简单
空白的背景不能设置可以设置可以设置不能设置
抗锯齿可以设置,有效可以设置,有效不可去除可以设置,有效
推荐指数推荐推荐不推荐推荐

如果觉得有用,点个赞吧!!!

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值