PorterDuffXfermode とは
PorterDuffXfermodeとはビットマップの合成方法を決めるものです。ベースとなるクラスはXfermodeですので、自分で好みの合成方法を作ることも出来ます。
CanvasにはPathやRectF、Bitmapなどを用いて図形や画像を描画することができます。PathやRectFなどは形の情報しかもっていないため、これらをCanvasにかきこむ時にはPaintを渡して、どのような色で描画するかを指定します。XfermodeはこのPaintに対して指定します。PaintがXfermodeを持っていると、すでにCanvasに描画されている内容と、新しく描画する内容とを比較して、どのように色を合成するかを決めてくれるようになります。
PorterDuffXfermodeは、PorterさんとDuffさんが決めた合成ルールを実装したクラスで、12種類のモードが用意されています。色が重なる部分について、新しく描画する色を優先するか、既に描画された色を優先するか、混ぜるか、などのモードがあります。
Viewの重ねあわせとPorterDuffXfermode
Viewの描画も突き詰めればCanvasの仕事です。
Viewの重ね合わせは後に追加したViewが上に来るようになっています。何も考えなければ、下に配置されるViewは上に来るViewに隠れて見えなくなりますが、上に来るViewがアルファ値の指定で半透明になっていたり、背景のDrawableが半透明だったりすると、下にあるViewが透けて見えるようになります。
ここで、重ね合わせるViewでPorterDuffXfermodeを使った合成ルールをView#onDrawやView#dispatchDrawなどで指定すると、半透明なViewを重ね合わせた時の見え方を変えることが出来ます。
例えば、PorterDuff.Mode.CLEARモードを指定したPorderDuffXfermodeを使った次のViewを使うと、その下に配置されるViewを消すことが出来ます。
package hoge.fuga;
public class ClearMaskView extends View {
private Paint mPorterDuffPaint;
private RectF mRect;
// コンストラクタから呼ぶ
private void init() {
mPorterDuffPaint = new Paint();
mPorterDuffPaint.setColor(Color.TRANSPARENT);
mPorterDuffPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mRect == null)
mRect = new RectF(0, 0, getWidth(), getHeight());
canvas.drawRect(mRect, mPorterDuffPaint);
super.dispatchDraw(canvas);
}
}
半透明なViewの重ね合わせとvisibilityプロパティの変更
半透明なViewを上に重ねる時には、下にあるViewを隠したくなることがあります(半透明だと下が透けて見えてしまい目障りに感じるため)。
このとき、上に重ねるViewを表示したら、その下にくるはずのViewをView#setVisibilityで不可視にする方法を取ると、上に重ねるViewが大きく、かつその下にくるViewの数が多くなると、ひたすら下に来るはずのすべてのViewを不可視にしなければならなくなります。また、上に重ねるViewを削除ないし不可視にしたら、下にいたViewを可視状態に戻す必要があります。つまり、可視・不可視をコントロールすべきViewの数だけ面倒臭さが増えます。
半透明なViewの重ね合わせとPorterDuffXfermode
そんなときに先に上げた例のClearMaskViewを使うと、上に重ねるViewの可視・不可視のみを明示的にハンドリングすればよくなります。
例えば、次のような180dp四方の領域に"はろー"と書かれたViewを重ねるcomplex_layout.xmlを考えてみます。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- なんかいろいろたくさん View がある -->
</FrameLayout>
<FrameLayout
android:id="@+id/overlay_views"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center">
<hoge.fuga.ClearMaskView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#33000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="はろー"/>
</FrameLayout>
</FrameLayout>
このレイアウトでは、はろーと書かれた領域は半透明な背景を持っていますが、一旦ClearMaskViewが下に描画されるViewをPorterDuffXfermodeで消しているので、下にあるはずのいろいろなViewは見えません。
これで、いちいち細かく可視・不可視のコントロールをする必要性から開放されました。