1. 程式人生 > >Android:基於EditText實現撤銷和重做機制

Android:基於EditText實現撤銷和重做機制

一、 場景描述和思路分析

說到撤銷和重做想必大家腦海中浮現的一定是Ctrl+Z、Ctrl+Y這兩個快捷鍵,平常生產開發的時候也少不了要和這兩個按鍵打交道。作為一個開發者筆者自然對其中的實現方法感到好奇,想必閱讀此文的你也是一樣的。

如果你稍微懂點資料結構並且有著基礎的封裝思想的話,大體都能想到一些思路:

將使用者操作抽象成一個介面,介面包含undo()和redo()兩個方法,並用棧來記錄操作的順序,通過出入棧和呼叫兩個方法來處理撤銷和重做的邏輯。

涉及到撤銷和重做的大部分都是需要使用者編輯的功能,如果你想在Android上基於EditText開發出一個文字編輯器的話,那麼按照這個思路一步步實現肯定是沒有問題的。

我們發現文字的編輯操作其實可以簡化為插入、刪除。使用者選中文字後貼上的操作,也就是替換,可以分解為刪除選中文字後插入貼上板內容。

接下來只要記錄下輸入和刪除的操作就可以儲存使用者的操作了,這裡我們可以使用EditText提供了TextWatcher用於監聽文字變化。

接下來請看程式碼實現。

二、 程式碼實現

首先我們需要實現編輯操作類,程式碼如下:

class EditOperation implements Parcelable, Serializable {

    //原始內容,通常是被刪除的部分
    private String src;
    private
int srcStart; private int srcEnd; //目標內容,通常是輸入的部分 private String dst; private int dstStart; private int dstEnd; EditOperation setSrc(CharSequence src, int srcStart, int srcEnd) { this.src = src != null ? src.toString() : ""; this.srcStart = srcStart; this
.srcEnd = srcEnd; return this; } EditOperation setDst(CharSequence dst, int dstStart, int dstEnd) { this.dst = dst != null ? dst.toString() : ""; this.dstStart = dstStart; this.dstEnd = dstEnd; return this; } void undo(EditText text) { Editable editable = text.getText(); int idx = -1; if (dstEnd > 0) {//刪除目標內容 editable.delete(dstStart, dstEnd); if (src == null) { idx = dstStart; } } if (src != null) {//插入原始內容 editable.insert(srcStart, src); idx = srcStart + src.length(); } if (idx >= 0) {//恢復游標位置 text.setSelection(idx); } } void redo(EditText text) { Editable editable = text.getText(); int idx = -1; if (srcEnd > 0) {//刪除原始內容 editable.delete(srcStart, srcEnd); if (dst == null) { idx = srcStart; } } if (dst != null) {//插入目標內容 editable.insert(dstStart, dst); idx = dstStart + dst.length(); } if (idx >= 0) {//恢復游標位置 text.setSelection(idx); } } }

之後我們要在使用者編輯文字的時候生成對應的EditOperation例項,也就是實現TextWatcher

public class OperationManager implements TextWatcher {

    private EditOperation opt;

    //啟用開關,用於過濾撤銷/重做時的編輯操作
    private boolean enable = true;

    OperationManager disable() {
        enable = false;
        return this;
    }

    OperationManager enable() {
        enable = true;
        return this;
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (count > 0) {
            int end = start + count;
            if (enable) {
                if (opt == null) {
                    opt = new EditOperation();
                }
                //記錄原始內容
                opt.setSrc(s.subSequence(start, end), start, end);
            }
        }
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (count > 0) {
            int end = start + count;
            if (enable) {
                if (opt == null) {
                    opt = new EditOperation();
                }
                //記錄目標內容
                opt.setDst(s.subSequence(start, end), start, end);
            }
        }
    }

    @Override
    public void afterTextChanged(Editable s) {
        if (enable && opt != null) {
            if (!redoOpts.isEmpty()) {//重做棧不空時使用者又編輯了文字,視為拋棄重做棧
                redoOpts.clear();
            }
            //將操作入棧
            undoOpts.push(opt);
        }
        opt = null;
    }

    //使用LinkedList代替棧
    private final LinkedList<EditOperation> undoOpts = new LinkedList<>();
    private final LinkedList<EditOperation> redoOpts = new LinkedList<>();

}

之後的撤銷重做就很簡單了:

    public boolean undo() {
        if (canUndo()) {
            EditOperation undoOpt = undoOpts.pop();

            //遮蔽撤銷產生的事件
            disable();
            undoOpt.undo(editText);
            enable();

            //填入重做棧
            redoOpts.push(undoOpt);
            return true;
        }
        return false;
    }

    public boolean redo() {
        if (canRedo()) {
            EditOperation redoOpt = redoOpts.pop();

            //遮蔽重做產生的事件
            disable();
            redoOpt.redo(editText);
            enable();

            //填入撤銷
            undoOpts.push(redoOpt);
            return true;
        }
        return false;
    }

最後實現效果如圖:
撤銷重做