OSDN Git Service

Remove unused code.
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / ui / SlotView.java
1 /*
2  * Copyright (C) 2010 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 com.android.gallery3d.ui;
18
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.os.Handler;
22 import android.view.GestureDetector;
23 import android.view.MotionEvent;
24 import android.view.animation.DecelerateInterpolator;
25
26 import com.android.gallery3d.anim.Animation;
27 import com.android.gallery3d.common.Utils;
28 import com.android.gallery3d.ui.PositionRepository.Position;
29 import com.android.gallery3d.util.LinkedNode;
30
31 import java.util.ArrayList;
32 import java.util.HashMap;
33
34 public class SlotView extends GLView {
35     @SuppressWarnings("unused")
36     private static final String TAG = "SlotView";
37
38     private static final boolean WIDE = true;
39
40     private static final int INDEX_NONE = -1;
41
42     public interface Listener {
43         public void onDown(int index);
44         public void onUp();
45         public void onSingleTapUp(int index);
46         public void onLongTap(int index);
47         public void onScrollPositionChanged(int position, int total);
48     }
49
50     public static class SimpleListener implements Listener {
51         public void onDown(int index) {}
52         public void onUp() {}
53         public void onSingleTapUp(int index) {}
54         public void onLongTap(int index) {}
55         public void onScrollPositionChanged(int position, int total) {}
56     }
57
58     private final GestureDetector mGestureDetector;
59     private final ScrollerHelper mScroller;
60     private final Paper mPaper = new Paper();
61
62     private Listener mListener;
63     private UserInteractionListener mUIListener;
64
65     // Use linked hash map to keep the rendering order
66     private final HashMap<DisplayItem, ItemEntry> mItems =
67             new HashMap<DisplayItem, ItemEntry>();
68
69     public LinkedNode.List<ItemEntry> mItemList = LinkedNode.newList();
70
71     // This is used for multipass rendering
72     private ArrayList<ItemEntry> mCurrentItems = new ArrayList<ItemEntry>();
73     private ArrayList<ItemEntry> mNextItems = new ArrayList<ItemEntry>();
74
75     private boolean mMoreAnimation = false;
76     private MyAnimation mAnimation = null;
77     private final Position mTempPosition = new Position();
78     private final Layout mLayout = new Layout();
79     private PositionProvider mPositions;
80     private int mStartIndex = INDEX_NONE;
81
82     // whether the down action happened while the view is scrolling.
83     private boolean mDownInScrolling;
84     private int mOverscrollEffect = OVERSCROLL_3D;
85     private final Handler mHandler;
86
87     public static final int OVERSCROLL_3D = 0;
88     public static final int OVERSCROLL_SYSTEM = 1;
89     public static final int OVERSCROLL_NONE = 2;
90
91     public SlotView(Context context) {
92         mGestureDetector =
93                 new GestureDetector(context, new MyGestureListener());
94         mScroller = new ScrollerHelper(context);
95         mHandler = new Handler(context.getMainLooper());
96     }
97
98     public void setCenterIndex(int index) {
99         int slotCount = mLayout.mSlotCount;
100         if (index < 0 || index >= slotCount) {
101             return;
102         }
103         Rect rect = mLayout.getSlotRect(index);
104         int position = WIDE
105                 ? (rect.left + rect.right - getWidth()) / 2
106                 : (rect.top + rect.bottom - getHeight()) / 2;
107         setScrollPosition(position);
108     }
109
110     public void makeSlotVisible(int index) {
111         Rect rect = mLayout.getSlotRect(index);
112         int visibleBegin = WIDE ? mScrollX : mScrollY;
113         int visibleLength = WIDE ? getWidth() : getHeight();
114         int visibleEnd = visibleBegin + visibleLength;
115         int slotBegin = WIDE ? rect.left : rect.top;
116         int slotEnd = WIDE ? rect.right : rect.bottom;
117
118         int position = visibleBegin;
119         if (visibleLength < slotEnd - slotBegin) {
120             position = visibleBegin;
121         } else if (slotBegin < visibleBegin) {
122             position = slotBegin;
123         } else if (slotEnd > visibleEnd) {
124             position = slotEnd - visibleLength;
125         }
126
127         setScrollPosition(position);
128     }
129
130     public void setScrollPosition(int position) {
131         position = Utils.clamp(position, 0, mLayout.getScrollLimit());
132         mScroller.setPosition(position);
133         updateScrollPosition(position, false);
134     }
135
136     public void setSlotSpec(Spec spec) {
137         mLayout.setSlotSpec(spec);
138     }
139
140     @Override
141     public void addComponent(GLView view) {
142         throw new UnsupportedOperationException();
143     }
144
145     @Override
146     protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
147         if (!changeSize) return;
148
149         // Make sure we are still at a resonable scroll position after the size
150         // is changed (like orientation change). We choose to keep the center
151         // visible slot still visible. This is arbitrary but reasonable.
152         int visibleIndex =
153                 (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
154         mLayout.setSize(r - l, b - t);
155         makeSlotVisible(visibleIndex);
156
157         onLayoutChanged(r - l, b - t);
158         if (mOverscrollEffect == OVERSCROLL_3D) {
159             mPaper.setSize(r - l, b - t);
160         }
161     }
162
163     protected void onLayoutChanged(int width, int height) {
164     }
165
166     public void startTransition(PositionProvider position) {
167         mPositions = position;
168         mAnimation = new MyAnimation();
169         mAnimation.start();
170         if (mItems.size() != 0) invalidate();
171     }
172
173     public void savePositions(PositionRepository repository) {
174         repository.clear();
175         LinkedNode.List<ItemEntry> list = mItemList;
176         ItemEntry entry = list.getFirst();
177         Position position = new Position();
178         while (entry != null) {
179             position.set(entry.target);
180             position.x -= mScrollX;
181             position.y -= mScrollY;
182             repository.putPosition(entry.item.getIdentity(), position);
183             entry = list.nextOf(entry);
184         }
185     }
186
187     private void updateScrollPosition(int position, boolean force) {
188         if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
189         if (WIDE) {
190             mScrollX = position;
191         } else {
192             mScrollY = position;
193         }
194         mLayout.setScrollPosition(position);
195         onScrollPositionChanged(position);
196     }
197
198     protected void onScrollPositionChanged(int newPosition) {
199         int limit = mLayout.getScrollLimit();
200         mListener.onScrollPositionChanged(newPosition, limit);
201     }
202
203     public void putDisplayItem(Position target, Position base, DisplayItem item) {
204         item.setBox(mLayout.getSlotWidth(), mLayout.getSlotHeight());
205         ItemEntry entry = new ItemEntry(item, target, base);
206         mItemList.insertLast(entry);
207         mItems.put(item, entry);
208     }
209
210     public void removeDisplayItem(DisplayItem item) {
211         ItemEntry entry = mItems.remove(item);
212         if (entry != null) entry.remove();
213     }
214
215     public Rect getSlotRect(int slotIndex) {
216         return mLayout.getSlotRect(slotIndex);
217     }
218
219     @Override
220     protected boolean onTouch(MotionEvent event) {
221         if (mUIListener != null) mUIListener.onUserInteraction();
222         mGestureDetector.onTouchEvent(event);
223         switch (event.getAction()) {
224             case MotionEvent.ACTION_DOWN:
225                 mDownInScrolling = !mScroller.isFinished();
226                 mScroller.forceFinished();
227                 break;
228             case MotionEvent.ACTION_UP:
229                 mPaper.onRelease();
230                 invalidate();
231                 break;
232         }
233         return true;
234     }
235
236     public void setListener(Listener listener) {
237         mListener = listener;
238     }
239
240     public void setUserInteractionListener(UserInteractionListener listener) {
241         mUIListener = listener;
242     }
243
244     public void setOverscrollEffect(int kind) {
245         mOverscrollEffect = kind;
246         mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
247     }
248
249     @Override
250     protected void render(GLCanvas canvas) {
251         super.render(canvas);
252
253         long animTime = AnimationTime.get();
254         boolean more = mScroller.advanceAnimation(animTime);
255         int oldX = mScrollX;
256         updateScrollPosition(mScroller.getPosition(), false);
257
258         boolean paperActive = false;
259         if (mOverscrollEffect == OVERSCROLL_3D) {
260             // Check if an edge is reached and notify mPaper if so.
261             int newX = mScrollX;
262             int limit = mLayout.getScrollLimit();
263             if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) {
264                 float v = mScroller.getCurrVelocity();
265                 if (newX == limit) v = -v;
266
267                 // I don't know why, but getCurrVelocity() can return NaN.
268                 if (!Float.isNaN(v)) {
269                     mPaper.edgeReached(v);
270                 }
271             }
272             paperActive = mPaper.advanceAnimation();
273         }
274
275         more |= paperActive;
276
277         float interpolate = 1f;
278         if (mAnimation != null) {
279             more |= mAnimation.calculate(animTime);
280             interpolate = mAnimation.value;
281         }
282
283         if (WIDE) {
284             canvas.translate(-mScrollX, 0);
285         } else {
286             canvas.translate(0, -mScrollY);
287         }
288
289         LinkedNode.List<ItemEntry> list = mItemList;
290         for (ItemEntry entry = list.getLast(); entry != null;) {
291             int r = renderItem(canvas, entry, interpolate, 0, paperActive);
292             if ((r & DisplayItem.RENDER_MORE_PASS) != 0) {
293                 mCurrentItems.add(entry);
294             }
295             more |= ((r & DisplayItem.RENDER_MORE_FRAME) != 0);
296             entry = list.previousOf(entry);
297         }
298
299         int pass = 1;
300         while (!mCurrentItems.isEmpty()) {
301             for (int i = 0, n = mCurrentItems.size(); i < n; i++) {
302                 ItemEntry entry = mCurrentItems.get(i);
303                 int r = renderItem(canvas, entry, interpolate, pass, paperActive);
304                 if ((r & DisplayItem.RENDER_MORE_PASS) != 0) {
305                     mNextItems.add(entry);
306                 }
307                 more |= ((r & DisplayItem.RENDER_MORE_FRAME) != 0);
308             }
309             mCurrentItems.clear();
310             // swap mNextItems with mCurrentItems
311             ArrayList<ItemEntry> tmp = mNextItems;
312             mNextItems = mCurrentItems;
313             mCurrentItems = tmp;
314             pass += 1;
315         }
316
317         if (WIDE) {
318             canvas.translate(mScrollX, 0);
319         } else {
320             canvas.translate(0, mScrollY);
321         }
322
323         if (more) invalidate();
324
325         final UserInteractionListener listener = mUIListener;
326         if (mMoreAnimation && !more && listener != null) {
327             mHandler.post(new Runnable() {
328                 @Override
329                 public void run() {
330                     listener.onUserInteractionEnd();
331                 }
332             });
333         }
334         mMoreAnimation = more;
335     }
336
337     private int renderItem(GLCanvas canvas, ItemEntry entry,
338             float interpolate, int pass, boolean paperActive) {
339         canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
340         Position position = entry.target;
341         if (mPositions != null) {
342             position = mTempPosition;
343             position.set(entry.target);
344             position.x -= mScrollX;
345             position.y -= mScrollY;
346             Position source = mPositions
347                     .getPosition(entry.item.getIdentity(), position);
348             source.x += mScrollX;
349             source.y += mScrollY;
350             position = mTempPosition;
351             Position.interpolate(
352                     source, entry.target, position, interpolate);
353         }
354         canvas.multiplyAlpha(position.alpha);
355         if (paperActive) {
356             canvas.multiplyMatrix(mPaper.getTransform(
357                     position, entry.base, mScrollX, mScrollY), 0);
358         } else {
359             canvas.translate(position.x, position.y, position.z);
360         }
361         if (position.theta != 0) {
362             canvas.rotate(position.theta, 0, 0, 1);
363         }
364         int more = entry.item.render(canvas, pass);
365         canvas.restore();
366         return more;
367     }
368
369     public static class MyAnimation extends Animation {
370         public float value;
371
372         public MyAnimation() {
373             setInterpolator(new DecelerateInterpolator(4));
374             setDuration(1500);
375         }
376
377         @Override
378         protected void onCalculate(float progress) {
379             value = progress;
380         }
381     }
382
383     private static class ItemEntry extends LinkedNode {
384         public DisplayItem item;
385         public Position target;
386         public Position base;
387
388         public ItemEntry(DisplayItem item, Position target, Position base) {
389             this.item = item;
390             this.target = target;
391             this.base = base;
392         }
393     }
394
395     // This Spec class is used to specify the size of each slot in the SlotView.
396     // There are two ways to do it:
397     //
398     // (1) Specify slotWidth and slotHeight: they specify the width and height
399     //     of each slot. The number of rows and the gap between slots will be
400     //     determined automatically.
401     // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number
402     //     of rows in landscape/portrait mode and the gap between slots. The
403     //     width and height of each slot is determined automatically.
404     //
405     // The initial value of -1 means they are not specified.
406     public static class Spec {
407         public int slotWidth = -1;
408         public int slotHeight = -1;
409
410         public int rowsLand = -1;
411         public int rowsPort = -1;
412         public int slotGap = -1;
413     }
414
415     public static class Layout {
416
417         private int mVisibleStart;
418         private int mVisibleEnd;
419
420         private int mSlotCount;
421         private int mSlotWidth;
422         private int mSlotHeight;
423         private int mSlotGap;
424
425         private Spec mSpec;
426
427         private int mWidth;
428         private int mHeight;
429
430         private int mUnitCount;
431         private int mContentLength;
432         private int mScrollPosition;
433
434         private int mVerticalPadding;
435         private int mHorizontalPadding;
436
437         public void setSlotSpec(Spec spec) {
438             mSpec = spec;
439         }
440
441         public boolean setSlotCount(int slotCount) {
442             mSlotCount = slotCount;
443             int hPadding = mHorizontalPadding;
444             int vPadding = mVerticalPadding;
445             initLayoutParameters();
446             return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
447         }
448
449         public Rect getSlotRect(int index) {
450             int col, row;
451             if (WIDE) {
452                 col = index / mUnitCount;
453                 row = index - col * mUnitCount;
454             } else {
455                 row = index / mUnitCount;
456                 col = index - row * mUnitCount;
457             }
458
459             int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap);
460             int y = mVerticalPadding + row * (mSlotHeight + mSlotGap);
461             return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
462         }
463
464         public int getSlotWidth() {
465             return mSlotWidth;
466         }
467
468         public int getSlotHeight() {
469             return mSlotHeight;
470         }
471
472         // Calculate
473         // (1) mUnitCount: the number of slots we can fit into one column (or row).
474         // (2) mContentLength: the width (or height) we need to display all the
475         //     columns (rows).
476         // (3) padding[]: the vertical and horizontal padding we need in order
477         //     to put the slots towards to the center of the display.
478         //
479         // The "major" direction is the direction the user can scroll. The other
480         // direction is the "minor" direction.
481         //
482         // The comments inside this method are the description when the major
483         // directon is horizontal (X), and the minor directon is vertical (Y).
484         private void initLayoutParameters(
485                 int majorLength, int minorLength,  /* The view width and height */
486                 int majorUnitSize, int minorUnitSize,  /* The slot width and height */
487                 int[] padding) {
488             int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap);
489             if (unitCount == 0) unitCount = 1;
490             mUnitCount = unitCount;
491
492             // We put extra padding above and below the column.
493             int availableUnits = Math.min(mUnitCount, mSlotCount);
494             int usedMinorLength = availableUnits * minorUnitSize +
495                     (availableUnits - 1) * mSlotGap;
496             padding[0] = (minorLength - usedMinorLength) / 2;
497
498             // Then calculate how many columns we need for all slots.
499             int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
500             mContentLength = count * majorUnitSize + (count - 1) * mSlotGap;
501
502             // If the content length is less then the screen width, put
503             // extra padding in left and right.
504             padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
505         }
506
507         private void initLayoutParameters() {
508             // Initialize mSlotWidth and mSlotHeight from mSpec
509             if (mSpec.slotWidth != -1) {
510                 mSlotGap = 0;
511                 mSlotWidth = mSpec.slotWidth;
512                 mSlotHeight = mSpec.slotHeight;
513             } else {
514                 int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort;
515                 mSlotGap = mSpec.slotGap;
516                 mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows);
517                 mSlotWidth = mSlotHeight;
518             }
519
520             int[] padding = new int[2];
521             if (WIDE) {
522                 initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
523                 mVerticalPadding = padding[0];
524                 mHorizontalPadding = padding[1];
525             } else {
526                 initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
527                 mVerticalPadding = padding[1];
528                 mHorizontalPadding = padding[0];
529             }
530             updateVisibleSlotRange();
531         }
532
533         public void setSize(int width, int height) {
534             mWidth = width;
535             mHeight = height;
536             initLayoutParameters();
537         }
538
539         private void updateVisibleSlotRange() {
540             int position = mScrollPosition;
541
542             if (WIDE) {
543                 int startCol = position / (mSlotWidth + mSlotGap);
544                 int start = Math.max(0, mUnitCount * startCol);
545                 int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) /
546                         (mSlotWidth + mSlotGap);
547                 int end = Math.min(mSlotCount, mUnitCount * endCol);
548                 setVisibleRange(start, end);
549             } else {
550                 int startRow = position / (mSlotHeight + mSlotGap);
551                 int start = Math.max(0, mUnitCount * startRow);
552                 int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) /
553                         (mSlotHeight + mSlotGap);
554                 int end = Math.min(mSlotCount, mUnitCount * endRow);
555                 setVisibleRange(start, end);
556             }
557         }
558
559         public void setScrollPosition(int position) {
560             if (mScrollPosition == position) return;
561             mScrollPosition = position;
562             updateVisibleSlotRange();
563         }
564
565         private void setVisibleRange(int start, int end) {
566             if (start == mVisibleStart && end == mVisibleEnd) return;
567             if (start < end) {
568                 mVisibleStart = start;
569                 mVisibleEnd = end;
570             } else {
571                 mVisibleStart = mVisibleEnd = 0;
572             }
573         }
574
575         public int getVisibleStart() {
576             return mVisibleStart;
577         }
578
579         public int getVisibleEnd() {
580             return mVisibleEnd;
581         }
582
583         public int getSlotIndexByPosition(float x, float y) {
584             int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
585             int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
586
587             absoluteX -= mHorizontalPadding;
588             absoluteY -= mVerticalPadding;
589
590             if (absoluteX < 0 || absoluteY < 0) {
591                 return INDEX_NONE;
592             }
593
594             int columnIdx = absoluteX / (mSlotWidth + mSlotGap);
595             int rowIdx = absoluteY / (mSlotHeight + mSlotGap);
596
597             if (!WIDE && columnIdx >= mUnitCount) {
598                 return INDEX_NONE;
599             }
600
601             if (WIDE && rowIdx >= mUnitCount) {
602                 return INDEX_NONE;
603             }
604
605             if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) {
606                 return INDEX_NONE;
607             }
608
609             if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) {
610                 return INDEX_NONE;
611             }
612
613             int index = WIDE
614                     ? (columnIdx * mUnitCount + rowIdx)
615                     : (rowIdx * mUnitCount + columnIdx);
616
617             return index >= mSlotCount ? INDEX_NONE : index;
618         }
619
620         public int getScrollLimit() {
621             int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
622             return limit <= 0 ? 0 : limit;
623         }
624     }
625
626     private class MyGestureListener implements
627             GestureDetector.OnGestureListener {
628         private boolean isDown;
629
630         // We call the listener's onDown() when our onShowPress() is called and
631         // call the listener's onUp() when we receive any further event.
632         @Override
633         public void onShowPress(MotionEvent e) {
634             if (isDown) return;
635             int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
636             if (index != INDEX_NONE) {
637                 isDown = true;
638                 mListener.onDown(index);
639             }
640         }
641
642         private void cancelDown() {
643             if (!isDown) return;
644             isDown = false;
645             mListener.onUp();
646         }
647
648         @Override
649         public boolean onDown(MotionEvent e) {
650             return false;
651         }
652
653         @Override
654         public boolean onFling(MotionEvent e1,
655                 MotionEvent e2, float velocityX, float velocityY) {
656             cancelDown();
657             int scrollLimit = mLayout.getScrollLimit();
658             if (scrollLimit == 0) return false;
659             float velocity = WIDE ? velocityX : velocityY;
660             mScroller.fling((int) -velocity, 0, scrollLimit);
661             if (mUIListener != null) mUIListener.onUserInteractionBegin();
662             invalidate();
663             return true;
664         }
665
666         @Override
667         public boolean onScroll(MotionEvent e1,
668                 MotionEvent e2, float distanceX, float distanceY) {
669             cancelDown();
670             float distance = WIDE ? distanceX : distanceY;
671             int overDistance = mScroller.startScroll(
672                     Math.round(distance), 0, mLayout.getScrollLimit());
673             if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) {
674                 mPaper.overScroll(overDistance);
675             }
676             invalidate();
677             return true;
678         }
679
680         @Override
681         public boolean onSingleTapUp(MotionEvent e) {
682             cancelDown();
683             if (mDownInScrolling) return true;
684             int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
685             if (index != INDEX_NONE) mListener.onSingleTapUp(index);
686             return true;
687         }
688
689         @Override
690         public void onLongPress(MotionEvent e) {
691             cancelDown();
692             if (mDownInScrolling) return;
693             lockRendering();
694             try {
695                 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
696                 if (index != INDEX_NONE) mListener.onLongTap(index);
697             } finally {
698                 unlockRendering();
699             }
700         }
701     }
702
703     public void setStartIndex(int index) {
704         mStartIndex = index;
705     }
706
707     // Return true if the layout parameters have been changed
708     public boolean setSlotCount(int slotCount) {
709         boolean changed = mLayout.setSlotCount(slotCount);
710
711         // mStartIndex is applied the first time setSlotCount is called.
712         if (mStartIndex != INDEX_NONE) {
713             setCenterIndex(mStartIndex);
714             mStartIndex = INDEX_NONE;
715         }
716         // Reset the scroll position to avoid scrolling over the updated limit.
717         setScrollPosition(WIDE ? mScrollX : mScrollY);
718         return changed;
719     }
720
721     public int getVisibleStart() {
722         return mLayout.getVisibleStart();
723     }
724
725     public int getVisibleEnd() {
726         return mLayout.getVisibleEnd();
727     }
728
729     public int getScrollX() {
730         return mScrollX;
731     }
732
733     public int getScrollY() {
734         return mScrollY;
735     }
736 }