search
LoginSignup
3

More than 5 years have passed since last update.

テキストエディタに行数を表示するEditTextカスタム

AdventCalendar Android その2 の12日目の記事です


テキストエディタを作るには

Androidでテキスト入力を実装するにはEditTextを使うことになるかと思います。
テキストエディタを作るにはEditTextをマルチラインにし、場合によってはSpannableStringBuilderを駆使してリッチな機能を盛り込んでいきます。

今回は機能面ではなく、このようにエディタの行数をどうやって表示するかを解説します。
Screenshot_1513043110.png

実現方法

onDrawをoverrideします

CustomEdiText.java
package jp.advent2017.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.v7.widget.AppCompatEditText;
import android.text.Layout;
import android.util.AttributeSet;

/**
 * Created by yuta.ida on 2017/09/01.
 */

public class CustomEdiText extends AppCompatEditText {
    private static final String LF = "\n";

    public CustomEdiText(Context context) {
        super(context);
    }

    public CustomEdiText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        String text = this.getText().toString();
        int displayLineCount = this.getLineCount();
        int lineNo = 1;
        int start = 0;
        Layout layout = this.getLayout();
        for (int i = 0; i < displayLineCount; i++) {

            if (isNewLine(text, start)) {
                drawLineNumber(canvas, i, lineNo);
                lineNo++;
            }

            int end = layout.getLineEnd(i);
            if (hasLineBreak(text, start, end)) {
                String lineString = text.substring(start, end);
                drawLineBreak(canvas, i, lineString);
            }
            start = end;
        }
        super.onDraw(canvas);
    }

    // ①その行の直前に改行が含まれているか
    private boolean isNewLine(String text, int charStart) {
        if (charStart == 0) {
            return true;
        }
        String lineString = text.substring(charStart - 1, charStart);
        return lineString.indexOf(LF) >= 0;
    }

    private void drawLineNumber(Canvas canvas, Integer number, Integer lineNo) {
        Layout layout = this.getLayout();
        Paint textPaint = this.getPaint();

        // ②行のY座標を取得
        int top = this.getPaddingTop() + layout.getLineBottom(number);

        // ③行の先頭にテキストを描画
        canvas.drawText(lineNo.toString(), 0, top, textPaint);
    }

}

詳説

getLineCount

getLineCountはビューの見た目上の行数を取得します。
折り返しによる行数も含まれるので注意が必要です。

行範囲を取得

layout.getLineEnd(layout.getLineStartでも)で行範囲を取得し、直前に改行コードが含まれている場合は新しい行として検知します。

描画する

layout.getLineBottom(lineNumber)で行の位置を取得し、canvas.drawTextを利用し行番号を描画します。

応用

このように行末位置を取得することで改行文字を表示することも可能です。

    @Override
    protected void onDraw(Canvas canvas) {
        ...
        for (int i = 0; i < displayLineCount; i++) {
            ...
            int end = layout.getLineEnd(i);
            if (hasLineBreak(text, start, end)) {
                String lineString = text.substring(start, end);
                drawLineBreak(canvas, i, lineString);
            }
        }
        ...
    }
    private boolean hasLineBreak(String text, int charStart, int charEnd) {
        String lineString = text.substring(charStart, charEnd);
        return lineString.indexOf(LF) >= 0;
    }
    private void drawLineBreak(Canvas canvas, Integer lineNumber, String lineString) {
        Layout layout = this.getLayout();
        Paint textPaint = this.getPaint();
        float measure = textPaint.measureText(lineString, 0, lineString.length());
        measure += this.getPaddingLeft();
        int top = this.getPaddingTop() + layout.getLineBottom(lineNumber);
        canvas.drawText("⏎", measure, top, textPaint);
    }

Screenshot_1513043190.png

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
What you can do with signing up
3