# 目录
* 字符串记录类 * EditText添加事件响应器 * Button设置 * Button绑定方法 * 过程描述
# 字符串记录类
import java.util.ArrayList; import java.util.List;
public class StringRecorder {
int currentCursorPosition;
List<EditRecord> editRecordUndoList = new ArrayList<>(); List<EditRecord> editRecordRedoList = new ArrayList<>();
public void insertEdit(boolean p_isInsert, String p_newContent, String p_originalContent, int p_startPosition){ editRecordUndoList.add(new EditRecord(p_isInsert,p_newContent,p_originalContent,p_startPosition)); editRecordRedoList.clear(); }
public int currentCursorPosition(){ return currentCursorPosition; }
public void clearRecord(){ editRecordUndoList.clear(); editRecordRedoList.clear(); }
public String undo(String p_currentContent){ String _result; if(!canUndo()){ return p_currentContent; } EditRecord _movingEditRecord = editRecordUndoList.get(editRecordUndoList.size() - 1); currentCursorPosition = _movingEditRecord.editStartPosition; if(_movingEditRecord.isInsert){ _result = p_currentContent.substring(0,_movingEditRecord.editStartPosition); if(_movingEditRecord.originalContent.length() > 0) { _result = _result + _movingEditRecord.originalContent; } _result = _result + p_currentContent.substring(_movingEditRecord.editStartPosition + _movingEditRecord.newContent.length()); } else{ currentCursorPosition = currentCursorPosition + 1; _result = p_currentContent.substring(0,_movingEditRecord.editStartPosition); _result = _result + _movingEditRecord.newContent; _result = _result + p_currentContent.substring(_movingEditRecord.editStartPosition); } editRecordUndoList.remove(editRecordUndoList.size()-1); editRecordRedoList.add(_movingEditRecord); return _result; }
public String redo(String p_currentContent){ String _result; if(!canRedo()){ return p_currentContent; } EditRecord _movingEditRecord = editRecordRedoList.get(editRecordRedoList.size() - 1); currentCursorPosition = _movingEditRecord.editStartPosition; if(_movingEditRecord.isInsert){ _result = p_currentContent.substring(0,_movingEditRecord.editStartPosition); _result = _result + _movingEditRecord.newContent; currentCursorPosition = currentCursorPosition + _movingEditRecord.newContent.length(); if(_movingEditRecord.originalContent.length() > 0) { _result = _result + p_currentContent.substring(_movingEditRecord.editStartPosition + _movingEditRecord.originalContent.length()); } else { _result = _result + p_currentContent.substring(_movingEditRecord.editStartPosition); } } else{ _result = p_currentContent.substring(0,_movingEditRecord.editStartPosition); _result = _result + p_currentContent.substring(_movingEditRecord.editStartPosition+_movingEditRecord.newContent.length()); } editRecordRedoList.remove(editRecordRedoList.size()-1); editRecordUndoList.add(_movingEditRecord); return _result; }
public boolean canUndo(){ return editRecordUndoList.size() > 0; }
public boolean canRedo(){ return editRecordRedoList.size() > 0; }
class EditRecord{
boolean isInsert; String newContent; String originalContent; int editStartPosition;
public EditRecord(boolean p_insert,String p_newContent, String p_originalContent, int p_startPosition){ isInsert = p_insert; newContent = p_newContent; originalContent = p_originalContent; editStartPosition = p_startPosition; }
} }
# EditText添加事件响应器
StringRecorder stringRecorder = new StringRecorder(); boolean activeTextWatcher = true;
private void setWidget(){ editText_content.addTextChangedListener(new TextWatcher() { String _newContent; boolean _isInsert; int _position; String _originalContent; @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { if(!activeTextWatcher){ return; } _position = i; if(i2 < 1){ _isInsert = false; _newContent = charSequence.subSequence(i,i+i1).toString(); } else{ _isInsert = true; if(i1 > 0){ _originalContent = charSequence.subSequence(i,i+i1).toString(); } else{ _originalContent = ""; } } }
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { if(!activeTextWatcher){ return; } if(_isInsert){ _newContent = charSequence.subSequence(i,i+i2).toString(); } stringRecorder.insertEdit(_isInsert,_newContent,_originalContent,_position); if(stringRecorder.canUndo()){ button_undo.setEnabled(true); } if(stringRecorder.canRedo()){ button_redo.setEnabled(true); } else{ button_redo.setEnabled(false); } }
@Override public void afterTextChanged(Editable editable) {
} }); }
# Button设置
<Button android:id="@+id/button_main_undo" android:text="撤销" style="@style/button_default" android:onClick="widget_undo" android:enabled="false" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/button_main_redo" android:text="重做" style="@style/button_default" android:onClick="widget_redo" android:enabled="false" android:layout_width="wrap_content" android:layout_height="wrap_content" />
# Button绑定方法
// 撤销 protected void widget_undo(View p_view){ activeTextWatcher = false; editText_content.setText(stringRecorder.undo(editText_content.getText().toString())); editText_content.setSelection(stringRecorder.currentCursorPosition()); activeTextWatcher = true; if(!stringRecorder.canUndo()){ button_undo.setEnabled(false); } if(stringRecorder.canRedo()){ button_redo.setEnabled(true); } } // 重做 protected void widget_redo(View p_view){ activeTextWatcher = false; editText_content.setText(stringRecorder.redo(editText_content.getText().toString())); editText_content.setSelection(stringRecorder.currentCursorPosition()); activeTextWatcher = true; if(!stringRecorder.canRedo()){ button_redo.setEnabled(false); } if(stringRecorder.canUndo()){ button_undo.setEnabled(true); } }
# 过程描述
想要实现文本的撤销、重做功能,应该记录文本改动的过程。记录每一次改动后的完整文本可能会导致多余的巨大消耗,由于文本改动的步骤可以同步记录,因此只需要记录文本改动的部分。
每次改动文本的行为分为三种:一,增加;二,删除;三,先删除,后增加,名为覆盖。为了适应这三种情况,数据模型中应该同时有增加之记录、删除之记录。怎样判断这三种情况?如果没有删除,则必定是增加;如果有删除,如果有增加,则是覆盖;如果没有增加,则是删除。反过来也可以。因此只需要区分记录的文本是否有内容就可以做出判断。(在本例中采用了额外的判断记录来判断,并且把增加和删除的内容共用一种数据来记录。)
为了在正确的位置撤销或重做改动文本,还应该记录文本中的改动文本的起始位置。
使用一个改动文本的模型的列表来记录提供撤销的改动文本,再使用一个同样的列表来记录提供重做的改动文本。应该检测文本的每次改动,并记录下:增加的文本、删除的文本、改动文本的起点在文本的位置,把这个数据模型添加到撤销列表中,并且激活撤销开关。
撤销时,应该检查最后加入的改动文本模型的增加的文本和删除的文本是否是空的,这样来判断三种情况,然后根据情况来做对应的撤销行为。应该使现在的内容在‘记录的改动起始’位置上分开,如果增加的文本不是空、删除的文本是空,则新的文本应该按顺序装载前部分内容、后部分从增加的文本的长度的位置开始到终末的内容;如果增加的文本不是空、删除的文本也不是空,则新的文本应该按顺序装载前部分内容、删除的文本、后部分从增加的文本的长度开始到终末的内容;如果增加的文本是空,删除的文本应该不是空,则新的文本应该按顺序装载前部分内容、删除的文本、后部分内容;光标应该设置在改动起始位置。为了对应重做功能,应该把这个模型添加到重做模型列表中。然后在撤销列表中删除它。
重做时,判断三种情况的方式与撤销时一样。应该使现在的内容在‘记录的改动起始’位置上分开,如果增加的文本不是空、删除的文本是空,则新的文本应该按顺序装载前部分内容、增加的内容、后部分内容;如果增加的文本不是空、删除的文本也不是空,则新的文本应该按顺序装载前部分内容、增加的文本、后部分从删除的文本长度位置开始到终末的内容;如果增加的文本是空,删除的文本应该不是空,则新的文本应该按顺序装载前部分内容、后部分从删除的文本长度位置开始到终末的内容;如果增加的文本不是空,光标应该设置在增加的内容结尾的位置;如果增加的文本是空,应该设置在改动位置。为了对应撤销功能,应该把这个模型添加到撤销模型列表中。然后在重做列表中删除它。
除了重做时,每次添加模型到撤销模型列表中时,应该清空重做列表,因为如果不清空重做列表,这时重做可能会在新的文本上添加旧的记录,而不是在旧的记录中恢复旧的记录,这样超出了重做的范围。
更新撤销、重做后的文本时,应该避免这次改动触发记录撤销的功能,否则就会记录无效的内容。