Android Xfermode

最近需要做类似于QQ裁剪头像的功能:

以前的我,肯定会糊里糊涂的copy网上的资料完事!!这样完全学不到东西,所以这次认真点,学习里面的“精髓”,虽然不难!

好了,这里会接触到Canvas的Xfermode与Layer的东西,在自定义View中可以实现很复杂的效果。

Xfermode

先稍微了解一下关于Xfermode,Xfermode称为图像混合模式,可以将不同的图像进行混合处理。

Xfermode有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,由于AvoidXfermode, PixelXorXfermode都已经被标注为过时了,所以这次主要研究的是仍然在使用的PorterDuffXfermode。

PorterDuffXfermode混合的效果可以有:

效果说明:

  • CLEAR:清除图像
  • 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:滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖

关于Xfermode的了解先到这个。

使用Xfermode

先简单写一个自定义的View,熟悉一下Canvas:

public class Preview extends View {

    private Paint paint;

    public Preview(Context context) {
        this(context, null);
    }

    public Preview(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Preview(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 画透明层
        // 注意:一定要先设置颜色,然后再设置透明度才会起效果
        paint.setColor(Color.BLACK);
        // 透明度 0(完全透明)- 255(完全不透明)
        paint.setAlpha(150);
        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
        // 画圆圈
        int radius = getWidth() / 3;
        paint.setColor(Color.WHITE);
        paint.setAlpha(255);
        paint.setStrokeWidth(3);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius + 3, paint);
    }

}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_orange_light"
        />

    <com.johan.magiceye.Preview
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

效果如图:

OK,我们初步写了一个View,画了一个透明层,然后画了一个圆圈,接下来,我们需要把圆圈的透明层去掉。根据Xfermode的混合效果,我们需要的是SRC_OUT模式,我们修改一下onDraw()方法里面的代码:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 画透明层 0 - 255
    paint.setColor(Color.BLACK);
    paint.setAlpha(150);
    canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
    // 画圆圈
    int radius = getWidth() / 3;
    paint.setColor(Color.WHITE);
    paint.setAlpha(255);
    paint.setStrokeWidth(3);
    paint.setStyle(Paint.Style.STROKE);
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius + 3, paint);
    // 去掉圆圈内的透明层
    paint.setStyle(Paint.Style.FILL);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, paint);
    paint.setXfermode(null);
}

效果如图:

如图,圆圈的透明层是去掉了,可是为什么会变成黑色呢??这里,你可能就开始懵逼了,为什么会这样呢??

这里我们就要来了解一下Canvas的Layer了。

Canvas是支持图层Layer渲染技术的,Canvas默认就有一个图层Layer,一般情况下,我们使用Canvas的drawXXX方法,都是在这层默认的Layer上面进行的。

所以我们使用SRC_OUT混合模式后,效果其实就是从src(源图像:整个透明层)中抠掉dest(目标图像:实心圆),显示黑色是因为默认Layer背景为黑色(猜测)。

既然Canvas支持多图层,那我们就新建一个图层(新建的图层背景为透明),然后再去drawXXX。

在Canvas中,使用saveLayer方法可以新建一个图层,使用restoreToCount方法把新图层的东西绘制到底部图层(这里是默认图层Layer)上。

好了,我们再来改一下onDraw()方法的代码:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 新建图层
        int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        // 画透明层 0 - 255
        paint.setColor(Color.BLACK);
        paint.setAlpha(150);
        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
        // 画圆圈
        int radius = getWidth() / 3;
        paint.setColor(Color.WHITE);
        paint.setAlpha(255);
        paint.setStrokeWidth(3);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius + 3, paint);
        // 去掉圆圈内的透明层
        paint.setStyle(Paint.Style.FILL);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, paint);
        paint.setXfermode(null);
        // 恢复图层
        canvas.restoreToCount(saveCount);
    }

效果如图:

这样就实现了仿QQ头像裁剪的预览图了!

参考

Android Paint Xfermode 学习小结
Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值