マイペースなプログラミング日記

DTMやプログラミングにお熱なd-kamiがマイペースに書くブログ

簡易テキストエディタ作成 その7

今回はスクロールの制御を行う。スクロールの制御は何回かにわけてエントリを書く予定。まずはスクロール位置の制限をかける。スクロール位置を0以下にしないことと、テキストが表示されないところまでいったら、それ以上スクロールさせないようにする。テキストの変更があったらスクロールの限界の位置もかわるため、テキストが変更されたことを通知する必要がある。まだ編集機能は存在しないが、今のうちに通知機能を作っておく。そのためにまずDocumentChangeHandlerというインタフェースを作った。documentChangedとchangeLineがテキストが変更されたときに呼び出されるメソッドとなる。channgeLineは変更が及んだ全ての行番号を引数で受け取る形となっている。

package text.edit.document;

public interface DocumentChangeHandler {
    void documentChanged(int row, int column, String text);
    void changeLine(int[] lines);
}

次に変更するのはTextDocumentクラス。まずDocumantChangeHandlerのリストを持たせた。addDocumentChangeHandlerでハンドラを追加してfire〜で全てのハンドラのそれぞれのメソッドっを呼び出す。今のところsetTextでfire〜が呼び出される

//インスタンス変数
private final List<StringBuilder> textList;
private final List<DocumentChangeHandler> handlers;

//コンストラクタ
public TextDocument(){
    textList = new ArrayList<StringBuilder>();
    textList.add(new StringBuilder());
        
    handlers = new ArrayList<DocumentChangeHandler>();
}

//ハンドラを追加する
public void addDocumentChangeHandler(DocumentChangeHandler handler){
    handlers.add(handler);
}

//全てのハンドラのdocumentChangedを呼び出す
public void fireDocumentChanged(int row, int column, String text){
    for(DocumentChangeHandler handler : handlers){
        handler.documentChanged(row, column, text);
    }
}

//全てのハンドラのchangeLineを呼び出す    
public void fireChangeLine(int[] lines){
    for(DocumentChangeHandler handler : handlers){
        handler.changeLine(lines);
    }
}

public void setText(String text){
    for(char c : text.toCharArray()){
        append(c);
    }
        
    int[] lines = new int[getLineCount()];
        
    for(int i = 0; i < lines.length; i++){
        lines[i] = i;
    }
        
    fireDocumentChanged(0, 0, text);
    fireChangeLine(lines);
}

最後にEditorView。まずは追加したインスタンス変数から。スクロール位置の限界やブレを無視するための定数を追加

//スクロールできる限界の位置
private int maxScrollX;
private int maxScrollY;

//measureText用
private TextPaint paint;

//タッチイベントによるスクロール時のブレを無視するための閾値
private static final int IGNORE_SCROLL = 3;

次にコンストラクタと今回追加したinitScroll。コンストラクタではinitScrollを呼び出す部分を追加。initScrollはスクロールに関わる変数の初期化を行っている

public EditorView(Context context){
    super(context);
        
    fontSize = 16;
    document = new TextDocument();    
    document.addDocumentChangeHandler(this);
        
    initColorMap();
    initViewInfo();
    initRuler();
    initScroll();
    setBackgroundColor(Color.WHITE);
}

private void initScroll(){
    maxScrollX =0;
    maxScrollY = 0;
    paint = new TextPaint();
    paint.setTextSize(fontSize);
}

次にDocumentChangeHandlerの実装。クラスの宣言部分と、インタフェースで宣言されたメソッドの実装部分。documentChangedでmaxScrollYを設定して、changeLineでmaxScrollXを設定している。

public class EditorView extends View implements DocumentChangeHandler{
public void documentChanged(int row, int column, String text){
    int rowCount = document.getLineCount();
    maxScrollY = rowCount * fontSize;
}
    
public void changeLine(int[] lines){
    int start = lines[0];
    int end = lines[lines.length - 1];
        
    for(int row = start; row < end; row++){
        String line = document.getLine(row);
        int lineWidth = (int)paint.measureText(line);

        if(lineWidth > maxScrollX){
            maxScrollX = lineWidth;
        }
    }
}

最後にタッチイベント関係。スクロールのブレ対応や、スクロールの位置が最小値と最大値の間だけで行えるように変更

@Override
public boolean onTouchEvent(MotionEvent event){
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            touchX = (int)event.getX();
            touchY = (int)event.getY();
            break;
                
        case MotionEvent.ACTION_MOVE:
            int currentX = (int)event.getX();
            int currentY = (int)event.getY();

            touchScroll(currentX, currentY);
                
            touchX = currentX;
            touchY = currentY;
            invalidate();
                
            break;
    }
        
    return true;
}
    
private void touchScroll(int currentX, int currentY){
    int scrollX = viewInfo.getInt(ConstName.SCROLL_X);
    int scrollY = viewInfo.getInt(ConstName.SCROLL_Y);

    //タッチイベントが発生した位置の差を求める
    int dx = (currentX - touchX);
    int dy = (currentY - touchY);

    //差の絶対値を求める
    int adx = Math.abs(dx);
    int ady = Math.abs(dy);
        
    //ブレ対応、xとyの差が小さいほうの絶対値が閾値以下ならブレとみなす
    if(adx < ady && adx < EditorView.IGNORE_SCROLL){
        dx = 0;
    }else if(ady < adx && ady < EditorView.IGNORE_SCROLL){
        dy = 0;
    }else if(adx == ady && adx < EditorView.IGNORE_SCROLL){
        //両方向に僅かなスクロールの場合は水平方向をブレとみなす
        dx = 0;
    }
        
    scrollX -= dx;
    scrollY -= dy;
        
    //スクロール位置が0以下にならないようにする
    scrollX = scrollX < 0 ? 0 : scrollX;
    scrollY = scrollY < 0 ? 0 : scrollY;
    //スクロール位置が最大値を超えないようにする
    scrollX = scrollX < maxScrollX ? scrollX : maxScrollX;
    scrollY = scrollY < maxScrollY ? scrollY : maxScrollY;
        
    viewInfo.put(ConstName.SCROLL_X, scrollX);
    viewInfo.put(ConstName.SCROLL_Y, scrollY);
}