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