1172 | * Finds the next focusable component that fits in the specified bounds. 1173 | *
1174 | * 1175 | * @param topFocus 1176 | * look for a candidate is the one at the top of the bounds if 1177 | * topFocus is true, or at the bottom of the bounds if topFocus 1178 | * is false 1179 | * @param top 1180 | * the top offset of the bounds in which a focusable must be 1181 | * found 1182 | * @param bottom 1183 | * the bottom offset of the bounds in which a focusable must be 1184 | * found 1185 | * @return the next focusable component in the bounds or null if none can be 1186 | * found 1187 | */ 1188 | private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) { 1189 | 1190 | List1261 | * Handles scrolling in response to a "page up/down" shortcut press. This 1262 | * method will scroll the view by one page up or down and give the focus to 1263 | * the topmost/bottommost component in the new visible area. If no component 1264 | * is a good candidate for focus, this scrollview reclaims the focus. 1265 | *
1266 | * 1267 | * @param direction 1268 | * the scroll direction: {@link android.view.View#FOCUS_UP} to go 1269 | * one page up or {@link android.view.View#FOCUS_DOWN} to go one 1270 | * page down 1271 | * @return true if the key event is consumed by this method, false otherwise 1272 | */ 1273 | public boolean pageScroll(int direction) { 1274 | boolean down = direction == View.FOCUS_DOWN; 1275 | int height = getHeight(); 1276 | 1277 | if (down) { 1278 | mTempRect.top = getScrollY() + height; 1279 | int count = getChildCount(); 1280 | if (count > 0) { 1281 | View view = getChildAt(count - 1); 1282 | if (mTempRect.top + height > view.getBottom()) { 1283 | mTempRect.top = view.getBottom() - height; 1284 | } 1285 | } 1286 | } else { 1287 | mTempRect.top = getScrollY() - height; 1288 | if (mTempRect.top < 0) { 1289 | mTempRect.top = 0; 1290 | } 1291 | } 1292 | mTempRect.bottom = mTempRect.top + height; 1293 | 1294 | return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom); 1295 | } 1296 | 1297 | /** 1298 | *1299 | * Handles scrolling in response to a "home/end" shortcut press. This method 1300 | * will scroll the view to the top or bottom and give the focus to the 1301 | * topmost/bottommost component in the new visible area. If no component is 1302 | * a good candidate for focus, this scrollview reclaims the focus. 1303 | *
1304 | * 1305 | * @param direction 1306 | * the scroll direction: {@link android.view.View#FOCUS_UP} to go 1307 | * the top of the view or {@link android.view.View#FOCUS_DOWN} to 1308 | * go the bottom 1309 | * @return true if the key event is consumed by this method, false otherwise 1310 | */ 1311 | public boolean fullScroll(int direction) { 1312 | boolean down = direction == View.FOCUS_DOWN; 1313 | int height = getHeight(); 1314 | 1315 | mTempRect.top = 0; 1316 | mTempRect.bottom = height; 1317 | 1318 | if (down) { 1319 | int count = getChildCount(); 1320 | if (count > 0) { 1321 | View view = getChildAt(count - 1); 1322 | mTempRect.bottom = view.getBottom() + getPaddingBottom(); 1323 | mTempRect.top = mTempRect.bottom - height; 1324 | } 1325 | } 1326 | 1327 | return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom); 1328 | } 1329 | 1330 | /** 1331 | *
1332 | * Scrolls the view to make the area defined by top
and
1333 | * bottom
visible. This method attempts to give the focus to a
1334 | * component visible in this area. If no component can be focused in the new
1335 | * visible area, the focus is reclaimed by this ScrollView.
1336 | *
1551 | * The scroll range of a scroll view is the overall height of all of its 1552 | * children. 1553 | *
1554 | */ 1555 | @Override 1556 | protected int computeVerticalScrollRange() { 1557 | final int count = getChildCount(); 1558 | final int contentHeight = getHeight() - getPaddingBottom() 1559 | - getPaddingTop(); 1560 | if (count == 0) { 1561 | return contentHeight; 1562 | } 1563 | 1564 | int scrollRange = getChildAt(0).getBottom(); 1565 | final int scrollY = getScrollY(); 1566 | final int overscrollBottom = Math.max(0, scrollRange - contentHeight); 1567 | if (scrollY < 0) { 1568 | scrollRange -= scrollY; 1569 | } else if (scrollY > overscrollBottom) { 1570 | scrollRange += scrollY - overscrollBottom; 1571 | } 1572 | 1573 | return scrollRange; 1574 | } 1575 | 1576 | @Override 1577 | protected int computeVerticalScrollOffset() { 1578 | return Math.max(0, super.computeVerticalScrollOffset()); 1579 | } 1580 | 1581 | @Override 1582 | protected void measureChild(View child, int parentWidthMeasureSpec, 1583 | int parentHeightMeasureSpec) { 1584 | ViewGroup.LayoutParams lp = child.getLayoutParams(); 1585 | 1586 | int childWidthMeasureSpec; 1587 | int childHeightMeasureSpec; 1588 | 1589 | childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 1590 | getPaddingLeft() + getPaddingRight(), lp.width); 1591 | 1592 | childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, 1593 | MeasureSpec.UNSPECIFIED); 1594 | 1595 | child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 1596 | } 1597 | 1598 | @Override 1599 | protected void measureChildWithMargins(View child, 1600 | int parentWidthMeasureSpec, int widthUsed, 1601 | int parentHeightMeasureSpec, int heightUsed) { 1602 | final MarginLayoutParams lp = (MarginLayoutParams) child 1603 | .getLayoutParams(); 1604 | 1605 | final int childWidthMeasureSpec = getChildMeasureSpec( 1606 | parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight() 1607 | + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); 1608 | final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( 1609 | lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED); 1610 | 1611 | child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 1612 | } 1613 | 1614 | @Override 1615 | public void computeScroll() { 1616 | int measuredHeight = getMeasuredHeight(); 1617 | int measuredWidth = getMeasuredHeight(); 1618 | if (mScroller.computeScrollOffset()) { 1619 | int oldX = getScrollX(); 1620 | int oldY = getScrollY(); 1621 | int x = mScroller.getCurrX(); 1622 | int y = mScroller.getCurrY(); 1623 | if (oldX != x || oldY != y) { 1624 | final int horizontalRange = getHorizontalScrollRange(); 1625 | final int verticalRange = getVerticalScrollRange(); 1626 | final int overscrollMode = ViewCompat.getOverScrollMode(this); 1627 | final boolean canOverscroll = overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS 1628 | || (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && (verticalRange > 0 || horizontalRange > 0)); 1629 | 1630 | overScrollByCompat(x - oldX, y - oldY, oldX, oldY, horizontalRange, verticalRange, 0, 1631 | 0, false); 1632 | 1633 | if (canOverscroll) { 1634 | ensureGlows(); 1635 | if (y <= 0 && oldY > 0) { 1636 | mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); 1637 | } else if (y >= verticalRange && oldY < verticalRange) { 1638 | mEdgeGlowBottom.onAbsorb((int) mScroller 1639 | .getCurrVelocity()); 1640 | } 1641 | if (x <= 0 && oldX > 0) { 1642 | mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); 1643 | } else if (x >= verticalRange && oldX < horizontalRange) { 1644 | mEdgeGlowRight.onAbsorb((int) mScroller 1645 | .getCurrVelocity()); 1646 | } 1647 | } 1648 | } 1649 | } 1650 | } 1651 | 1652 | /** 1653 | * Scrolls the view to the given child. 1654 | * 1655 | * @param child 1656 | * the View to scroll to 1657 | */ 1658 | private void scrollToChild(View child) { 1659 | child.getDrawingRect(mTempRect); 1660 | 1661 | /* Offset from child's local coordinates to ScrollView coordinates */ 1662 | offsetDescendantRectToMyCoords(child, mTempRect); 1663 | 1664 | int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); 1665 | 1666 | if (scrollDelta != 0) { 1667 | scrollBy(0, scrollDelta); 1668 | } 1669 | } 1670 | 1671 | /** 1672 | * If rect is off screen, scroll just enough to get it (or at least the 1673 | * first screen size chunk of it) on screen. 1674 | * 1675 | * @param rect 1676 | * The rectangle. 1677 | * @param immediate 1678 | * True to scroll immediately without animation 1679 | * @return true if scrolling was performed 1680 | */ 1681 | private boolean scrollToChildRect(Rect rect, boolean immediate) { 1682 | final int delta = computeScrollDeltaToGetChildRectOnScreen(rect); 1683 | final boolean scroll = delta != 0; 1684 | if (scroll) { 1685 | if (immediate) { 1686 | scrollBy(0, delta); 1687 | } else { 1688 | smoothScrollBy(0, delta); 1689 | } 1690 | } 1691 | return scroll; 1692 | } 1693 | 1694 | /** 1695 | * Compute the amount to scroll in the Y direction in order to get a 1696 | * rectangle completely on the screen (or, if taller than the screen, at 1697 | * least the first screen size chunk of it). 1698 | * 1699 | * @param rect 1700 | * The rect. 1701 | * @return The scroll delta. 1702 | */ 1703 | protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { 1704 | if (getChildCount() == 0) 1705 | return 0; 1706 | 1707 | int height = getHeight(); 1708 | int screenTop = getScrollY(); 1709 | int screenBottom = screenTop + height; 1710 | 1711 | int fadingEdge = getVerticalFadingEdgeLength(); 1712 | 1713 | // leave room for top fading edge as long as rect isn't at very top 1714 | if (rect.top > 0) { 1715 | screenTop += fadingEdge; 1716 | } 1717 | 1718 | // leave room for bottom fading edge as long as rect isn't at very 1719 | // bottom 1720 | if (rect.bottom < getChildAt(0).getHeight()) { 1721 | screenBottom -= fadingEdge; 1722 | } 1723 | 1724 | int scrollYDelta = 0; 1725 | 1726 | if (rect.bottom > screenBottom && rect.top > screenTop) { 1727 | // need to move down to get it in view: move down just enough so 1728 | // that the entire rectangle is in view (or at least the first 1729 | // screen size chunk). 1730 | 1731 | if (rect.height() > height) { 1732 | // just enough to get screen size chunk on 1733 | scrollYDelta += (rect.top - screenTop); 1734 | } else { 1735 | // get entire rect at bottom of screen 1736 | scrollYDelta += (rect.bottom - screenBottom); 1737 | } 1738 | 1739 | // make sure we aren't scrolling beyond the end of our content 1740 | int bottom = getChildAt(0).getBottom(); 1741 | int distanceToBottom = bottom - screenBottom; 1742 | scrollYDelta = Math.min(scrollYDelta, distanceToBottom); 1743 | 1744 | } else if (rect.top < screenTop && rect.bottom < screenBottom) { 1745 | // need to move up to get it in view: move up just enough so that 1746 | // entire rectangle is in view (or at least the first screen 1747 | // size chunk of it). 1748 | 1749 | if (rect.height() > height) { 1750 | // screen size chunk 1751 | scrollYDelta -= (screenBottom - rect.bottom); 1752 | } else { 1753 | // entire rect at top 1754 | scrollYDelta -= (screenTop - rect.top); 1755 | } 1756 | 1757 | // make sure we aren't scrolling any further than the top our 1758 | // content 1759 | scrollYDelta = Math.max(scrollYDelta, -getScrollY()); 1760 | } 1761 | return scrollYDelta; 1762 | } 1763 | 1764 | @Override 1765 | public void requestChildFocus(View child, View focused) { 1766 | if (!mIsLayoutDirty) { 1767 | scrollToChild(focused); 1768 | } else { 1769 | // The child may not be laid out yet, we can't compute the scroll 1770 | // yet 1771 | mChildToScrollTo = focused; 1772 | } 1773 | super.requestChildFocus(child, focused); 1774 | } 1775 | 1776 | /** 1777 | * When looking for focus in children of a scroll view, need to be a little 1778 | * more careful not to give focus to something that is scrolled off screen. 1779 | * 1780 | * This is more expensive than the default {@link android.view.ViewGroup} 1781 | * implementation, otherwise this behavior might have been made the default. 1782 | */ 1783 | @Override 1784 | protected boolean onRequestFocusInDescendants(int direction, 1785 | Rect previouslyFocusedRect) { 1786 | 1787 | // convert from forward / backward notation to up / down / left / right 1788 | // (ugh). 1789 | if (direction == View.FOCUS_FORWARD) { 1790 | direction = View.FOCUS_DOWN; 1791 | } else if (direction == View.FOCUS_BACKWARD) { 1792 | direction = View.FOCUS_UP; 1793 | } 1794 | 1795 | final View nextFocus = previouslyFocusedRect == null ? FocusFinder 1796 | .getInstance().findNextFocus(this, null, direction) 1797 | : FocusFinder.getInstance().findNextFocusFromRect(this, 1798 | previouslyFocusedRect, direction); 1799 | 1800 | if (nextFocus == null) { 1801 | return false; 1802 | } 1803 | 1804 | if (isOffScreen(nextFocus)) { 1805 | return false; 1806 | } 1807 | 1808 | return nextFocus.requestFocus(direction, previouslyFocusedRect); 1809 | } 1810 | 1811 | @Override 1812 | public boolean requestChildRectangleOnScreen(View child, Rect rectangle, 1813 | boolean immediate) { 1814 | // offset into coordinate space of this scroll view 1815 | rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() 1816 | - child.getScrollY()); 1817 | 1818 | return scrollToChildRect(rectangle, immediate); 1819 | } 1820 | 1821 | @Override 1822 | public void requestLayout() { 1823 | mIsLayoutDirty = true; 1824 | super.requestLayout(); 1825 | } 1826 | 1827 | @Override 1828 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 1829 | super.onLayout(changed, l, t, r, b); 1830 | mIsLayoutDirty = false; 1831 | // Give a child focus if it needs it 1832 | if (mChildToScrollTo != null 1833 | && isViewDescendantOf(mChildToScrollTo, this)) { 1834 | scrollToChild(mChildToScrollTo); 1835 | } 1836 | mChildToScrollTo = null; 1837 | 1838 | if (!mIsLaidOut) { 1839 | if (mSavedState != null) { 1840 | scrollTo(getScrollX(), mSavedState.scrollPosition); 1841 | mSavedState = null; 1842 | } // mScrollY default value is "0" 1843 | 1844 | final int childHeight = (getChildCount() > 0) ? getChildAt(0) 1845 | .getMeasuredHeight() : 0; 1846 | final int scrollRange = Math.max(0, childHeight 1847 | - (b - t - getPaddingBottom() - getPaddingTop())); 1848 | 1849 | // Don't forget to clamp 1850 | if (getScrollY() > scrollRange) { 1851 | scrollTo(getScrollX(), scrollRange); 1852 | } else if (getScrollY() < 0) { 1853 | scrollTo(getScrollX(), 0); 1854 | } 1855 | } 1856 | 1857 | // Calling this with the present values causes it to re-claim them 1858 | scrollTo(getScrollX(), getScrollY()); 1859 | mIsLaidOut = true; 1860 | } 1861 | 1862 | @Override 1863 | public void onAttachedToWindow() { 1864 | mIsLaidOut = false; 1865 | } 1866 | 1867 | @Override 1868 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1869 | super.onSizeChanged(w, h, oldw, oldh); 1870 | 1871 | View currentFocused = findFocus(); 1872 | if (null == currentFocused || this == currentFocused) 1873 | return; 1874 | 1875 | // If the currently-focused view was visible on the screen when the 1876 | // screen was at the old height, then scroll the screen to make that 1877 | // view visible with the new screen height. 1878 | if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) { 1879 | currentFocused.getDrawingRect(mTempRect); 1880 | offsetDescendantRectToMyCoords(currentFocused, mTempRect); 1881 | int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); 1882 | doScrollY(scrollDelta); 1883 | } 1884 | } 1885 | 1886 | /** 1887 | * Return true if child is a descendant of parent, (or equal to the parent). 1888 | */ 1889 | private static boolean isViewDescendantOf(View child, View parent) { 1890 | if (child == parent) { 1891 | return true; 1892 | } 1893 | 1894 | final ViewParent theParent = child.getParent(); 1895 | return (theParent instanceof ViewGroup) 1896 | && isViewDescendantOf((View) theParent, parent); 1897 | } 1898 | 1899 | /** 1900 | * Fling the scroll view 1901 | * 1902 | * @param velocityY 1903 | * The initial velocity in the Y direction. Positive numbers mean 1904 | * that the finger/cursor is moving down the screen, which means 1905 | * we want to scroll towards the top. 1906 | */ 1907 | public void flingVertical(int velocityY) { 1908 | if (getChildCount() > 0) { 1909 | int width = getWidth() - getPaddingRight() - getPaddingLeft(); 1910 | int height = getHeight() - getPaddingBottom() - getPaddingTop(); 1911 | int bottom = getChildAt(0).getHeight(); 1912 | 1913 | int scrollX = getScrollX(); 1914 | mScroller.fling(scrollX, getScrollY(), 0, velocityY, scrollX, scrollX, 0,Math.max(0, bottom - height),0,height / 2); 1915 | 1916 | ViewCompat.postInvalidateOnAnimation(this); 1917 | } 1918 | } 1919 | /** 1920 | * void android.support.v4.widget.ScrollerCompat.fling 1921 | * (int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) 1922 | * @param velocityX 1923 | */ 1924 | public void flingHorizontal(int velocityX) { 1925 | if (getChildCount() > 0) { 1926 | int width = getWidth() - getPaddingRight() - getPaddingLeft(); 1927 | int height = getHeight() - getPaddingBottom() - getPaddingTop(); 1928 | int right = getChildAt(0).getWidth(); 1929 | 1930 | int scrollY = getScrollY(); 1931 | mScroller.fling(getScrollX(), scrollY, velocityX, 0,0, Math.max(0, right - width), scrollY, scrollY,width/2,0); 1932 | ViewCompat.postInvalidateOnAnimation(this); 1933 | } 1934 | } 1935 | 1936 | private void flingWithNestedDispatchVertical(int velocityY) { 1937 | final int scrollY = getScrollY(); 1938 | final boolean canFling = (scrollY > 0 || velocityY > 0) 1939 | && (scrollY < getVerticalScrollRange() || velocityY < 0); 1940 | if (!dispatchNestedPreFling(0, velocityY)) { 1941 | dispatchNestedFling(0, velocityY, canFling); 1942 | if (canFling) { 1943 | flingVertical(velocityY); 1944 | } 1945 | } 1946 | } 1947 | private void flingWithNestedDispatchHorizontal(int velocityX) { 1948 | final int scrollX = getScrollX(); 1949 | final boolean canFling = (scrollX > 0 || velocityX > 0) 1950 | && (scrollX < getHorizontalScrollRange() || velocityX < 0); 1951 | if (!dispatchNestedPreFling(velocityX, 0)) { 1952 | dispatchNestedFling(velocityX, 0, canFling); 1953 | if (canFling) { 1954 | flingHorizontal(velocityX); 1955 | } 1956 | } 1957 | } 1958 | 1959 | private void endDrag() { 1960 | mIsBeingDragged = false; 1961 | 1962 | recycleVelocityTracker(); 1963 | 1964 | if (mEdgeGlowTop != null) { 1965 | mEdgeGlowLeft.onRelease(); 1966 | mEdgeGlowRight.onRelease(); 1967 | mEdgeGlowTop.onRelease(); 1968 | mEdgeGlowBottom.onRelease(); 1969 | } 1970 | } 1971 | 1972 | /** 1973 | * {@inheritDoc} 1974 | * 1975 | *
1976 | * This version also clamps the scrolling to the bounds of our child.
1977 | */
1978 | @Override
1979 | public void scrollTo(int x, int y) {
1980 | // we rely on the fact the View.scrollBy calls scrollTo.
1981 | if (getChildCount() > 0) {
1982 | View child = getChildAt(0);
1983 | x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(),
1984 | child.getWidth());
1985 | y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(),
1986 | child.getHeight());
1987 | if (x != getScrollX() || y != getScrollY()) {
1988 | super.scrollTo(x, y);
1989 | }
1990 | }
1991 | }
1992 |
1993 | private void ensureGlows() {
1994 | if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
1995 | if (mEdgeGlowTop == null) {
1996 | Context context = getContext();
1997 | mEdgeGlowTop = new EdgeEffectCompat(context);
1998 | mEdgeGlowBottom = new EdgeEffectCompat(context);
1999 | mEdgeGlowLeft = new EdgeEffectCompat(context);
2000 | mEdgeGlowRight = new EdgeEffectCompat(context);
2001 | }
2002 | } else {
2003 | mEdgeGlowTop = null;
2004 | mEdgeGlowBottom = null;
2005 | mEdgeGlowLeft = null;
2006 | mEdgeGlowRight = null;
2007 | }
2008 | }
2009 | @Override
2010 | public int getSolidColor() {
2011 | return Color.BLUE;
2012 | }
2013 | @Override
2014 | public void draw(Canvas canvas) {
2015 | super.draw(canvas);
2016 | if (mEdgeGlowTop != null) {
2017 | final int scrollX = getScrollX();
2018 | final int scrollY = getScrollY();
2019 | if (scrollDirection == DIRECTION_HORIZONTAL) {
2020 | // if (!mEdgeGlowLeft.isFinished()) {
2021 | // Log.i("NestedScrollView", "draw 水平 左侧");
2022 | // final int restoreCount = canvas.save();
2023 | // final int height = getHeight() - getPaddingTop()
2024 | // - getPaddingBottom();
2025 | //
2026 | // canvas.translate(getPaddingLeft(), height);
2027 | // canvas.rotate(-90);
2028 | // mEdgeGlowLeft.setSize(getWidth(), height);
2029 | // if (mEdgeGlowLeft.draw(canvas)) {
2030 | // ViewCompat.postInvalidateOnAnimation(this);
2031 | // }
2032 | // canvas.restoreToCount(restoreCount);
2033 | // }
2034 | // if (!mEdgeGlowRight.isFinished()) {
2035 | // Log.i("NestedScrollView", "draw 水平 右侧");
2036 | // final int restoreCount = canvas.save();
2037 | // final int height = getHeight() - getPaddingTop()
2038 | // - getPaddingBottom();
2039 | //
2040 | // canvas.translate(getPaddingLeft() + getWidth(),height);
2041 | // canvas.rotate(90);
2042 | // mEdgeGlowRight.setSize(getWidth(), height);
2043 | // if (mEdgeGlowRight.draw(canvas)) {
2044 | // ViewCompat.postInvalidateOnAnimation(this);
2045 | // }
2046 | // canvas.restoreToCount(restoreCount);
2047 | // }
2048 |
2049 | if (!mEdgeGlowLeft.isFinished()) {
2050 | final int restoreCount = canvas.save();
2051 | final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2052 |
2053 | canvas.rotate(270);
2054 | canvas.translate(-height + getPaddingTop() - scrollY, Math.min(0, scrollX));
2055 | mEdgeGlowLeft.setSize(height, getWidth());
2056 | if (mEdgeGlowLeft.draw(canvas)) {
2057 | invalidate();
2058 | }
2059 | canvas.restoreToCount(restoreCount);
2060 | }
2061 | if (!mEdgeGlowRight.isFinished()) {
2062 | final int restoreCount = canvas.save();
2063 | final int width = getWidth();
2064 | final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2065 |
2066 | canvas.rotate(90);
2067 | canvas.translate(-getPaddingTop() + scrollY,
2068 | -(Math.max(getHorizontalScrollRange(), scrollX) + width));
2069 | mEdgeGlowRight.setSize(height, width);
2070 | if (mEdgeGlowRight.draw(canvas)) {
2071 | invalidate();
2072 | }
2073 | canvas.restoreToCount(restoreCount);
2074 | }
2075 | }else if (scrollDirection == DIRECTION_VERTICAL) {
2076 | if (!mEdgeGlowTop.isFinished()) {
2077 | final int restoreCount = canvas.save();
2078 | final int width = getWidth() - getPaddingLeft()
2079 | - getPaddingRight();
2080 |
2081 | canvas.translate(scrollX + getPaddingLeft(), Math.min(0, scrollY));
2082 | mEdgeGlowTop.setSize(width, getHeight());
2083 | if (mEdgeGlowTop.draw(canvas)) {
2084 | ViewCompat.postInvalidateOnAnimation(this);
2085 | }
2086 | canvas.restoreToCount(restoreCount);
2087 | }
2088 | if (!mEdgeGlowBottom.isFinished()) {
2089 | final int restoreCount = canvas.save();
2090 | final int width = getWidth() - getPaddingLeft()
2091 | - getPaddingRight();
2092 | final int height = getHeight();
2093 |
2094 | canvas.translate(scrollX - width + getPaddingLeft(),
2095 | Math.max(getVerticalScrollRange(), scrollY) + height);
2096 | canvas.rotate(180, width, 0);
2097 | mEdgeGlowBottom.setSize(width, height);
2098 | if (mEdgeGlowBottom.draw(canvas)) {
2099 | ViewCompat.postInvalidateOnAnimation(this);
2100 | }
2101 | canvas.restoreToCount(restoreCount);
2102 | }
2103 | }else {
2104 | //do nothing
2105 | }
2106 |
2107 |
2108 | }
2109 | }
2110 |
2111 | private static int clamp(int n, int my, int child) {
2112 | if (my >= child || n < 0) {
2113 | /*
2114 | * my >= child is this case: |--------------- me ---------------|
2115 | * |------ child ------| or |--------------- me ---------------|
2116 | * |------ child ------| or |--------------- me ---------------|
2117 | * |------ child ------|
2118 | *
2119 | * n < 0 is this case: |------ me ------| |-------- child --------|
2120 | * |-- mScrollX --|
2121 | */
2122 | return 0;
2123 | }
2124 | if ((my + n) > child) {
2125 | /*
2126 | * this case: |------ me ------| |------ child ------| |-- mScrollX
2127 | * --|
2128 | */
2129 | return child - my;
2130 | }
2131 | return n;
2132 | }
2133 |
2134 | @Override
2135 | protected void onRestoreInstanceState(Parcelable state) {
2136 | SavedState ss = (SavedState) state;
2137 | super.onRestoreInstanceState(ss.getSuperState());
2138 | mSavedState = ss;
2139 | requestLayout();
2140 | }
2141 |
2142 | @Override
2143 | protected Parcelable onSaveInstanceState() {
2144 | Parcelable superState = super.onSaveInstanceState();
2145 | SavedState ss = new SavedState(superState);
2146 | ss.scrollPosition = getScrollY();
2147 | return ss;
2148 | }
2149 |
2150 | static class SavedState extends BaseSavedState {
2151 | public int scrollPosition;
2152 |
2153 | SavedState(Parcelable superState) {
2154 | super(superState);
2155 | }
2156 |
2157 | public SavedState(Parcel source) {
2158 | super(source);
2159 | scrollPosition = source.readInt();
2160 | }
2161 |
2162 | @Override
2163 | public void writeToParcel(Parcel dest, int flags) {
2164 | super.writeToParcel(dest, flags);
2165 | dest.writeInt(scrollPosition);
2166 | }
2167 |
2168 | @Override
2169 | public String toString() {
2170 | return "HorizontalScrollView.SavedState{"
2171 | + Integer.toHexString(System.identityHashCode(this))
2172 | + " scrollPosition=" + scrollPosition + "}";
2173 | }
2174 |
2175 | public static final Parcelable.Creator