Merge "Fix several issues with the "EasyEditSpan"."
This commit is contained in:
committed by
Android (Google) Code Review
commit
d486bd2789
@@ -7591,7 +7591,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
|
||||
// Hide the controllers if the amount of content changed
|
||||
if (before != after) {
|
||||
hideControllers();
|
||||
// We do not hide the span controllers, as they can be added when a new text is
|
||||
// inserted into the text view
|
||||
hideCursorControllers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7799,20 +7801,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
* Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
|
||||
* pop-up should be displayed.
|
||||
*/
|
||||
private class EditTextShortcutController {
|
||||
private class EasyEditSpanController {
|
||||
|
||||
private EditTextShortcutPopupWindow mPopupWindow;
|
||||
private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
|
||||
|
||||
private EasyEditSpan mEditTextShortcutSpan;
|
||||
private EasyEditPopupWindow mPopupWindow;
|
||||
|
||||
private EasyEditSpan mEasyEditSpan;
|
||||
|
||||
private Runnable mHidePopup;
|
||||
|
||||
private void hide() {
|
||||
if (mEditTextShortcutSpan != null) {
|
||||
if (mPopupWindow != null) {
|
||||
mPopupWindow.hide();
|
||||
if (mText instanceof Spannable) {
|
||||
((Spannable) mText).removeSpan(mEditTextShortcutSpan);
|
||||
}
|
||||
mEditTextShortcutSpan = null;
|
||||
TextView.this.removeCallbacks(mHidePopup);
|
||||
}
|
||||
removeSpans(mText);
|
||||
mEasyEditSpan = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7822,43 +7827,111 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
* as the notifications are not sent when a spannable (with spans) is inserted.
|
||||
*/
|
||||
public void onTextChange(CharSequence buffer) {
|
||||
if (mEditTextShortcutSpan != null) {
|
||||
hide();
|
||||
adjustSpans(mText);
|
||||
|
||||
if (getWindowVisibility() != View.VISIBLE) {
|
||||
// The window is not visible yet, ignore the text change.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLayout == null) {
|
||||
// The view has not been layout yet, ignore the text change
|
||||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = InputMethodManager.peekInstance();
|
||||
if (!(TextView.this instanceof ExtractEditText)
|
||||
&& imm != null && imm.isFullscreenMode()) {
|
||||
// The input is in extract mode. We do not have to handle the easy edit in the
|
||||
// original TextView, as the ExtractEditText will do
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the current easy edit span, as the text changed, and remove the pop-up
|
||||
// (if any)
|
||||
if (mEasyEditSpan != null) {
|
||||
if (mText instanceof Spannable) {
|
||||
((Spannable) mText).removeSpan(mEasyEditSpan);
|
||||
}
|
||||
mEasyEditSpan = null;
|
||||
}
|
||||
if (mPopupWindow != null && mPopupWindow.isShowing()) {
|
||||
mPopupWindow.hide();
|
||||
}
|
||||
|
||||
// Display the new easy edit span (if any).
|
||||
if (buffer instanceof Spanned) {
|
||||
mEditTextShortcutSpan = getSpan((Spanned) buffer);
|
||||
if (mEditTextShortcutSpan != null) {
|
||||
mEasyEditSpan = getSpan((Spanned) buffer);
|
||||
if (mEasyEditSpan != null) {
|
||||
if (mPopupWindow == null) {
|
||||
mPopupWindow = new EditTextShortcutPopupWindow();
|
||||
mPopupWindow = new EasyEditPopupWindow();
|
||||
mHidePopup = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
mPopupWindow.show(mEditTextShortcutSpan);
|
||||
mPopupWindow.show(mEasyEditSpan);
|
||||
TextView.this.removeCallbacks(mHidePopup);
|
||||
TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the spans by removing all of them except the last one.
|
||||
*/
|
||||
private void adjustSpans(CharSequence buffer) {
|
||||
// This method enforces that only one easy edit span is attached to the text.
|
||||
// A better way to enforce this would be to listen for onSpanAdded, but this method
|
||||
// cannot be used in this scenario as no notification is triggered when a text with
|
||||
// spans is inserted into a text.
|
||||
if (buffer instanceof Spannable) {
|
||||
Spannable spannable = (Spannable) buffer;
|
||||
EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
|
||||
EasyEditSpan.class);
|
||||
for (int i = 0; i < spans.length - 1; i++) {
|
||||
spannable.removeSpan(spans[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the {@link EasyEditSpan} currently attached.
|
||||
*/
|
||||
private void removeSpans(CharSequence buffer) {
|
||||
if (buffer instanceof Spannable) {
|
||||
Spannable spannable = (Spannable) buffer;
|
||||
EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
|
||||
EasyEditSpan.class);
|
||||
for (int i = 0; i < spans.length; i++) {
|
||||
spannable.removeSpan(spans[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EasyEditSpan getSpan(Spanned spanned) {
|
||||
EasyEditSpan[] inputMethodSpans = spanned.getSpans(0, spanned.length(),
|
||||
EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
|
||||
EasyEditSpan.class);
|
||||
|
||||
if (inputMethodSpans.length == 0) {
|
||||
if (easyEditSpans.length == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return inputMethodSpans[0];
|
||||
return easyEditSpans[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
|
||||
* by {@link EditTextShortcutController}.
|
||||
* by {@link EasyEditSpanController}.
|
||||
*/
|
||||
private class EditTextShortcutPopupWindow extends PinnedPopupWindow
|
||||
private class EasyEditPopupWindow extends PinnedPopupWindow
|
||||
implements OnClickListener {
|
||||
private static final int POPUP_TEXT_LAYOUT =
|
||||
com.android.internal.R.layout.text_edit_action_popup_text;
|
||||
private TextView mDeleteTextView;
|
||||
private EasyEditSpan mEditTextShortcutSpan;
|
||||
private EasyEditSpan mEasyEditSpan;
|
||||
|
||||
@Override
|
||||
protected void createPopupWindow() {
|
||||
@@ -7889,8 +7962,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
mContentView.addView(mDeleteTextView);
|
||||
}
|
||||
|
||||
public void show(EasyEditSpan inputMethodSpan) {
|
||||
mEditTextShortcutSpan = inputMethodSpan;
|
||||
public void show(EasyEditSpan easyEditSpan) {
|
||||
mEasyEditSpan = easyEditSpan;
|
||||
super.show();
|
||||
}
|
||||
|
||||
@@ -7903,8 +7976,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
|
||||
private void deleteText() {
|
||||
Editable editable = (Editable) mText;
|
||||
int start = editable.getSpanStart(mEditTextShortcutSpan);
|
||||
int end = editable.getSpanEnd(mEditTextShortcutSpan);
|
||||
int start = editable.getSpanStart(mEasyEditSpan);
|
||||
int end = editable.getSpanEnd(mEasyEditSpan);
|
||||
if (start >= 0 && end >= 0) {
|
||||
editable.delete(start, end);
|
||||
}
|
||||
@@ -7914,7 +7987,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
protected int getTextOffset() {
|
||||
// Place the pop-up at the end of the span
|
||||
Editable editable = (Editable) mText;
|
||||
return editable.getSpanEnd(mEditTextShortcutSpan);
|
||||
return editable.getSpanEnd(mEasyEditSpan);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -7933,10 +8006,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
|
||||
private CharSequence mBeforeText;
|
||||
|
||||
private EditTextShortcutController mEditTextShortcutController;
|
||||
private EasyEditSpanController mEasyEditSpanController;
|
||||
|
||||
private ChangeWatcher() {
|
||||
mEditTextShortcutController = new EditTextShortcutController();
|
||||
mEasyEditSpanController = new EasyEditSpanController();
|
||||
}
|
||||
|
||||
public void beforeTextChanged(CharSequence buffer, int start,
|
||||
@@ -7959,7 +8032,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
+ " before=" + before + " after=" + after + ": " + buffer);
|
||||
TextView.this.handleTextChanged(buffer, start, before, after);
|
||||
|
||||
mEditTextShortcutController.onTextChange(buffer);
|
||||
mEasyEditSpanController.onTextChange(buffer);
|
||||
|
||||
if (AccessibilityManager.getInstance(mContext).isEnabled() &&
|
||||
(isFocused() || isSelected() && isShown())) {
|
||||
@@ -7997,7 +8070,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
|
||||
private void hideControllers() {
|
||||
mEditTextShortcutController.hide();
|
||||
mEasyEditSpanController.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9236,8 +9309,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
|
||||
private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
|
||||
// 3 handles, 2 ActionPopup (suggestionsPopup first hides the others)
|
||||
private final int MAXIMUM_NUMBER_OF_LISTENERS = 5;
|
||||
// 3 handles
|
||||
// 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
|
||||
private final int MAXIMUM_NUMBER_OF_LISTENERS = 6;
|
||||
private TextViewPositionListener[] mPositionListeners =
|
||||
new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
|
||||
private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
|
||||
@@ -11069,14 +11143,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
* Hides the insertion controller and stops text selection mode, hiding the selection controller
|
||||
*/
|
||||
private void hideControllers() {
|
||||
hideInsertionPointCursorController();
|
||||
stopSelectionActionMode();
|
||||
hideCursorControllers();
|
||||
hideSpanControllers();
|
||||
}
|
||||
|
||||
private void hideSpanControllers() {
|
||||
if (mChangeWatcher != null) {
|
||||
mChangeWatcher.hideControllers();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideCursorControllers() {
|
||||
hideInsertionPointCursorController();
|
||||
stopSelectionActionMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character offset closest to the specified absolute position. A typical use case is to
|
||||
* pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
|
||||
|
||||
Reference in New Issue
Block a user