Home | History | Annotate | Download | only in method
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.text.method;
     18 
     19 import android.text.Layout;
     20 import android.text.Layout.Alignment;
     21 import android.text.NoCopySpan;
     22 import android.text.Spannable;
     23 import android.view.KeyEvent;
     24 import android.view.MotionEvent;
     25 import android.view.ViewConfiguration;
     26 import android.widget.TextView;
     27 
     28 public class Touch {
     29     private Touch() { }
     30 
     31     /**
     32      * Scrolls the specified widget to the specified coordinates, except
     33      * constrains the X scrolling position to the horizontal regions of
     34      * the text that will be visible after scrolling to the specified
     35      * Y position.
     36      */
     37     public static void scrollTo(TextView widget, Layout layout, int x, int y) {
     38         final int horizontalPadding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
     39         final int availableWidth = widget.getWidth() - horizontalPadding;
     40 
     41         final int top = layout.getLineForVertical(y);
     42         Alignment a = layout.getParagraphAlignment(top);
     43         boolean ltr = layout.getParagraphDirection(top) > 0;
     44 
     45         int left, right;
     46         if (widget.getHorizontallyScrolling()) {
     47             final int verticalPadding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
     48             final int bottom = layout.getLineForVertical(y + widget.getHeight() - verticalPadding);
     49 
     50             left = Integer.MAX_VALUE;
     51             right = 0;
     52 
     53             for (int i = top; i <= bottom; i++) {
     54                 left = (int) Math.min(left, layout.getLineLeft(i));
     55                 right = (int) Math.max(right, layout.getLineRight(i));
     56             }
     57         } else {
     58             left = 0;
     59             right = availableWidth;
     60         }
     61 
     62         final int actualWidth = right - left;
     63 
     64         if (actualWidth < availableWidth) {
     65             if (a == Alignment.ALIGN_CENTER) {
     66                 x = left - ((availableWidth - actualWidth) / 2);
     67             } else if ((ltr && (a == Alignment.ALIGN_OPPOSITE)) ||
     68                        (!ltr && (a == Alignment.ALIGN_NORMAL)) ||
     69                        (a == Alignment.ALIGN_RIGHT)) {
     70                 // align_opposite does NOT mean align_right, we need the paragraph
     71                 // direction to resolve it to left or right
     72                 x = left - (availableWidth - actualWidth);
     73             } else {
     74                 x = left;
     75             }
     76         } else {
     77             x = Math.min(x, right - availableWidth);
     78             x = Math.max(x, left);
     79         }
     80 
     81         widget.scrollTo(x, y);
     82     }
     83 
     84     /**
     85      * Handles touch events for dragging.  You may want to do other actions
     86      * like moving the cursor on touch as well.
     87      */
     88     public static boolean onTouchEvent(TextView widget, Spannable buffer,
     89                                        MotionEvent event) {
     90         DragState[] ds;
     91 
     92         switch (event.getActionMasked()) {
     93         case MotionEvent.ACTION_DOWN:
     94             ds = buffer.getSpans(0, buffer.length(), DragState.class);
     95 
     96             for (int i = 0; i < ds.length; i++) {
     97                 buffer.removeSpan(ds[i]);
     98             }
     99 
    100             buffer.setSpan(new DragState(event.getX(), event.getY(),
    101                             widget.getScrollX(), widget.getScrollY()),
    102                     0, 0, Spannable.SPAN_MARK_MARK);
    103             return true;
    104 
    105         case MotionEvent.ACTION_UP:
    106             ds = buffer.getSpans(0, buffer.length(), DragState.class);
    107 
    108             for (int i = 0; i < ds.length; i++) {
    109                 buffer.removeSpan(ds[i]);
    110             }
    111 
    112             if (ds.length > 0 && ds[0].mUsed) {
    113                 return true;
    114             } else {
    115                 return false;
    116             }
    117 
    118         case MotionEvent.ACTION_MOVE:
    119             ds = buffer.getSpans(0, buffer.length(), DragState.class);
    120 
    121             if (ds.length > 0) {
    122                 ds[0].mIsSelectionStarted = false;
    123 
    124                 if (ds[0].mFarEnough == false) {
    125                     int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
    126 
    127                     if (Math.abs(event.getX() - ds[0].mX) >= slop ||
    128                         Math.abs(event.getY() - ds[0].mY) >= slop) {
    129                         ds[0].mFarEnough = true;
    130                         if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
    131                             ds[0].mIsActivelySelecting = true;
    132                             ds[0].mIsSelectionStarted = true;
    133                         }
    134                     }
    135                 }
    136 
    137                 if (ds[0].mFarEnough) {
    138                     ds[0].mUsed = true;
    139                     boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0
    140                             || MetaKeyKeyListener.getMetaState(buffer,
    141                                     MetaKeyKeyListener.META_SHIFT_ON) == 1
    142                             || MetaKeyKeyListener.getMetaState(buffer,
    143                                     MetaKeyKeyListener.META_SELECTING) != 0;
    144 
    145                     if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
    146                         ds[0].mIsActivelySelecting = false;
    147                     }
    148 
    149                     float dx;
    150                     float dy;
    151                     if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
    152                         // if we're selecting, we want the scroll to go in
    153                         // the direction of the drag
    154                         dx = event.getX() - ds[0].mX;
    155                         dy = event.getY() - ds[0].mY;
    156                     } else {
    157                         dx = ds[0].mX - event.getX();
    158                         dy = ds[0].mY - event.getY();
    159                     }
    160                     ds[0].mX = event.getX();
    161                     ds[0].mY = event.getY();
    162 
    163                     int nx = widget.getScrollX() + (int) dx;
    164                     int ny = widget.getScrollY() + (int) dy;
    165 
    166                     int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
    167                     Layout layout = widget.getLayout();
    168 
    169                     ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding));
    170                     ny = Math.max(ny, 0);
    171 
    172                     int oldX = widget.getScrollX();
    173                     int oldY = widget.getScrollY();
    174 
    175                     if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
    176                         scrollTo(widget, layout, nx, ny);
    177                     }
    178 
    179                     // If we actually scrolled, then cancel the up action.
    180                     if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
    181                         widget.cancelLongPress();
    182                     }
    183 
    184                     return true;
    185                 }
    186             }
    187         }
    188 
    189         return false;
    190     }
    191 
    192     /**
    193      * @param widget The text view.
    194      * @param buffer The text buffer.
    195      */
    196     public static int getInitialScrollX(TextView widget, Spannable buffer) {
    197         DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
    198         return ds.length > 0 ? ds[0].mScrollX : -1;
    199     }
    200 
    201     /**
    202      * @param widget The text view.
    203      * @param buffer The text buffer.
    204      */
    205     public static int getInitialScrollY(TextView widget, Spannable buffer) {
    206         DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
    207         return ds.length > 0 ? ds[0].mScrollY : -1;
    208     }
    209 
    210     /**
    211      * Checks if selection is still active.
    212      * This is useful for extending Selection span on buffer.
    213      * @param buffer The text buffer.
    214      * @return true if buffer has been marked for selection.
    215      *
    216      * @hide
    217      */
    218     static boolean isActivelySelecting(Spannable buffer) {
    219         DragState[] ds;
    220         ds = buffer.getSpans(0, buffer.length(), DragState.class);
    221 
    222         return ds.length > 0 && ds[0].mIsActivelySelecting;
    223     }
    224 
    225     /**
    226      * Checks if selection has begun (are we out of slop?).
    227      * Note: DragState.mIsSelectionStarted goes back to false with the very next event.
    228      * This is useful for starting Selection span on buffer.
    229      * @param buffer The text buffer.
    230      * @return true if selection has started on the buffer.
    231      *
    232      * @hide
    233      */
    234     static boolean isSelectionStarted(Spannable buffer) {
    235         DragState[] ds;
    236         ds = buffer.getSpans(0, buffer.length(), DragState.class);
    237 
    238         return ds.length > 0 && ds[0].mIsSelectionStarted;
    239     }
    240 
    241     private static class DragState implements NoCopySpan {
    242         public float mX;
    243         public float mY;
    244         public int mScrollX;
    245         public int mScrollY;
    246         public boolean mFarEnough;
    247         public boolean mUsed;
    248         public boolean mIsActivelySelecting;
    249         public boolean mIsSelectionStarted;
    250 
    251         public DragState(float x, float y, int scrollX, int scrollY) {
    252             mX = x;
    253             mY = y;
    254             mScrollX = scrollX;
    255             mScrollY = scrollY;
    256         }
    257     }
    258 }
    259