読者です 読者をやめる 読者になる 読者になる

Path#opの闇にハマって抜け出せなかった

Android

View作りに欠かせないPath。
API19から追加されたPath#opを触ってみたことについて。

Path#opで何が出来るか

個人的な感覚ですが、異なる2つのPathを引き算したり、UnionしたりXORしたりすることが出来る便利なやつです。
例えば、
f:id:amyu_dev:20150623151030g:plain
このようにPathですべて描画しているものに対して、Bottle内の"中のものが減る"ということを計算して再度座標を計算し直すというのはかなり骨が折れます。
Path#cubicToなどベジェ曲線を使ってる場合はかなりの労力です。

そこで、Path同士の引き算を行うことによって簡単に実現するわけです。
今回の飲み物が増えたり減ったりするAnimationでは以下のようになっています。

mBottleBeerPath.op(mOpPath, Path.Op.DIFFERENCE);
mGlassBeerPath.op(mOpPath, Path.Op.DIFFERENCE);
canvas.drawPath(mGlassPath, mGlassPaint);
canvas.drawPath(mGlassBeerPath, mBeerPaint);
canvas.drawPath(mBottlePath, mBottlePaint);
canvas.drawPath(mBottleBeerPath, mBeerPaint);

イメージ的には
f:id:amyu_dev:20150623152109p:plain
こんな感じですね。

ハマって抜け出せない闇


コレです。

public class OpSampleView extends View {

    private Path mMainPath;

    private Path mOpPath;

    private Paint mMainPaint;

    public OpSampleView(Context context) {
        this(context, null, 0);
    }

    public OpSampleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OpSampleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setUpPaint();
        setUpPath();
    }

    private void setUpPaint() {
        mMainPaint = new Paint();
        mMainPaint.setColor(Color.BLUE);
        mMainPaint.setStyle(Paint.Style.STROKE);
        mMainPaint.setStrokeWidth(20);
    }

    private void setUpPath() {
        mMainPath = new Path();
        mOpPath = new Path();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mMainPath.reset();
        mMainPath.moveTo(w / 2, h / 2);
        mMainPath.lineTo(w / 2 - 300, h / 2 - 300);
        mMainPath.lineTo(w / 2 + 300, h / 2 - 300);

        mOpPath.addRect(0, 0, w / 2, h, Path.Direction.CCW);

        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //mMainPath.op(mOpPath, Path.Op.DIFFERENCE);
        canvas.drawPath(mMainPath, mMainPaint);
    }
}

f:id:amyu_dev:20150623152551p:plain
このように適当にCloseしてないPathのみで描画した線があります。
当然Closeしていないので三角形は出来上がりません。
別に三角形を作ることを望んでいないので当たり前です。

で、次から問題なことです。

    @Override
    protected void onDraw(Canvas canvas) {
        mMainPath.op(mOpPath, Path.Op.DIFFERENCE);
        canvas.drawPath(mMainPath, mMainPaint);
    }

mMainPath.op(mOpPath, Path.Op.DIFFERENCE);からコメントを外し、左半分を選択しているmOpPathでopすると...
f:id:amyu_dev:20150623152936p:plain
こんな感じに望んでいない形が描画されてしまっているんです...
勝手にCloseされて非常に困ってます。
うーん、コレほんとどうしよう。

これ理由とか解決方法知っている方いたら教えて下さい...

暫定な解決策

線は図形!!!!
がんばって1周させて図形とすればちゃんと描画されます。

最後に

Path#opはかなり便利ですが使えるのがAPI19からというかなり限られています。
そういう微妙に使いづらいところだったり、"op"というググりにくい単語だったりと非常に闇が深くなっています。
ほんと誰か知見ためたら教えて下さい。
コーラ1本上げます。

おわり