【问题标题】:Android TextWatcher saving batches of similar changes for undo/redoAndroid TextWatcher 为撤消/重做保存批量类似的更改
【发布时间】:2013-01-24 12:22:12
【问题描述】:

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

例如:

  1. 当我对连续/重叠的文本进行多次连续删除时,这是一个批处理,我会对由此产生的 EditText 内容进行快照

  2. 当我对连续/重叠的文本进行多次连续添加时,这是一个批处理,我会对由此产生的 EditText 内容进行快照

  3. 当我对连续/重叠的文本进行多次连续替换(=修改)时,这是一个批处理,我会对由此产生的 EditText 内容进行快照

希望你能明白。

仅供参考:我需要这个用于撤消/重做机制,因为我不希望/可以在每次文本更改时制作快照。

我怎样才能实现我的目标?

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

那么我该如何改变这一点,以便我拥有一个很酷的类似文本编辑器的撤消/重做机制后端?

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) {
      }

    });

【问题讨论】:

    标签: java android android-edittext undo textwatcher


    【解决方案1】:

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

    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) {
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-22
      相关资源
      最近更新 更多