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
|
// Hide the controllers if the amount of content changed
|
||||||
if (before != after) {
|
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
|
* Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
|
||||||
* pop-up should be displayed.
|
* 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() {
|
private void hide() {
|
||||||
if (mEditTextShortcutSpan != null) {
|
if (mPopupWindow != null) {
|
||||||
mPopupWindow.hide();
|
mPopupWindow.hide();
|
||||||
if (mText instanceof Spannable) {
|
TextView.this.removeCallbacks(mHidePopup);
|
||||||
((Spannable) mText).removeSpan(mEditTextShortcutSpan);
|
|
||||||
}
|
|
||||||
mEditTextShortcutSpan = null;
|
|
||||||
}
|
}
|
||||||
|
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.
|
* as the notifications are not sent when a spannable (with spans) is inserted.
|
||||||
*/
|
*/
|
||||||
public void onTextChange(CharSequence buffer) {
|
public void onTextChange(CharSequence buffer) {
|
||||||
if (mEditTextShortcutSpan != null) {
|
adjustSpans(mText);
|
||||||
hide();
|
|
||||||
|
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) {
|
if (buffer instanceof Spanned) {
|
||||||
mEditTextShortcutSpan = getSpan((Spanned) buffer);
|
mEasyEditSpan = getSpan((Spanned) buffer);
|
||||||
if (mEditTextShortcutSpan != null) {
|
if (mEasyEditSpan != null) {
|
||||||
if (mPopupWindow == 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) {
|
private EasyEditSpan getSpan(Spanned spanned) {
|
||||||
EasyEditSpan[] inputMethodSpans = spanned.getSpans(0, spanned.length(),
|
EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
|
||||||
EasyEditSpan.class);
|
EasyEditSpan.class);
|
||||||
|
if (easyEditSpans.length == 0) {
|
||||||
if (inputMethodSpans.length == 0) {
|
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return inputMethodSpans[0];
|
return easyEditSpans[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
|
* 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 {
|
implements OnClickListener {
|
||||||
private static final int POPUP_TEXT_LAYOUT =
|
private static final int POPUP_TEXT_LAYOUT =
|
||||||
com.android.internal.R.layout.text_edit_action_popup_text;
|
com.android.internal.R.layout.text_edit_action_popup_text;
|
||||||
private TextView mDeleteTextView;
|
private TextView mDeleteTextView;
|
||||||
private EasyEditSpan mEditTextShortcutSpan;
|
private EasyEditSpan mEasyEditSpan;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void createPopupWindow() {
|
protected void createPopupWindow() {
|
||||||
@@ -7889,8 +7962,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
mContentView.addView(mDeleteTextView);
|
mContentView.addView(mDeleteTextView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void show(EasyEditSpan inputMethodSpan) {
|
public void show(EasyEditSpan easyEditSpan) {
|
||||||
mEditTextShortcutSpan = inputMethodSpan;
|
mEasyEditSpan = easyEditSpan;
|
||||||
super.show();
|
super.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7903,8 +7976,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
|
|
||||||
private void deleteText() {
|
private void deleteText() {
|
||||||
Editable editable = (Editable) mText;
|
Editable editable = (Editable) mText;
|
||||||
int start = editable.getSpanStart(mEditTextShortcutSpan);
|
int start = editable.getSpanStart(mEasyEditSpan);
|
||||||
int end = editable.getSpanEnd(mEditTextShortcutSpan);
|
int end = editable.getSpanEnd(mEasyEditSpan);
|
||||||
if (start >= 0 && end >= 0) {
|
if (start >= 0 && end >= 0) {
|
||||||
editable.delete(start, end);
|
editable.delete(start, end);
|
||||||
}
|
}
|
||||||
@@ -7914,7 +7987,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
protected int getTextOffset() {
|
protected int getTextOffset() {
|
||||||
// Place the pop-up at the end of the span
|
// Place the pop-up at the end of the span
|
||||||
Editable editable = (Editable) mText;
|
Editable editable = (Editable) mText;
|
||||||
return editable.getSpanEnd(mEditTextShortcutSpan);
|
return editable.getSpanEnd(mEasyEditSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -7933,10 +8006,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
|
|
||||||
private CharSequence mBeforeText;
|
private CharSequence mBeforeText;
|
||||||
|
|
||||||
private EditTextShortcutController mEditTextShortcutController;
|
private EasyEditSpanController mEasyEditSpanController;
|
||||||
|
|
||||||
private ChangeWatcher() {
|
private ChangeWatcher() {
|
||||||
mEditTextShortcutController = new EditTextShortcutController();
|
mEasyEditSpanController = new EasyEditSpanController();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void beforeTextChanged(CharSequence buffer, int start,
|
public void beforeTextChanged(CharSequence buffer, int start,
|
||||||
@@ -7959,7 +8032,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
+ " before=" + before + " after=" + after + ": " + buffer);
|
+ " before=" + before + " after=" + after + ": " + buffer);
|
||||||
TextView.this.handleTextChanged(buffer, start, before, after);
|
TextView.this.handleTextChanged(buffer, start, before, after);
|
||||||
|
|
||||||
mEditTextShortcutController.onTextChange(buffer);
|
mEasyEditSpanController.onTextChange(buffer);
|
||||||
|
|
||||||
if (AccessibilityManager.getInstance(mContext).isEnabled() &&
|
if (AccessibilityManager.getInstance(mContext).isEnabled() &&
|
||||||
(isFocused() || isSelected() && isShown())) {
|
(isFocused() || isSelected() && isShown())) {
|
||||||
@@ -7997,7 +8070,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void hideControllers() {
|
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 {
|
private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
|
||||||
// 3 handles, 2 ActionPopup (suggestionsPopup first hides the others)
|
// 3 handles
|
||||||
private final int MAXIMUM_NUMBER_OF_LISTENERS = 5;
|
// 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
|
||||||
|
private final int MAXIMUM_NUMBER_OF_LISTENERS = 6;
|
||||||
private TextViewPositionListener[] mPositionListeners =
|
private TextViewPositionListener[] mPositionListeners =
|
||||||
new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
|
new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
|
||||||
private boolean mCanMove[] = new boolean[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
|
* Hides the insertion controller and stops text selection mode, hiding the selection controller
|
||||||
*/
|
*/
|
||||||
private void hideControllers() {
|
private void hideControllers() {
|
||||||
hideInsertionPointCursorController();
|
hideCursorControllers();
|
||||||
stopSelectionActionMode();
|
hideSpanControllers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideSpanControllers() {
|
||||||
if (mChangeWatcher != null) {
|
if (mChangeWatcher != null) {
|
||||||
mChangeWatcher.hideControllers();
|
mChangeWatcher.hideControllers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hideCursorControllers() {
|
||||||
|
hideInsertionPointCursorController();
|
||||||
|
stopSelectionActionMode();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the character offset closest to the specified absolute position. A typical use case is to
|
* 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.
|
* pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
|
||||||
|
|||||||
Reference in New Issue
Block a user