【Android】アンドゥ・リドゥ機能の組み込み

投稿者: | 2011年10月15日

前回作成したアンドゥ・リドゥ機能をAndroidのアプリに組み込んでみよう。
簡単なドローアプリを作ってみることにする。
画面は以下のような構成とする。

UndoとRedoの二つのボタンがある。背景全体はカスタムビューとなっていて、タッチ&ムーブで
絵を書くことが出来る。
まずはレイアウトのxmlを示す。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<com.lesson.DrawingView
    android:id="@+id/drawingView"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    />
<Button
    android:id="@+id/btnUndo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="undo"
    />
<Button
    android:id="@+id/btnRedo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_toRightOf="@id/btnUndo"
    android:text="redo"
    />
</RelativeLayout>

そして、メインのActivityは次のようなコードとなる。

MainActivity.java

package com.lesson;

import com.lesson.R;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

public class MainActivity extends Activity {
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final DrawingView view = (DrawingView)findViewById(R.id.drawingView);
        
        findViewById(R.id.btnUndo).setOnClickListener(new OnClickListener() {
        
            @Override
            public void onClick(View v) {
                view.undo();
            }
        });
        
        findViewById(R.id.btnRedo).setOnClickListener(new OnClickListener() {
        
            @Override
            public void onClick(View v) {
                view.redo();
            }
        });
    }
}

最後に、このアプリの要となるカスタムビューのソースを示す。

DrawingView.java

package com.lesson;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class DrawingView extends View {

    private final HistoryStack<ArrayList<PointF>> history = new HistoryStack<ArrayList<PointF>>();
    private ArrayList<PointF> currentStroke;
    
    public DrawingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }
    
    /**
     * アンドゥ
     */
    public void undo(){
        history.undo();
        invalidate();
    }
    
    /**
     * リドゥ
     */
    public void redo(){
        history.redo();
        invalidate();
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if( event.getAction() == MotionEvent.ACTION_DOWN){
            // 新しい描画
            currentStroke = new ArrayList<PointF>();
            return true;
        }
        else if(event.getAction() == MotionEvent.ACTION_MOVE){
            currentStroke.add(new PointF(event.getX(),event.getY()));
            invalidate();
            return true;
        }
        else if(event.getAction()==MotionEvent.ACTION_UP){
            history.add(currentStroke);
            currentStroke = null;
            invalidate();
            
            return true;
        }
        
        return super.onTouchEvent(event);
    }
    
    /**
     * PointFの配列を元に一連の線を描画する
     * @param canvas
     * @param paint
     * @param stroke
     */
    private void drawStroke(Canvas canvas,Paint paint,ArrayList<PointF> stroke){
        PointF startPoint = null;
        for(PointF pf:stroke){              
            if( startPoint != null){
                canvas.drawLine(startPoint.x, startPoint.y, pf.x, pf.y, paint);
            }
            
            startPoint = pf;
        }
    }
    
    private final Paint paint = new Paint();
    
    {
        paint.setColor(Color.CYAN);
        paint.setStrokeWidth(1.f);
    }
    
    
    @Override
    protected void onDraw(Canvas canvas) {
        
        // 履歴に入っている線を描画する
        for(final ArrayList<PointF> stroke:history.iterateUndo()){
            drawStroke(canvas,paint,stroke);
        }
        
        // 現在描画中の線を描画する
        if( currentStroke != null){
            drawStroke(canvas,paint,currentStroke );
        }
    }
}

17行目で宣言している履歴を格納するためのHistoryStackというクラスのソースは前回の記事に示した通りである。
onDrawのタイミングで、履歴にある全てのストロークと描画中のストロークを表示している。
画面のUndoボタンが押されると履歴を一つ戻し、Redoボタンが押されると履歴を一つ進めるようにしている。

1.初期状態
2.アンドゥ
3.アンドゥ
4.アンドゥ
5.書き込み

このようにして、アンドゥ・リドゥ機能を実現することができた。
ただ、このアプリのアンドゥ・リドゥ機能の回数は無制限である。
廉価版アプリなどで回数制限をかけたいときは、
もう少しコーディングを工夫する必要がありそうだ。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です