LoginSignup
16
15

More than 5 years have passed since last update.

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

Posted at

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は見えません。

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

16
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
15