OSDN Git Service

Make up/down movement of multilength lines remember position
authorClara Bayarri <clarabayarri@google.com>
Thu, 26 May 2016 09:58:48 +0000 (10:58 +0100)
committerClara Bayarri <clarabayarri@google.com>
Tue, 18 Jul 2017 14:59:28 +0000 (15:59 +0100)
Test: Separate CTS CL being submitted along this one, see topic
Bug: 25486843
Change-Id: I463c126b07db05b1c4a26e870cde5c5567471da0

api/test-current.txt
core/java/android/text/Selection.java

index cd6637f..1f3b8e9 100644 (file)
@@ -41709,6 +41709,13 @@ package android.text {
     field public static final java.lang.Object SELECTION_START;
   }
 
+  public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
+    ctor public Selection.MemoryTextWatcher();
+    method public void afterTextChanged(android.text.Editable);
+    method public void beforeTextChanged(java.lang.CharSequence, int, int, int);
+    method public void onTextChanged(java.lang.CharSequence, int, int, int);
+  }
+
   public abstract interface SpanWatcher implements android.text.NoCopySpan {
     method public abstract void onSpanAdded(android.text.Spannable, java.lang.Object, int, int);
     method public abstract void onSpanChanged(android.text.Spannable, java.lang.Object, int, int, int, int);
index 3222dbf..3445658 100644 (file)
@@ -16,6 +16,8 @@
 
 package android.text;
 
+import android.annotation.TestApi;
+
 import java.text.BreakIterator;
 
 
@@ -35,10 +37,10 @@ public class Selection {
      * there is no selection or cursor.
      */
     public static final int getSelectionStart(CharSequence text) {
-        if (text instanceof Spanned)
+        if (text instanceof Spanned) {
             return ((Spanned) text).getSpanStart(SELECTION_START);
-        else
-            return -1;
+        }
+        return -1;
     }
 
     /**
@@ -46,10 +48,17 @@ public class Selection {
      * there is no selection or cursor.
      */
     public static final int getSelectionEnd(CharSequence text) {
-        if (text instanceof Spanned)
+        if (text instanceof Spanned) {
             return ((Spanned) text).getSpanStart(SELECTION_END);
-        else
-            return -1;
+        }
+        return -1;
+    }
+
+    private static int getSelectionMemory(CharSequence text) {
+        if (text instanceof Spanned) {
+            return ((Spanned) text).getSpanStart(SELECTION_MEMORY);
+        }
+        return -1;
     }
 
     /*
@@ -65,6 +74,14 @@ public class Selection {
      * to <code>stop</code>.
      */
     public static void setSelection(Spannable text, int start, int stop) {
+        setSelection(text, start, stop, -1);
+    }
+
+    /**
+     * Set the selection anchor to <code>start</code>, the selection edge
+     * to <code>stop</code> and the memory horizontal to <code>memory</code>.
+     */
+    private static void setSelection(Spannable text, int start, int stop, int memory) {
         // int len = text.length();
         // start = pin(start, 0, len);  XXX remove unless we really need it
         // stop = pin(stop, 0, len);
@@ -74,9 +91,57 @@ public class Selection {
 
         if (ostart != start || oend != stop) {
             text.setSpan(SELECTION_START, start, start,
-                         Spanned.SPAN_POINT_POINT|Spanned.SPAN_INTERMEDIATE);
-            text.setSpan(SELECTION_END, stop, stop,
-                         Spanned.SPAN_POINT_POINT);
+                    Spanned.SPAN_POINT_POINT | Spanned.SPAN_INTERMEDIATE);
+            text.setSpan(SELECTION_END, stop, stop, Spanned.SPAN_POINT_POINT);
+            updateMemory(text, memory);
+        }
+    }
+
+    /**
+     * Update the memory position for text. This is used to ensure vertical navigation of lines
+     * with different lengths behaves as expected and remembers the longest horizontal position
+     * seen during a vertical traversal.
+     */
+    private static void updateMemory(Spannable text, int memory) {
+        if (memory > -1) {
+            int currentMemory = getSelectionMemory(text);
+            if (memory != currentMemory) {
+                text.setSpan(SELECTION_MEMORY, memory, memory, Spanned.SPAN_POINT_POINT);
+                if (currentMemory == -1) {
+                    // This is the first value, create a watcher.
+                    final TextWatcher watcher = new MemoryTextWatcher();
+                    text.setSpan(watcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+            }
+        } else {
+            removeMemory(text);
+        }
+    }
+
+    private static void removeMemory(Spannable text) {
+        text.removeSpan(SELECTION_MEMORY);
+        MemoryTextWatcher[] watchers = text.getSpans(0, text.length(), MemoryTextWatcher.class);
+        for (MemoryTextWatcher watcher : watchers) {
+            text.removeSpan(watcher);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static final class MemoryTextWatcher implements TextWatcher {
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            s.removeSpan(SELECTION_MEMORY);
+            s.removeSpan(this);
         }
     }
 
@@ -98,8 +163,17 @@ public class Selection {
      * Move the selection edge to offset <code>index</code>.
      */
     public static final void extendSelection(Spannable text, int index) {
-        if (text.getSpanStart(SELECTION_END) != index)
+        extendSelection(text, index, -1);
+    }
+
+    /**
+     * Move the selection edge to offset <code>index</code> and update the memory horizontal.
+     */
+    private static void extendSelection(Spannable text, int index, int memory) {
+        if (text.getSpanStart(SELECTION_END) != index) {
             text.setSpan(SELECTION_END, index, index, Spanned.SPAN_POINT_POINT);
+        }
+        updateMemory(text, memory);
     }
 
     /**
@@ -108,6 +182,7 @@ public class Selection {
     public static final void removeSelection(Spannable text) {
         text.removeSpan(SELECTION_START);
         text.removeSpan(SELECTION_END);
+        removeMemory(text);
     }
 
     /*
@@ -138,17 +213,8 @@ public class Selection {
             int line = layout.getLineForOffset(end);
 
             if (line > 0) {
-                int move;
-
-                if (layout.getParagraphDirection(line) ==
-                    layout.getParagraphDirection(line - 1)) {
-                    float h = layout.getPrimaryHorizontal(end);
-                    move = layout.getOffsetForHorizontal(line - 1, h);
-                } else {
-                    move = layout.getLineStart(line - 1);
-                }
-
-                setSelection(text, move);
+                setSelectionAndMemory(
+                        text, layout, line, end, -1 /* direction */, false /* extend */);
                 return true;
             } else if (end != 0) {
                 setSelection(text, 0);
@@ -160,6 +226,40 @@ public class Selection {
     }
 
     /**
+     * Calculate the movement and memory positions needed, and set or extend the selection.
+     */
+    private static void setSelectionAndMemory(Spannable text, Layout layout, int line, int end,
+            int direction, boolean extend) {
+        int move;
+        int newMemory;
+
+        if (layout.getParagraphDirection(line)
+                == layout.getParagraphDirection(line + direction)) {
+            int memory = getSelectionMemory(text);
+            if (memory > -1) {
+                // We have a memory position
+                float h = layout.getPrimaryHorizontal(memory);
+                move = layout.getOffsetForHorizontal(line + direction, h);
+                newMemory = memory;
+            } else {
+                // Create a new memory position
+                float h = layout.getPrimaryHorizontal(end);
+                move = layout.getOffsetForHorizontal(line + direction, h);
+                newMemory = end;
+            }
+        } else {
+            move = layout.getLineStart(line + direction);
+            newMemory = -1;
+        }
+
+        if (extend) {
+            extendSelection(text, move, newMemory);
+        } else {
+            setSelection(text, move, move, newMemory);
+        }
+    }
+
+    /**
      * Move the cursor to the buffer offset physically below the current
      * offset, to the end of the buffer if it is on the bottom line but
      * not at the end, or return false if the cursor is already at the
@@ -184,17 +284,8 @@ public class Selection {
             int line = layout.getLineForOffset(end);
 
             if (line < layout.getLineCount() - 1) {
-                int move;
-
-                if (layout.getParagraphDirection(line) ==
-                    layout.getParagraphDirection(line + 1)) {
-                    float h = layout.getPrimaryHorizontal(end);
-                    move = layout.getOffsetForHorizontal(line + 1, h);
-                } else {
-                    move = layout.getLineStart(line + 1);
-                }
-
-                setSelection(text, move);
+                setSelectionAndMemory(
+                        text, layout, line, end, 1 /* direction */, false /* extend */);
                 return true;
             } else if (end != text.length()) {
                 setSelection(text, text.length());
@@ -263,17 +354,7 @@ public class Selection {
         int line = layout.getLineForOffset(end);
 
         if (line > 0) {
-            int move;
-
-            if (layout.getParagraphDirection(line) ==
-                layout.getParagraphDirection(line - 1)) {
-                float h = layout.getPrimaryHorizontal(end);
-                move = layout.getOffsetForHorizontal(line - 1, h);
-            } else {
-                move = layout.getLineStart(line - 1);
-            }
-
-            extendSelection(text, move);
+            setSelectionAndMemory(text, layout, line, end, -1 /* direction */, true /* extend */);
             return true;
         } else if (end != 0) {
             extendSelection(text, 0);
@@ -292,20 +373,10 @@ public class Selection {
         int line = layout.getLineForOffset(end);
 
         if (line < layout.getLineCount() - 1) {
-            int move;
-
-            if (layout.getParagraphDirection(line) ==
-                layout.getParagraphDirection(line + 1)) {
-                float h = layout.getPrimaryHorizontal(end);
-                move = layout.getOffsetForHorizontal(line + 1, h);
-            } else {
-                move = layout.getLineStart(line + 1);
-            }
-
-            extendSelection(text, move);
+            setSelectionAndMemory(text, layout, line, end, 1 /* direction */, true /* extend */);
             return true;
         } else if (end != text.length()) {
-            extendSelection(text, text.length());
+            extendSelection(text, text.length(), -1);
             return true;
         }
 
@@ -466,6 +537,8 @@ public class Selection {
 
     private static final class START implements NoCopySpan { }
     private static final class END implements NoCopySpan { }
+    private static final class MEMORY implements NoCopySpan { }
+    private static final Object SELECTION_MEMORY = new MEMORY();
 
     /*
      * Public constants