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

  • 11
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

PorterDuffXfermode とは

PorterDuffXfermodeとはビットマップの合成方法を決めるものです。ベースとなるクラスはXfermodeですので、自分で好みの合成方法を作ることも出来ます。

CanvasにはPathRectFBitmapなどを用いて図形や画像を描画することができます。PathRectFなどは形の情報しかもっていないため、これらをCanvasにかきこむ時にはPaintを渡して、どのような色で描画するかを指定します。XfermodeはこのPaintに対して指定します。PaintXfermodeを持っていると、すでにCanvasに描画されている内容と、新しく描画する内容とを比較して、どのように色を合成するかを決めてくれるようになります。

PorterDuffXfermodeは、PorterさんとDuffさんが決めた合成ルールを実装したクラスで、12種類のモードが用意されています。色が重なる部分について、新しく描画する色を優先するか、既に描画された色を優先するか、混ぜるか、などのモードがあります。

Viewの重ねあわせとPorterDuffXfermode

Viewの描画も突き詰めればCanvasの仕事です。

Viewの重ね合わせは後に追加したViewが上に来るようになっています。何も考えなければ、下に配置されるViewは上に来るViewに隠れて見えなくなりますが、上に来るViewがアルファ値の指定で半透明になっていたり、背景のDrawableが半透明だったりすると、下にあるViewが透けて見えるようになります。

ここで、重ね合わせるViewPorterDuffXfermodeを使った合成ルールをView#onDrawView#dispatchDrawなどで指定すると、半透明なViewを重ね合わせた時の見え方を変えることが出来ます。

例えば、PorterDuff.Mode.CLEARモードを指定したPorderDuffXfermodeを使った次のViewを使うと、その下に配置されるViewを消すことが出来ます。

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を表示したら、その下にくるはずのViewView#setVisibilityで不可視にする方法を取ると、上に重ねるViewが大きく、かつその下にくるViewの数が多くなると、ひたすら下に来るはずのすべてのViewを不可視にしなければならなくなります。また、上に重ねるViewを削除ないし不可視にしたら、下にいたViewを可視状態に戻す必要があります。つまり、可視・不可視をコントロールすべきViewの数だけ面倒臭さが増えます。

半透明なViewの重ね合わせとPorterDuffXfermode

そんなときに先に上げた例のClearMaskViewを使うと、上に重ねるViewの可視・不可視のみを明示的にハンドリングすればよくなります。

例えば、次のような180dp四方の領域に"はろー"と書かれたViewを重ねるcomplex_layout.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が下に描画されるViewPorterDuffXfermodeで消しているので、下にあるはずのいろいろなViewは見えません。

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