ScrollingMovementMethod.java revision 67b6ab72ae96a9f2be929de2c32c110df5390fdd
1/*
2 * Copyright (C) 2006 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
17package android.text.method;
18
19import android.view.MotionEvent;
20import android.text.*;
21import android.widget.TextView;
22import android.view.View;
23
24/**
25 * A movement method that interprets movement keys by scrolling the text buffer.
26 */
27public class ScrollingMovementMethod extends BaseMovementMethod implements MovementMethod {
28    private int getTopLine(TextView widget) {
29        return widget.getLayout().getLineForVertical(widget.getScrollY());
30    }
31
32    private int getBottomLine(TextView widget) {
33        return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget));
34    }
35
36    private int getInnerWidth(TextView widget) {
37        return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight();
38    }
39
40    private int getInnerHeight(TextView widget) {
41        return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom();
42    }
43
44    private int getCharacterWidth(TextView widget) {
45        return (int) Math.ceil(widget.getPaint().getFontSpacing());
46    }
47
48    private int getScrollBoundsLeft(TextView widget) {
49        final Layout layout = widget.getLayout();
50        final int topLine = getTopLine(widget);
51        final int bottomLine = getBottomLine(widget);
52        if (topLine > bottomLine) {
53            return 0;
54        }
55        int left = Integer.MAX_VALUE;
56        for (int line = topLine; line <= bottomLine; line++) {
57            final int lineLeft = (int) Math.floor(layout.getLineLeft(line));
58            if (lineLeft < left) {
59                left = lineLeft;
60            }
61        }
62        return left;
63    }
64
65    private int getScrollBoundsRight(TextView widget) {
66        final Layout layout = widget.getLayout();
67        final int topLine = getTopLine(widget);
68        final int bottomLine = getBottomLine(widget);
69        if (topLine > bottomLine) {
70            return 0;
71        }
72        int right = Integer.MIN_VALUE;
73        for (int line = topLine; line <= bottomLine; line++) {
74            final int lineRight = (int) Math.ceil(layout.getLineRight(line));
75            if (lineRight > right) {
76                right = lineRight;
77            }
78        }
79        return right;
80    }
81
82    @Override
83    protected boolean left(TextView widget, Spannable buffer) {
84        final int minScrollX = getScrollBoundsLeft(widget);
85        int scrollX = widget.getScrollX();
86        if (scrollX > minScrollX) {
87            scrollX = Math.max(scrollX - getCharacterWidth(widget), minScrollX);
88            widget.scrollTo(scrollX, widget.getScrollY());
89            return true;
90        }
91        return false;
92    }
93
94    @Override
95    protected boolean right(TextView widget, Spannable buffer) {
96        final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
97        int scrollX = widget.getScrollX();
98        if (scrollX < maxScrollX) {
99            scrollX = Math.min(scrollX + getCharacterWidth(widget), maxScrollX);
100            widget.scrollTo(scrollX, widget.getScrollY());
101            return true;
102        }
103        return false;
104    }
105
106    @Override
107    protected boolean up(TextView widget, Spannable buffer) {
108        final Layout layout = widget.getLayout();
109        final int top = widget.getScrollY();
110        int topLine = layout.getLineForVertical(top);
111        if (layout.getLineTop(topLine) == top) {
112            // If the top line is partially visible, bring it all the way
113            // into view; otherwise, bring the previous line into view.
114            topLine -= 1;
115        }
116        if (topLine >= 0) {
117            Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
118            return true;
119        }
120        return false;
121    }
122
123    @Override
124    protected boolean down(TextView widget, Spannable buffer) {
125        final Layout layout = widget.getLayout();
126        final int innerHeight = getInnerHeight(widget);
127        final int bottom = widget.getScrollY() + innerHeight;
128        int bottomLine = layout.getLineForVertical(bottom);
129        if (layout.getLineTop(bottomLine + 1) < bottom + 1) {
130            // Less than a pixel of this line is out of view,
131            // so we must have tried to make it entirely in view
132            // and now want the next line to be in view instead.
133            bottomLine += 1;
134        }
135        if (bottomLine <= layout.getLineCount() - 1) {
136            Touch.scrollTo(widget, layout, widget.getScrollX(),
137                    layout.getLineTop(bottomLine + 1) - innerHeight);
138            return true;
139        }
140        return false;
141    }
142
143    @Override
144    protected boolean pageUp(TextView widget, Spannable buffer) {
145        final Layout layout = widget.getLayout();
146        final int top = widget.getScrollY() - getInnerHeight(widget);
147        int topLine = layout.getLineForVertical(top);
148        if (topLine >= 0) {
149            Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
150            return true;
151        }
152        return false;
153    }
154
155    @Override
156    protected boolean pageDown(TextView widget, Spannable buffer) {
157        final Layout layout = widget.getLayout();
158        final int innerHeight = getInnerHeight(widget);
159        final int bottom = widget.getScrollY() + innerHeight + innerHeight;
160        int bottomLine = layout.getLineForVertical(bottom);
161        if (bottomLine <= layout.getLineCount() - 1) {
162            Touch.scrollTo(widget, layout, widget.getScrollX(),
163                    layout.getLineTop(bottomLine + 1) - innerHeight);
164            return true;
165        }
166        return false;
167    }
168
169    @Override
170    protected boolean top(TextView widget, Spannable buffer) {
171        final Layout layout = widget.getLayout();
172        if (getTopLine(widget) >= 0) {
173            Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0));
174            return true;
175        }
176        return false;
177    }
178
179    @Override
180    protected boolean bottom(TextView widget, Spannable buffer) {
181        final Layout layout = widget.getLayout();
182        final int lineCount = layout.getLineCount();
183        if (getBottomLine(widget) <= lineCount - 1) {
184            Touch.scrollTo(widget, layout, widget.getScrollX(),
185                    layout.getLineTop(lineCount) - getInnerHeight(widget));
186            return true;
187        }
188        return false;
189    }
190
191    @Override
192    protected boolean lineStart(TextView widget, Spannable buffer) {
193        final int minScrollX = getScrollBoundsLeft(widget);
194        int scrollX = widget.getScrollX();
195        if (scrollX > minScrollX) {
196            widget.scrollTo(minScrollX, widget.getScrollY());
197            return true;
198        }
199        return false;
200    }
201
202    @Override
203    protected boolean lineEnd(TextView widget, Spannable buffer) {
204        final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
205        int scrollX = widget.getScrollX();
206        if (scrollX < maxScrollX) {
207            widget.scrollTo(maxScrollX, widget.getScrollY());
208            return true;
209        }
210        return false;
211    }
212
213    @Override
214    protected boolean home(TextView widget, Spannable buffer) {
215        return top(widget, buffer);
216    }
217
218    @Override
219    protected boolean end(TextView widget, Spannable buffer) {
220        return bottom(widget, buffer);
221    }
222
223    @Override
224    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
225        return Touch.onTouchEvent(widget, buffer, event);
226    }
227
228    @Override
229    public void onTakeFocus(TextView widget, Spannable text, int dir) {
230        Layout layout = widget.getLayout();
231
232        if (layout != null && (dir & View.FOCUS_FORWARD) != 0) {
233            widget.scrollTo(widget.getScrollX(),
234                            layout.getLineTop(0));
235        }
236        if (layout != null && (dir & View.FOCUS_BACKWARD) != 0) {
237            int padding = widget.getTotalPaddingTop() +
238                          widget.getTotalPaddingBottom();
239            int line = layout.getLineCount() - 1;
240
241            widget.scrollTo(widget.getScrollX(),
242                            layout.getLineTop(line+1) -
243                            (widget.getHeight() - padding));
244        }
245    }
246
247    public static MovementMethod getInstance() {
248        if (sInstance == null)
249            sInstance = new ScrollingMovementMethod();
250
251        return sInstance;
252    }
253
254    private static ScrollingMovementMethod sInstance;
255}
256