首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Android TextWatcher保存大量类似的更改,以便撤消/重做

Android TextWatcher保存大量类似的更改,以便撤消/重做
EN

Stack Overflow用户
提问于 2013-02-08 17:00:21
回答 1查看 1.2K关注 0票数 1

每次对EditText内容进行一批类似的更改之后,我都会尝试对其进行快照。

例如:

  1. 当我对连续/重叠的文本执行几次连续删除时,这是一个批处理,并对由此产生的EditText内容进行快照。
  2. 当我对连续/重叠的文本执行几次连续添加时,这是一个批处理,并对由此产生的EditText内容进行快照。
  3. 当我对连续/重叠的文本执行几次连续替换(=modifys)时,这是一个批处理,并对由此产生的EditText内容进行快照。

我希望你明白这个想法。

FYI:我需要这个作为撤销/重做机制,因为我不希望/每次文本更改时都可以创建快照。

我怎样才能达到我的目标?

编写失败的代码是因为我(在尝试代码之前)认为toplevel if语句的条件适用于删除文本/添加文本/修改文本。

那么,如何才能改变这种情况,使我有一个很酷的文本编辑器--比如撤销/重做机制后端?

代码语言:javascript
复制
editBigField.addTextChangedListener(new TextWatcher() {

      private static final int NONE = -1;
      private static final int DELETE = 0;
      private static final int ADD = 1;
      private static final int MODIFY = 2;

      private int lastAction = NONE;

      private int delete;

      private int addstart;
      private int addend;

      private int modstart;
      private int modend;

      @Override
      public void afterTextChanged(Editable s) {
      }

      @Override
      public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (after == 0) // delete text
        {
          if (lastAction != DELETE || !(start <= delete && start + count - 1 >= delete)) {
            // save state here TODO
            lastAction = DELETE;
          }
          delete = start;
        }
        else if (count == 0 && after > 0) // add text
        {
          if (lastAction != ADD || !(start >= addstart - 1 && start <= addend)) {
            // save state here TODO
            lastAction = ADD;
          }
          addstart = start;
          addend = start + after;
        }
        else if (count != 0 && after > 0) // modify/replace text
        {
          if (lastAction != MODIFY || !(start + count - 1 >= modstart - 1 && start <= modend)) {
            // save state here TODO
            lastAction = MODIFY;
          }
          modstart = start;
          modend = start + after;
        }

      }

      @Override
      public void onTextChanged(CharSequence s, int start, int before, int count) {
      }

    });
EN

回答 1

Stack Overflow用户

发布于 2013-10-09 12:07:23

也许有人还需要这个。撤消/重做批处理实现:

代码语言:javascript
复制
public class UndoRedoHelper {
    private static final String TAG = UndoRedoHelper.class.getCanonicalName();
    private boolean mIsUndoOrRedo = false;

    private EditHistory mEditHistory;

    private EditTextChangeListener mChangeListener;

    private TextView mTextView;

    // =================================================================== //

    public UndoRedoHelper(TextView textView) {
        mTextView = textView;
        mEditHistory = new EditHistory();
        mChangeListener = new EditTextChangeListener();
        mTextView.addTextChangedListener(mChangeListener);
    }

    // =================================================================== //

    public void disconnect() {
        mTextView.removeTextChangedListener(mChangeListener);
    }

    public void setMaxHistorySize(int maxHistorySize) {
        mEditHistory.setMaxHistorySize(maxHistorySize);
    }

    public void clearHistory() {
        mEditHistory.clear();
    }

    public boolean getCanUndo() {
        return (mEditHistory.mmPosition > 0);
    }

    public void undo() {
        EditItem edit = mEditHistory.getPrevious();
        if (edit == null) {
            return;
        }

        Editable text = mTextView.getEditableText();
        int start = edit.mmStart;
        int end = start + (edit.mmAfter != null ? edit.mmAfter.length() : 0);

        mIsUndoOrRedo = true;
        text.replace(start, end, edit.mmBefore);
        mIsUndoOrRedo = false;

        for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) {
            text.removeSpan(o);
        }

        Selection.setSelection(text, edit.mmBefore == null ? start : (start + edit.mmBefore.length()));
    }

    public boolean getCanRedo() {
        return (mEditHistory.mmPosition < mEditHistory.mmHistory.size());
    }

    public void redo() {
        EditItem edit = mEditHistory.getNext();
        if (edit == null) {
            return;
        }

        Editable text = mTextView.getEditableText();
        int start = edit.mmStart;
        int end = start + (edit.mmBefore != null ? edit.mmBefore.length() : 0);

        mIsUndoOrRedo = true;
        text.replace(start, end, edit.mmAfter);
        mIsUndoOrRedo = false;

        // This will get rid of underlines inserted when editor tries to come
        // up with a suggestion.
        for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) {
            text.removeSpan(o);
        }

        Selection.setSelection(text, edit.mmAfter == null ? start
                : (start + edit.mmAfter.length()));
    }

    public void storePersistentState(Editor editor, String prefix) {
        // Store hash code of text in the editor so that we can check if the
        // editor contents has changed.
        editor.putString(prefix + ".hash",
                String.valueOf(mTextView.getText().toString().hashCode()));
        editor.putInt(prefix + ".maxSize", mEditHistory.mmMaxHistorySize);
        editor.putInt(prefix + ".position", mEditHistory.mmPosition);
        editor.putInt(prefix + ".size", mEditHistory.mmHistory.size());

        int i = 0;
        for (EditItem ei : mEditHistory.mmHistory) {
            String pre = prefix + "." + i;

            editor.putInt(pre + ".start", ei.mmStart);
            editor.putString(pre + ".before", ei.mmBefore.toString());
            editor.putString(pre + ".after", ei.mmAfter.toString());

            i++;
        }
    }

    public boolean restorePersistentState(SharedPreferences sp, String prefix)
            throws IllegalStateException {

        boolean ok = doRestorePersistentState(sp, prefix);
        if (!ok) {
            mEditHistory.clear();
        }

        return ok;
    }

    private boolean doRestorePersistentState(SharedPreferences sp, String prefix) {
        String hash = sp.getString(prefix + ".hash", null);
        if (hash == null) {
            // No state to be restored.
            return true;
        }

        if (Integer.valueOf(hash) != mTextView.getText().toString().hashCode()) {
            return false;
        }

        mEditHistory.clear();
        mEditHistory.mmMaxHistorySize = sp.getInt(prefix + ".maxSize", -1);

        int count = sp.getInt(prefix + ".size", -1);
        if (count == -1) {
            return false;
        }

        for (int i = 0; i < count; i++) {
            String pre = prefix + "." + i;

            int start = sp.getInt(pre + ".start", -1);
            String before = sp.getString(pre + ".before", null);
            String after = sp.getString(pre + ".after", null);

            if (start == -1 || before == null || after == null) {
                return false;
            }
            mEditHistory.add(new EditItem(start, before, after));
        }

        mEditHistory.mmPosition = sp.getInt(prefix + ".position", -1);
        if (mEditHistory.mmPosition == -1) {
            return false;
        }

        return true;
    }

    // =================================================================== //

    private final class EditHistory {
        private int mmPosition = 0;
        private int mmMaxHistorySize = -1;

        private final LinkedList<EditItem> mmHistory = new LinkedList<EditItem>();

        private void clear() {
            mmPosition = 0;
            mmHistory.clear();
        }

        private void add(EditItem item) {
            while (mmHistory.size() > mmPosition) {
                mmHistory.removeLast();
            }
            mmHistory.add(item);
            mmPosition++;

            if (mmMaxHistorySize >= 0) {
                trimHistory();
            }
        }

        private void setMaxHistorySize(int maxHistorySize) {
            mmMaxHistorySize = maxHistorySize;
            if (mmMaxHistorySize >= 0) {
                trimHistory();
            }
        }

        private void trimHistory() {
            while (mmHistory.size() > mmMaxHistorySize) {
                mmHistory.removeFirst();
                mmPosition--;
            }

            if (mmPosition < 0) {
                mmPosition = 0;
            }
        }

        private EditItem getCurrent() {
            if (mmPosition == 0) {
                return null;
            }
            return mmHistory.get(mmPosition - 1);
        }

        private EditItem getPrevious() {
            if (mmPosition == 0) {
                return null;
            }
            mmPosition--;
            return mmHistory.get(mmPosition);
        }

        private EditItem getNext() {
            if (mmPosition >= mmHistory.size()) {
                return null;
            }

            EditItem item = mmHistory.get(mmPosition);
            mmPosition++;
            return item;
        }
    }

    private final class EditItem {
        private int mmStart;
        private CharSequence mmBefore;
        private CharSequence mmAfter;

        public EditItem(int start, CharSequence before, CharSequence after) {
            mmStart = start;
            mmBefore = before;
            mmAfter = after;
        }

        @Override
        public String toString() {
            return "EditItem{" +
                    "mmStart=" + mmStart +
                    ", mmBefore=" + mmBefore +
                    ", mmAfter=" + mmAfter +
                    '}';
        }
    }

    enum ActionType {
        INSERT, DELETE, PASTE, NOT_DEF;
    }

    private final class EditTextChangeListener implements TextWatcher {
        private CharSequence mBeforeChange;
        private CharSequence mAfterChange;
        private ActionType lastActionType = ActionType.NOT_DEF;
        private long lastActionTime = 0;

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            if (mIsUndoOrRedo) {
                return;
            }

            mBeforeChange = s.subSequence(start, start + count);
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (mIsUndoOrRedo) {
                return;
            }

            mAfterChange = s.subSequence(start, start + count);
            makeBatch(start);
        }

        private void makeBatch(int start) {
            ActionType at = getActionType();
            EditItem editItem = mEditHistory.getCurrent();
            if ((lastActionType != at || ActionType.PASTE == at || System.currentTimeMillis() - lastActionTime > 1000) || editItem == null) {
                mEditHistory.add(new EditItem(start, mBeforeChange, mAfterChange));
            } else {
                if (at == ActionType.DELETE) {
                    editItem.mmStart = start;
                    editItem.mmBefore = TextUtils.concat(mBeforeChange, editItem.mmBefore);
                } else {
                    editItem.mmAfter = TextUtils.concat(editItem.mmAfter, mAfterChange);
                }
            }
            lastActionType = at;
            lastActionTime = System.currentTimeMillis();
        }

        private ActionType getActionType() {
            if (!TextUtils.isEmpty(mBeforeChange) && TextUtils.isEmpty(mAfterChange)) {
                return ActionType.DELETE;
            } else if (TextUtils.isEmpty(mBeforeChange) && !TextUtils.isEmpty(mAfterChange)) {
                return ActionType.INSERT;
            } else {
                return ActionType.PASTE;
            }
        }

        public void afterTextChanged(Editable s) {
        }
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14777593

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档