何かの時にスッと使える力技 - PorterDuffXfermode 編

# PorterDuffXfermode とは

[PorterDuffXfermode](https://developer.android.com/reference/android/graphics/PorterDuffXfermode.html?hl=ja)とはビットマップの合成方法を決めるものです。ベースとなるクラスは[Xfermode](https://developer.android.com/reference/android/graphics/Xfermode.html?hl=ja)ですので、自分で好みの合成方法を作ることも出来ます。

`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`を消すことが出来ます。

```java:ClearMaskView.java
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: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`は見えません。

これで、いちいち細かく可視・不可視のコントロールをする必要性から開放されました。