Xfermode 指的是你要绘制的内容和 Canvas 的目标位置的内容应该怎样结合计算出最终的颜色。但通俗地说,其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案。
- 怎么用?
首先我们来看看Paint类中的setXfermode()方法:
public Xfermode setXfermode(Xfermode xfermode) {
long xfermodeNative = 0;
if (xfermode != null)
xfermodeNative = xfermode.native_instance;
nSetXfermode(mNativePaint, xfermodeNative);
mXfermode = xfermode;
return xfermode;
}
可见,它的参数是一个Xfermode对象,Xfermode还有一个子类:PorterDuffXfermode,我们一般在用的时候,都是用PorterDuffXfermode
现在我们来创建一个PorterDuffXfermode对象,首先来看看它的构造方法,
PorterDuffXfermode(PorterDuff.Mode mode)
可见,需要传一个PorterDuff.Mode,那这个PorterDuff.Mode又是什么东西呢?我们点进去看:
public class PorterDuff {
// these value must match their native equivalents. See SkXfermode.h
public enum Mode {
/** [0, 0] */
CLEAR (0),
/** [Sa, Sc] */
SRC (1),
/** [Da, Dc] */
DST (2),
/** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
SRC_OVER (3),
/** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
DST_OVER (4),
/** [Sa * Da, Sc * Da] */
SRC_IN (5),
/** [Sa * Da, Sa * Dc] */
DST_IN (6),
/** [Sa * (1 - Da), Sc * (1 - Da)] */
SRC_OUT (7),
/** [Da * (1 - Sa), Dc * (1 - Sa)] */
DST_OUT (8),
/** [Da, Sc * Da + (1 - Sa) * Dc] */
SRC_ATOP (9),
/** [Sa, Sa * Dc + Sc * (1 - Da)] */
DST_ATOP (10),
/** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
XOR (11),
/** [Sa + Da - Sa*Da,
Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
DARKEN (16),
/** [Sa + Da - Sa*Da,
Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
LIGHTEN (17),
/** [Sa * Da, Sc * Dc] */
MULTIPLY (13),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
SCREEN (14),
/** Saturate(S + D) */
ADD (12),
OVERLAY (15);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
/**
* @hide
*/
public final int nativeInt;
}
/**
* @hide
*/
public static final int modeToInt(Mode mode) {
return mode.nativeInt;
}
/**
* @hide
*/
public static final Mode intToMode(int val) {
switch (val) {
default:
case 0: return Mode.CLEAR;
case 1: return Mode.SRC;
case 2: return Mode.DST;
case 3: return Mode.SRC_OVER;
case 4: return Mode.DST_OVER;
case 5: return Mode.SRC_IN;
case 6: return Mode.DST_IN;
case 7: return Mode.SRC_OUT;
case 8: return Mode.DST_OUT;
case 9: return Mode.SRC_ATOP;
case 10: return Mode.DST_ATOP;
case 11: return Mode.XOR;
case 16: return Mode.DARKEN;
case 17: return Mode.LIGHTEN;
case 13: return Mode.MULTIPLY;
case 14: return Mode.SCREEN;
case 12: return Mode.ADD;
case 15: return Mode.OVERLAY;
}
}
}
它其实就是一个枚举类,有18种值,我们在传参的时候,选择其中一个就可以达到相应的效果,Google给我们了一张图,显示的是两个图形一圆一方通过一定的计算产生不同的组合效果,其中圆形是底部的目标图像,方形是上方的源图像。
实例代码:
//禁用硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Bitmap dstBitmap = getDstBitmap(200, 200);
Bitmap srcBitmap = getSrcBitmap(200, 200);
//设置离屏缓冲
int saveLayer = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(dstBitmap, 100, 100, mPaint);
if (porterDuffMode != null) {
mPaint.setXfermode(new PorterDuffXfermode(//混合模式));
}
canvas.drawBitmap(srcBitmap, 100, 100, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(saveLayer);
要注意什么?
1、禁用硬件加速:
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
2、使用离屏缓冲
//设置离屏缓冲
int saveLayer = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
//todo 核心绘制代码
//还原图层
canvas.restoreToCount(saveLayer);
3、两个bitmap的大小要一直,否则混合之后达到的效果可能不是你想要的
demo的全部代码如下:
1.自定义View
public class CustomView extends View {
public static final String TAG="CustomView";
private PorterDuff.Mode porterDuffMode;
private Paint mPaint= new Paint();
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setPorterDuffMode(PorterDuff.Mode porterDuffMode) {
this.porterDuffMode = porterDuffMode;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//禁用硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Bitmap dstBitmap = getDstBitmap(200, 200);
Bitmap srcBitmap = getSrcBitmap(200, 200);
//设置离屏缓冲
int saveLayer = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(dstBitmap,100,100,mPaint);
if (porterDuffMode!=null) {
mPaint.setXfermode(new PorterDuffXfermode(porterDuffMode));
}
canvas.drawBitmap(srcBitmap,100,100,mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(saveLayer);
}
private Bitmap getDstBitmap(int w,int h){
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
canvas.drawCircle(w/2,h/2,w/3,paint);
return bitmap;
}
private Bitmap getSrcBitmap(int w,int h){
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLUE);
canvas.drawRect(new Rect(0,0,w*2/3,h*2/3),paint);
return bitmap;
}
}
2.在布局文件中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zomll.customviewdemo.MainActivity">
<com.zomll.customviewdemo.CustomView
android:id="@+id/cv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<android.support.v7.widget.AppCompatSpinner
android:id="@+id/spiner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
3.引用该布局文件
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatSpinner spiner = (AppCompatSpinner) findViewById(R.id.spiner);
final CustomView cv = (CustomView) findViewById(R.id.cv);
ArrayList<String> datas=new ArrayList<>();
for (int i = 0; i < PorterDuff.Mode.values().length; i++) {
datas.add(PorterDuff.Mode.values()[i].name());
}
spiner.setAdapter(new ArrayAdapter<>(this,android.R.layout.simple_spinner_dropdown_item,datas));
spiner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
cv.setPorterDuffMode(PorterDuff.Mode.values()[position]);
cv.invalidate();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
}
运行效果如下,可以切换不同的模式来查看相应的效果: