am 0a77ce27: New edge effects for scrolling widgets (overscroll)
Merge commit '0a77ce277c6ed2aa25bbea5f8cd5687c0720cb68' into gingerbread-plus-aosp * commit '0a77ce277c6ed2aa25bbea5f8cd5687c0720cb68': New edge effects for scrolling widgets (overscroll)
This commit is contained in:
612
api/current.xml
612
api/current.xml
@@ -5894,39 +5894,6 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="kraken_resource_pad62"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="16843459"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="kraken_resource_pad63"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="16843458"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="kraken_resource_pad64"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="16843457"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="kraken_resource_pad7"
|
||||
type="int"
|
||||
transient="false"
|
||||
@@ -7016,6 +6983,39 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="overscrollFooter"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="16843459"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="overscrollHeader"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="16843458"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="overscrollMode"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="16843457"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="padding"
|
||||
type="int"
|
||||
transient="false"
|
||||
@@ -44982,6 +44982,17 @@
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<method name="apply"
|
||||
return="void"
|
||||
abstract="true"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="clear"
|
||||
return="android.content.SharedPreferences.Editor"
|
||||
abstract="true"
|
||||
@@ -45092,17 +45103,6 @@
|
||||
<parameter name="key" type="java.lang.String">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="apply"
|
||||
return="void"
|
||||
abstract="true"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="SharedPreferences.OnSharedPreferenceChangeListener"
|
||||
abstract="true"
|
||||
@@ -185826,6 +185826,17 @@
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getOverscrollMode"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getPaddingBottom"
|
||||
return="int"
|
||||
abstract="false"
|
||||
@@ -186966,6 +186977,25 @@
|
||||
<parameter name="heightMeasureSpec" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="onOverscrolled"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="protected"
|
||||
>
|
||||
<parameter name="scrollX" type="int">
|
||||
</parameter>
|
||||
<parameter name="scrollY" type="int">
|
||||
</parameter>
|
||||
<parameter name="clampedX" type="boolean">
|
||||
</parameter>
|
||||
<parameter name="clampedY" type="boolean">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="onRestoreInstanceState"
|
||||
return="void"
|
||||
abstract="false"
|
||||
@@ -187119,6 +187149,35 @@
|
||||
<parameter name="visibility" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="overscrollBy"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="protected"
|
||||
>
|
||||
<parameter name="deltaX" type="int">
|
||||
</parameter>
|
||||
<parameter name="deltaY" type="int">
|
||||
</parameter>
|
||||
<parameter name="scrollX" type="int">
|
||||
</parameter>
|
||||
<parameter name="scrollY" type="int">
|
||||
</parameter>
|
||||
<parameter name="scrollRangeX" type="int">
|
||||
</parameter>
|
||||
<parameter name="scrollRangeY" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxOverscrollX" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxOverscrollY" type="int">
|
||||
</parameter>
|
||||
<parameter name="isTouchEvent" type="boolean">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="performClick"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
@@ -187945,6 +188004,19 @@
|
||||
<parameter name="l" type="android.view.View.OnTouchListener">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="setOverscrollMode"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="overscrollMode" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="setPadding"
|
||||
return="void"
|
||||
abstract="false"
|
||||
@@ -188567,6 +188639,39 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="OVERSCROLL_ALWAYS"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="0"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="OVERSCROLL_IF_CONTENT_SCROLLS"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="1"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="OVERSCROLL_NEVER"
|
||||
type="int"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value="2"
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET"
|
||||
type="int[]"
|
||||
transient="false"
|
||||
@@ -189329,6 +189434,28 @@
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getScaledOverflingDistance"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getScaledOverscrollDistance"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getScaledPagingTouchSlop"
|
||||
return="int"
|
||||
abstract="false"
|
||||
@@ -203521,6 +203648,17 @@
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getUseWebViewBackgroundForOverscrollBackground"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getUseWideViewPort"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
@@ -204113,6 +204251,19 @@
|
||||
<parameter name="use" type="boolean">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="setUseWebViewBackgroundForOverscrollBackground"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="view" type="boolean">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="setUseWideViewPort"
|
||||
return="void"
|
||||
abstract="false"
|
||||
@@ -214749,6 +214900,28 @@
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getOverscrollFooter"
|
||||
return="android.graphics.drawable.Drawable"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getOverscrollHeader"
|
||||
return="android.graphics.drawable.Drawable"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="isItemChecked"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
@@ -214894,6 +215067,32 @@
|
||||
<parameter name="itemsCanFocus" type="boolean">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="setOverscrollFooter"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="footer" type="android.graphics.drawable.Drawable">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="setOverscrollHeader"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="header" type="android.graphics.drawable.Drawable">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="setSelection"
|
||||
return="void"
|
||||
abstract="false"
|
||||
@@ -215465,6 +215664,331 @@
|
||||
</parameter>
|
||||
</method>
|
||||
</interface>
|
||||
<class name="OverScroller"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<constructor name="OverScroller"
|
||||
type="android.widget.OverScroller"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="context" type="android.content.Context">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<constructor name="OverScroller"
|
||||
type="android.widget.OverScroller"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="context" type="android.content.Context">
|
||||
</parameter>
|
||||
<parameter name="interpolator" type="android.view.animation.Interpolator">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<constructor name="OverScroller"
|
||||
type="android.widget.OverScroller"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="context" type="android.content.Context">
|
||||
</parameter>
|
||||
<parameter name="interpolator" type="android.view.animation.Interpolator">
|
||||
</parameter>
|
||||
<parameter name="bounceCoefficientX" type="float">
|
||||
</parameter>
|
||||
<parameter name="bounceCoefficientY" type="float">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<method name="abortAnimation"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="computeScrollOffset"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="fling"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="startX" type="int">
|
||||
</parameter>
|
||||
<parameter name="startY" type="int">
|
||||
</parameter>
|
||||
<parameter name="velocityX" type="int">
|
||||
</parameter>
|
||||
<parameter name="velocityY" type="int">
|
||||
</parameter>
|
||||
<parameter name="minX" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxX" type="int">
|
||||
</parameter>
|
||||
<parameter name="minY" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxY" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="fling"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="startX" type="int">
|
||||
</parameter>
|
||||
<parameter name="startY" type="int">
|
||||
</parameter>
|
||||
<parameter name="velocityX" type="int">
|
||||
</parameter>
|
||||
<parameter name="velocityY" type="int">
|
||||
</parameter>
|
||||
<parameter name="minX" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxX" type="int">
|
||||
</parameter>
|
||||
<parameter name="minY" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxY" type="int">
|
||||
</parameter>
|
||||
<parameter name="overX" type="int">
|
||||
</parameter>
|
||||
<parameter name="overY" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="forceFinished"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="finished" type="boolean">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="getCurrX"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getCurrY"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getFinalX"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getFinalY"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getStartX"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="getStartY"
|
||||
return="int"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="isFinished"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="isOverscrolled"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="notifyHorizontalEdgeReached"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="startX" type="int">
|
||||
</parameter>
|
||||
<parameter name="finalX" type="int">
|
||||
</parameter>
|
||||
<parameter name="overX" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="notifyVerticalEdgeReached"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="startY" type="int">
|
||||
</parameter>
|
||||
<parameter name="finalY" type="int">
|
||||
</parameter>
|
||||
<parameter name="overY" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="springback"
|
||||
return="boolean"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="startX" type="int">
|
||||
</parameter>
|
||||
<parameter name="startY" type="int">
|
||||
</parameter>
|
||||
<parameter name="minX" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxX" type="int">
|
||||
</parameter>
|
||||
<parameter name="minY" type="int">
|
||||
</parameter>
|
||||
<parameter name="maxY" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="startScroll"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="startX" type="int">
|
||||
</parameter>
|
||||
<parameter name="startY" type="int">
|
||||
</parameter>
|
||||
<parameter name="dx" type="int">
|
||||
</parameter>
|
||||
<parameter name="dy" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="startScroll"
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="startX" type="int">
|
||||
</parameter>
|
||||
<parameter name="startY" type="int">
|
||||
</parameter>
|
||||
<parameter name="dx" type="int">
|
||||
</parameter>
|
||||
<parameter name="dy" type="int">
|
||||
</parameter>
|
||||
<parameter name="duration" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
</class>
|
||||
<class name="PopupWindow"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
|
||||
@@ -1520,6 +1520,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
*/
|
||||
private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
|
||||
|
||||
/**
|
||||
* Always allow a user to overscroll this view, provided it is a
|
||||
* view that can scroll.
|
||||
*
|
||||
* @see #getOverscrollMode()
|
||||
* @see #setOverscrollMode(int)
|
||||
*/
|
||||
public static final int OVERSCROLL_ALWAYS = 0;
|
||||
|
||||
/**
|
||||
* Allow a user to overscroll this view only if the content is large
|
||||
* enough to meaningfully scroll, provided it is a view that can scroll.
|
||||
*
|
||||
* @see #getOverscrollMode()
|
||||
* @see #setOverscrollMode(int)
|
||||
*/
|
||||
public static final int OVERSCROLL_IF_CONTENT_SCROLLS = 1;
|
||||
|
||||
/**
|
||||
* Never allow a user to overscroll this view.
|
||||
*
|
||||
* @see #getOverscrollMode()
|
||||
* @see #setOverscrollMode(int)
|
||||
*/
|
||||
public static final int OVERSCROLL_NEVER = 2;
|
||||
|
||||
/**
|
||||
* Controls the overscroll mode for this view.
|
||||
* See {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)},
|
||||
* {@link #OVERSCROLL_ALWAYS}, {@link #OVERSCROLL_IF_CONTENT_SCROLLS},
|
||||
* and {@link #OVERSCROLL_NEVER}.
|
||||
*/
|
||||
private int mOverscrollMode = OVERSCROLL_ALWAYS;
|
||||
|
||||
/**
|
||||
* The parent this view is attached to.
|
||||
* {@hide}
|
||||
@@ -1877,6 +1911,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
|
||||
int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
|
||||
|
||||
int overscrollMode = mOverscrollMode;
|
||||
final int N = a.getIndexCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
int attr = a.getIndex(i);
|
||||
@@ -2076,9 +2111,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
});
|
||||
}
|
||||
break;
|
||||
case R.styleable.View_overscrollMode:
|
||||
overscrollMode = a.getInt(attr, OVERSCROLL_ALWAYS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setOverscrollMode(overscrollMode);
|
||||
|
||||
if (background != null) {
|
||||
setBackgroundDrawable(background);
|
||||
}
|
||||
@@ -8680,6 +8720,128 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
return factory.inflate(resource, root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the view with standard behavior for scrolling beyond the normal
|
||||
* content boundaries. Views that call this method should override
|
||||
* {@link #onOverscrolled(int, int, boolean, boolean)} to respond to the
|
||||
* results of an overscroll operation.
|
||||
*
|
||||
* Views can use this method to handle any touch or fling-based scrolling.
|
||||
*
|
||||
* @param deltaX Change in X in pixels
|
||||
* @param deltaY Change in Y in pixels
|
||||
* @param scrollX Current X scroll value in pixels before applying deltaX
|
||||
* @param scrollY Current Y scroll value in pixels before applying deltaY
|
||||
* @param scrollRangeX Maximum content scroll range along the X axis
|
||||
* @param scrollRangeY Maximum content scroll range along the Y axis
|
||||
* @param maxOverscrollX Number of pixels to overscroll by in either direction
|
||||
* along the X axis.
|
||||
* @param maxOverscrollY Number of pixels to overscroll by in either direction
|
||||
* along the Y axis.
|
||||
* @param isTouchEvent true if this scroll operation is the result of a touch event.
|
||||
* @return true if scrolling was clamped to an overscroll boundary along either
|
||||
* axis, false otherwise.
|
||||
*/
|
||||
protected boolean overscrollBy(int deltaX, int deltaY,
|
||||
int scrollX, int scrollY,
|
||||
int scrollRangeX, int scrollRangeY,
|
||||
int maxOverscrollX, int maxOverscrollY,
|
||||
boolean isTouchEvent) {
|
||||
final int overscrollMode = mOverscrollMode;
|
||||
final boolean canScrollHorizontal =
|
||||
computeHorizontalScrollRange() > computeHorizontalScrollExtent();
|
||||
final boolean canScrollVertical =
|
||||
computeVerticalScrollRange() > computeVerticalScrollExtent();
|
||||
final boolean overscrollHorizontal = overscrollMode == OVERSCROLL_ALWAYS ||
|
||||
(overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
|
||||
final boolean overscrollVertical = overscrollMode == OVERSCROLL_ALWAYS ||
|
||||
(overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
|
||||
|
||||
int newScrollX = scrollX + deltaX;
|
||||
if (!overscrollHorizontal) {
|
||||
maxOverscrollX = 0;
|
||||
}
|
||||
|
||||
int newScrollY = scrollY + deltaY;
|
||||
if (!overscrollVertical) {
|
||||
maxOverscrollY = 0;
|
||||
}
|
||||
|
||||
// Clamp values if at the limits and record
|
||||
final int left = -maxOverscrollX;
|
||||
final int right = maxOverscrollX + scrollRangeX;
|
||||
final int top = -maxOverscrollY;
|
||||
final int bottom = maxOverscrollY + scrollRangeY;
|
||||
|
||||
boolean clampedX = false;
|
||||
if (newScrollX > right) {
|
||||
newScrollX = right;
|
||||
clampedX = true;
|
||||
} else if (newScrollX < left) {
|
||||
newScrollX = left;
|
||||
clampedX = true;
|
||||
}
|
||||
|
||||
boolean clampedY = false;
|
||||
if (newScrollY > bottom) {
|
||||
newScrollY = bottom;
|
||||
clampedY = true;
|
||||
} else if (newScrollY < top) {
|
||||
newScrollY = top;
|
||||
clampedY = true;
|
||||
}
|
||||
|
||||
onOverscrolled(newScrollX, newScrollY, clampedX, clampedY);
|
||||
|
||||
return clampedX || clampedY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)} to
|
||||
* respond to the results of an overscroll operation.
|
||||
*
|
||||
* @param scrollX New X scroll value in pixels
|
||||
* @param scrollY New Y scroll value in pixels
|
||||
* @param clampedX True if scrollX was clamped to an overscroll boundary
|
||||
* @param clampedY True if scrollY was clamped to an overscroll boundary
|
||||
*/
|
||||
protected void onOverscrolled(int scrollX, int scrollY,
|
||||
boolean clampedX, boolean clampedY) {
|
||||
// Intentionally empty.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the overscroll mode for this view. The result will be
|
||||
* one of {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS}
|
||||
* (allow overscrolling only if the view content is larger than the container),
|
||||
* or {@link #OVERSCROLL_NEVER}.
|
||||
*
|
||||
* @return This view's overscroll mode.
|
||||
*/
|
||||
public int getOverscrollMode() {
|
||||
return mOverscrollMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the overscroll mode for this view. Valid overscroll modes are
|
||||
* {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS}
|
||||
* (allow overscrolling only if the view content is larger than the container),
|
||||
* or {@link #OVERSCROLL_NEVER}.
|
||||
*
|
||||
* Setting the overscroll mode of a view will have an effect only if the
|
||||
* view is capable of scrolling.
|
||||
*
|
||||
* @param overscrollMode The new overscroll mode for this view.
|
||||
*/
|
||||
public void setOverscrollMode(int overscrollMode) {
|
||||
if (overscrollMode != OVERSCROLL_ALWAYS &&
|
||||
overscrollMode != OVERSCROLL_IF_CONTENT_SCROLLS &&
|
||||
overscrollMode != OVERSCROLL_NEVER) {
|
||||
throw new IllegalArgumentException("Invalid overscroll mode " + overscrollMode);
|
||||
}
|
||||
mOverscrollMode = overscrollMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
|
||||
* Each MeasureSpec represents a requirement for either the width or the height.
|
||||
|
||||
@@ -140,6 +140,16 @@ public class ViewConfiguration {
|
||||
*/
|
||||
private static float SCROLL_FRICTION = 0.015f;
|
||||
|
||||
/**
|
||||
* Max distance to overscroll for edge effects
|
||||
*/
|
||||
private static final int OVERSCROLL_DISTANCE = 4;
|
||||
|
||||
/**
|
||||
* Max distance to overfling for edge effects
|
||||
*/
|
||||
private static final int OVERFLING_DISTANCE = 8;
|
||||
|
||||
private final int mEdgeSlop;
|
||||
private final int mFadingEdgeLength;
|
||||
private final int mMinimumFlingVelocity;
|
||||
@@ -150,6 +160,8 @@ public class ViewConfiguration {
|
||||
private final int mDoubleTapSlop;
|
||||
private final int mWindowTouchSlop;
|
||||
private final int mMaximumDrawingCacheSize;
|
||||
private final int mOverscrollDistance;
|
||||
private final int mOverflingDistance;
|
||||
|
||||
private static final SparseArray<ViewConfiguration> sConfigurations =
|
||||
new SparseArray<ViewConfiguration>(2);
|
||||
@@ -170,6 +182,8 @@ public class ViewConfiguration {
|
||||
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
|
||||
//noinspection deprecation
|
||||
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
|
||||
mOverscrollDistance = OVERSCROLL_DISTANCE;
|
||||
mOverflingDistance = OVERFLING_DISTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +212,9 @@ public class ViewConfiguration {
|
||||
|
||||
// Size of the screen in bytes, in ARGB_8888 format
|
||||
mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels;
|
||||
|
||||
mOverscrollDistance = (int) (density * OVERSCROLL_DISTANCE + 0.5f);
|
||||
mOverflingDistance = (int) (density * OVERFLING_DISTANCE + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,6 +471,20 @@ public class ViewConfiguration {
|
||||
return mMaximumDrawingCacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The maximum distance a View should overscroll by when showing edge effects.
|
||||
*/
|
||||
public int getScaledOverscrollDistance() {
|
||||
return mOverscrollDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The maximum distance a View should overfling by when showing edge effects.
|
||||
*/
|
||||
public int getScaledOverflingDistance() {
|
||||
return mOverflingDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of time that the zoom controls should be
|
||||
* displayed on the screen expressed in milliseconds.
|
||||
|
||||
@@ -207,6 +207,7 @@ public class WebSettings {
|
||||
private boolean mBuiltInZoomControls = false;
|
||||
private boolean mAllowFileAccess = true;
|
||||
private boolean mLoadWithOverviewMode = false;
|
||||
private boolean mUseWebViewBackgroundOverscrollBackground = true;
|
||||
|
||||
// private WebSettings, not accessible by the host activity
|
||||
static private int mDoubleTapToastCount = 3;
|
||||
@@ -484,6 +485,23 @@ public class WebSettings {
|
||||
return mLoadWithOverviewMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the WebView uses its background for over scroll background.
|
||||
* If true, it will use the WebView's background. If false, it will use an
|
||||
* internal pattern. Default is true.
|
||||
*/
|
||||
public void setUseWebViewBackgroundForOverscrollBackground(boolean view) {
|
||||
mUseWebViewBackgroundOverscrollBackground = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this WebView uses WebView's background instead of
|
||||
* internal pattern for over scroll background.
|
||||
*/
|
||||
public boolean getUseWebViewBackgroundForOverscrollBackground() {
|
||||
return mUseWebViewBackgroundOverscrollBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store whether the WebView is saving form data.
|
||||
*/
|
||||
|
||||
@@ -39,8 +39,8 @@ import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslCertificate;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@@ -79,7 +79,7 @@ import android.widget.CheckedTextView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Scroller;
|
||||
import android.widget.OverScroller;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ZoomButtonsController;
|
||||
import android.widget.ZoomControls;
|
||||
@@ -92,8 +92,8 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -557,7 +557,10 @@ public class WebView extends AbsoluteLayout
|
||||
// time for the longest scroll animation
|
||||
private static final int MAX_DURATION = 750; // milliseconds
|
||||
private static final int SLIDE_TITLE_DURATION = 500; // milliseconds
|
||||
private Scroller mScroller;
|
||||
private OverScroller mScroller;
|
||||
private boolean mInOverScrollMode = false;
|
||||
private static Paint mOverScrollBackground;
|
||||
private static Paint mOverScrollBorder;
|
||||
|
||||
private boolean mWrapContent;
|
||||
private static final int MOTIONLESS_FALSE = 0;
|
||||
@@ -943,7 +946,7 @@ public class WebView extends AbsoluteLayout
|
||||
mViewManager = new ViewManager(this);
|
||||
mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
|
||||
mDatabase = WebViewDatabase.getInstance(context);
|
||||
mScroller = new Scroller(context);
|
||||
mScroller = new OverScroller(context);
|
||||
|
||||
updateMultiTouchSupport(context);
|
||||
}
|
||||
@@ -983,6 +986,7 @@ public class WebView extends AbsoluteLayout
|
||||
setFocusableInTouchMode(true);
|
||||
setClickable(true);
|
||||
setLongClickable(true);
|
||||
setOverscrollMode(OVERSCROLL_NEVER);
|
||||
|
||||
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
|
||||
int slop = configuration.getScaledTouchSlop();
|
||||
@@ -1146,7 +1150,8 @@ public class WebView extends AbsoluteLayout
|
||||
* Return the amount of the titlebarview (if any) that is visible
|
||||
*/
|
||||
private int getVisibleTitleHeight() {
|
||||
return Math.max(getTitleHeight() - mScrollY, 0);
|
||||
// need to restrict mScrollY due to over scroll
|
||||
return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1786,7 +1791,7 @@ public class WebView extends AbsoluteLayout
|
||||
}
|
||||
nativeClearCursor(); // start next trackball movement from page edge
|
||||
if (bottom) {
|
||||
return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0);
|
||||
return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0);
|
||||
}
|
||||
// Page down.
|
||||
int h = getHeight();
|
||||
@@ -2016,13 +2021,15 @@ public class WebView extends AbsoluteLayout
|
||||
|
||||
// Expects x in view coordinates
|
||||
private int pinLocX(int x) {
|
||||
return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
|
||||
if (mInOverScrollMode) return x;
|
||||
return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
|
||||
}
|
||||
|
||||
// Expects y in view coordinates
|
||||
private int pinLocY(int y) {
|
||||
if (mInOverScrollMode) return y;
|
||||
return pinLoc(y, getViewHeightWithTitle(),
|
||||
computeVerticalScrollRange() + getTitleHeight());
|
||||
computeRealVerticalScrollRange() + getTitleHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2334,7 +2341,7 @@ public class WebView extends AbsoluteLayout
|
||||
// Sets r to be our visible rectangle in content coordinates
|
||||
private void calcOurContentVisibleRect(Rect r) {
|
||||
calcOurVisibleRect(r);
|
||||
// pin the rect to the bounds of the content
|
||||
// since we might overscroll, pin the rect to the bounds of the content
|
||||
r.left = Math.max(viewToContentX(r.left), 0);
|
||||
// viewToContentY will remove the total height of the title bar. Add
|
||||
// the visible height back in to account for the fact that if the title
|
||||
@@ -2415,8 +2422,7 @@ public class WebView extends AbsoluteLayout
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeHorizontalScrollRange() {
|
||||
private int computeRealHorizontalScrollRange() {
|
||||
if (mDrawHistory) {
|
||||
return mHistoryWidth;
|
||||
} else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF
|
||||
@@ -2430,7 +2436,27 @@ public class WebView extends AbsoluteLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeVerticalScrollRange() {
|
||||
protected int computeHorizontalScrollRange() {
|
||||
int range = computeRealHorizontalScrollRange();
|
||||
|
||||
// Adjust reported range if overscrolled to compress the scroll bars
|
||||
final int scrollX = mScrollX;
|
||||
final int overscrollRight = computeMaxScrollX();
|
||||
if (scrollX < 0) {
|
||||
range -= scrollX;
|
||||
} else if (scrollX > overscrollRight) {
|
||||
range += scrollX - overscrollRight;
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeHorizontalScrollOffset() {
|
||||
return Math.max(mScrollX, 0);
|
||||
}
|
||||
|
||||
private int computeRealVerticalScrollRange() {
|
||||
if (mDrawHistory) {
|
||||
return mHistoryHeight;
|
||||
} else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
|
||||
@@ -2443,6 +2469,22 @@ public class WebView extends AbsoluteLayout
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeVerticalScrollRange() {
|
||||
int range = computeRealVerticalScrollRange();
|
||||
|
||||
// Adjust reported range if overscrolled to compress the scroll bars
|
||||
final int scrollY = mScrollY;
|
||||
final int overscrollBottom = computeMaxScrollY();
|
||||
if (scrollY < 0) {
|
||||
range -= scrollY;
|
||||
} else if (scrollY > overscrollBottom) {
|
||||
range += scrollY - overscrollBottom;
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeVerticalScrollOffset() {
|
||||
return Math.max(mScrollY - getTitleHeight(), 0);
|
||||
@@ -2462,6 +2504,23 @@ public class WebView extends AbsoluteLayout
|
||||
scrollBar.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOverscrolled(int scrollX, int scrollY, boolean clampedX,
|
||||
boolean clampedY) {
|
||||
mInOverScrollMode = false;
|
||||
int maxX = computeMaxScrollX();
|
||||
if (maxX == 0) {
|
||||
// do not over scroll x if the page just fits the screen
|
||||
scrollX = pinLocX(scrollX);
|
||||
} else if (scrollX < 0 || scrollX > maxX) {
|
||||
mInOverScrollMode = true;
|
||||
}
|
||||
if (scrollY < 0 || scrollY > computeMaxScrollY()) {
|
||||
mInOverScrollMode = true;
|
||||
}
|
||||
super.scrollTo(scrollX, scrollY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url for the current page. This is not always the same as the url
|
||||
* passed to WebViewClient.onPageStarted because although the load for
|
||||
@@ -2815,11 +2874,14 @@ public class WebView extends AbsoluteLayout
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
int oldX = mScrollX;
|
||||
int oldY = mScrollY;
|
||||
mScrollX = mScroller.getCurrX();
|
||||
mScrollY = mScroller.getCurrY();
|
||||
int x = mScroller.getCurrX();
|
||||
int y = mScroller.getCurrY();
|
||||
postInvalidate(); // So we draw again
|
||||
if (oldX != mScrollX || oldY != mScrollY) {
|
||||
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
|
||||
|
||||
if (oldX != x || oldY != y) {
|
||||
overscrollBy(x - oldX, y - oldY, oldX, oldY,
|
||||
computeMaxScrollX(), computeMaxScrollY(),
|
||||
getViewWidth() / 3, getViewHeight() / 3, false);
|
||||
}
|
||||
} else {
|
||||
super.computeScroll();
|
||||
@@ -3230,8 +3292,13 @@ public class WebView extends AbsoluteLayout
|
||||
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
||||
if (child == mTitleBar) {
|
||||
// When drawing the title bar, move it horizontally to always show
|
||||
// at the top of the WebView.
|
||||
// at the top of the WebView. While overscroll, stick the title bar
|
||||
// on the top otherwise we may have two during loading, one is drawn
|
||||
// here, another is drawn by the Browser.
|
||||
mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft());
|
||||
if (mScrollY <= 0) {
|
||||
mTitleBar.offsetTopAndBottom(mScrollY - mTitleBar.getTop());
|
||||
}
|
||||
}
|
||||
return super.drawChild(canvas, child, drawingTime);
|
||||
}
|
||||
@@ -3267,6 +3334,36 @@ public class WebView extends AbsoluteLayout
|
||||
}
|
||||
|
||||
int saveCount = canvas.save();
|
||||
if (mInOverScrollMode && !getSettings()
|
||||
.getUseWebViewBackgroundForOverscrollBackground()) {
|
||||
if (mOverScrollBackground == null) {
|
||||
mOverScrollBackground = new Paint();
|
||||
Bitmap bm = BitmapFactory.decodeResource(
|
||||
mContext.getResources(),
|
||||
com.android.internal.R.drawable.status_bar_background);
|
||||
mOverScrollBackground.setShader(new BitmapShader(bm,
|
||||
Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
|
||||
mOverScrollBorder = new Paint();
|
||||
mOverScrollBorder.setStyle(Paint.Style.STROKE);
|
||||
mOverScrollBorder.setStrokeWidth(0);
|
||||
mOverScrollBorder.setColor(0xffbbbbbb);
|
||||
}
|
||||
|
||||
int top = getTitleHeight();
|
||||
int right = computeRealHorizontalScrollRange();
|
||||
int bottom = top + computeRealVerticalScrollRange();
|
||||
// first draw the background and anchor to the top of the view
|
||||
canvas.save();
|
||||
canvas.translate(mScrollX, mScrollY);
|
||||
canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom
|
||||
- mScrollY, Region.Op.DIFFERENCE);
|
||||
canvas.drawPaint(mOverScrollBackground);
|
||||
canvas.restore();
|
||||
// then draw the border
|
||||
canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
|
||||
// next clip the region for the content
|
||||
canvas.clipRect(0, top, right, bottom);
|
||||
}
|
||||
if (mTitleBar != null) {
|
||||
canvas.translate(0, (int) mTitleBar.getHeight());
|
||||
}
|
||||
@@ -4415,12 +4512,14 @@ public class WebView extends AbsoluteLayout
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
sendOurVisibleRect();
|
||||
// update WebKit if visible title bar height changed. The logic is same
|
||||
// as getVisibleTitleHeight.
|
||||
int titleHeight = getTitleHeight();
|
||||
if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
|
||||
sendViewSizeZoom();
|
||||
if (!mInOverScrollMode) {
|
||||
sendOurVisibleRect();
|
||||
// update WebKit if visible title bar height changed. The logic is same
|
||||
// as getVisibleTitleHeight.
|
||||
int titleHeight = getTitleHeight();
|
||||
if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
|
||||
sendViewSizeZoom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4490,7 +4589,7 @@ public class WebView extends AbsoluteLayout
|
||||
public DragTrackerHandler(float x, float y, DragTracker proxy) {
|
||||
mProxy = proxy;
|
||||
|
||||
int docBottom = computeVerticalScrollRange() + getTitleHeight();
|
||||
int docBottom = computeRealVerticalScrollRange() + getTitleHeight();
|
||||
int viewTop = getScrollY();
|
||||
int viewBottom = viewTop + getHeight();
|
||||
|
||||
@@ -4503,7 +4602,7 @@ public class WebView extends AbsoluteLayout
|
||||
" up/down= " + mMinDY + " " + mMaxDY);
|
||||
}
|
||||
|
||||
int docRight = computeHorizontalScrollRange();
|
||||
int docRight = computeRealHorizontalScrollRange();
|
||||
int viewLeft = getScrollX();
|
||||
int viewRight = viewLeft + getWidth();
|
||||
mStartX = x;
|
||||
@@ -5011,18 +5110,6 @@ public class WebView extends AbsoluteLayout
|
||||
}
|
||||
|
||||
// do pan
|
||||
int newScrollX = pinLocX(mScrollX + deltaX);
|
||||
int newDeltaX = newScrollX - mScrollX;
|
||||
if (deltaX != newDeltaX) {
|
||||
deltaX = newDeltaX;
|
||||
fDeltaX = (float) newDeltaX;
|
||||
}
|
||||
int newScrollY = pinLocY(mScrollY + deltaY);
|
||||
int newDeltaY = newScrollY - mScrollY;
|
||||
if (deltaY != newDeltaY) {
|
||||
deltaY = newDeltaY;
|
||||
fDeltaY = (float) newDeltaY;
|
||||
}
|
||||
boolean done = false;
|
||||
boolean keepScrollBarsVisible = false;
|
||||
if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
|
||||
@@ -5206,6 +5293,12 @@ public class WebView extends AbsoluteLayout
|
||||
mHeldMotionless = MOTIONLESS_IGNORE;
|
||||
doFling();
|
||||
break;
|
||||
} else {
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0,
|
||||
computeMaxScrollX(), 0,
|
||||
computeMaxScrollY())) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
// redraw in high-quality, as we're done dragging
|
||||
mHeldMotionless = MOTIONLESS_TRUE;
|
||||
@@ -5225,6 +5318,8 @@ public class WebView extends AbsoluteLayout
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
if (mTouchMode == TOUCH_DRAG_MODE) {
|
||||
mScroller.springback(mScrollX, mScrollY, 0,
|
||||
computeMaxScrollX(), 0, computeMaxScrollY());
|
||||
invalidate();
|
||||
}
|
||||
cancelWebCoreTouchEvent(contentX, contentY, false);
|
||||
@@ -5288,7 +5383,9 @@ public class WebView extends AbsoluteLayout
|
||||
|
||||
private void doDrag(int deltaX, int deltaY) {
|
||||
if ((deltaX | deltaY) != 0) {
|
||||
scrollBy(deltaX, deltaY);
|
||||
overscrollBy(deltaX, deltaY, mScrollX, mScrollY,
|
||||
computeMaxScrollX(), computeMaxScrollY(),
|
||||
getViewWidth() / 3, getViewHeight() / 3, true);
|
||||
}
|
||||
if (!getSettings().getBuiltInZoomControls()) {
|
||||
boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
|
||||
@@ -5636,17 +5733,17 @@ public class WebView extends AbsoluteLayout
|
||||
}
|
||||
|
||||
private int computeMaxScrollX() {
|
||||
return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
|
||||
return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
|
||||
}
|
||||
|
||||
private int computeMaxScrollY() {
|
||||
return Math.max(computeVerticalScrollRange() + getTitleHeight()
|
||||
return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
|
||||
- getViewHeightWithTitle(), 0);
|
||||
}
|
||||
|
||||
public void flingScroll(int vx, int vy) {
|
||||
mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
|
||||
computeMaxScrollY());
|
||||
computeMaxScrollY(), getViewWidth() / 3, getViewHeight() / 3);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@@ -5676,6 +5773,10 @@ public class WebView extends AbsoluteLayout
|
||||
if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
|
||||
WebViewCore.resumePriority();
|
||||
WebViewCore.resumeUpdatePicture(mWebViewCore);
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0, computeMaxScrollX(),
|
||||
0, computeMaxScrollY())) {
|
||||
invalidate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
float currentVelocity = mScroller.getCurrVelocity();
|
||||
@@ -5703,7 +5804,9 @@ public class WebView extends AbsoluteLayout
|
||||
mLastVelY = vy;
|
||||
mLastVelocity = (float) Math.hypot(vx, vy);
|
||||
|
||||
mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
|
||||
// no horizontal overscroll if the content just fits
|
||||
mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY,
|
||||
maxX == 0 ? 0 : getViewWidth() / 3, getViewHeight() / 3);
|
||||
// TODO: duration is calculated based on velocity, if the range is
|
||||
// small, the animation will stop before duration is up. We may
|
||||
// want to calculate how long the animation is going to run to precisely
|
||||
@@ -6757,6 +6860,10 @@ public class WebView extends AbsoluteLayout
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mDeferTouchMode == TOUCH_DRAG_MODE) {
|
||||
// no fling in defer process
|
||||
mScroller.springback(mScrollX, mScrollY, 0,
|
||||
computeMaxScrollX(), 0,
|
||||
computeMaxScrollY());
|
||||
invalidate();
|
||||
WebViewCore.resumePriority();
|
||||
WebViewCore.resumeUpdatePicture(mWebViewCore);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package android.widget;
|
||||
import com.android.internal.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
@@ -33,6 +34,7 @@ import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
@@ -44,7 +46,7 @@ import android.view.ViewConfiguration;
|
||||
import android.view.ViewDebug;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
@@ -127,6 +129,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
*/
|
||||
static final int TOUCH_MODE_FLING = 4;
|
||||
|
||||
/**
|
||||
* Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
|
||||
*/
|
||||
static final int TOUCH_MODE_OVERSCROLL = 5;
|
||||
|
||||
/**
|
||||
* Indicates the view is being flung outside of normal content bounds
|
||||
* and will spring back.
|
||||
*/
|
||||
static final int TOUCH_MODE_OVERFLING = 6;
|
||||
|
||||
/**
|
||||
* Regular layout - usually an unsolicited layout from the view system
|
||||
*/
|
||||
@@ -368,6 +381,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
|
||||
private ContextMenuInfo mContextMenuInfo = null;
|
||||
|
||||
/**
|
||||
* Maximum distance to record overscroll
|
||||
*/
|
||||
int mOverscrollMax;
|
||||
|
||||
/**
|
||||
* Content height divided by this is the overscroll limit.
|
||||
*/
|
||||
static final int OVERSCROLL_LIMIT_DIVISOR = 3;
|
||||
|
||||
/**
|
||||
* Used to request a layout when we changed touch mode
|
||||
*/
|
||||
@@ -460,6 +483,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
*/
|
||||
private static final int INVALID_POINTER = -1;
|
||||
|
||||
/**
|
||||
* Maximum distance to overscroll by during edge effects
|
||||
*/
|
||||
int mOverscrollDistance;
|
||||
|
||||
/**
|
||||
* Maximum distance to overfling during edge effects
|
||||
*/
|
||||
int mOverflingDistance;
|
||||
|
||||
// These two EdgeGlows are always set and used together.
|
||||
// Checking one for null is as good as checking both.
|
||||
|
||||
/**
|
||||
* Tracks the state of the top edge glow.
|
||||
*/
|
||||
private EdgeGlow mEdgeGlowTop;
|
||||
|
||||
/**
|
||||
* Tracks the state of the bottom edge glow.
|
||||
*/
|
||||
private EdgeGlow mEdgeGlowBottom;
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when the list or grid
|
||||
* has been scrolled.
|
||||
@@ -575,9 +621,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
mOverscrollDistance = configuration.getScaledOverscrollDistance();
|
||||
mOverflingDistance = configuration.getScaledOverflingDistance();
|
||||
|
||||
mDensityScale = getContext().getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverscrollMode(int mode) {
|
||||
if (mode != OVERSCROLL_NEVER) {
|
||||
if (mEdgeGlowTop == null) {
|
||||
final Resources res = getContext().getResources();
|
||||
final Drawable edge = res.getDrawable(R.drawable.edge_light);
|
||||
final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
|
||||
mEdgeGlowTop = new EdgeGlow(edge, glow);
|
||||
mEdgeGlowBottom = new EdgeGlow(edge, glow);
|
||||
}
|
||||
} else {
|
||||
mEdgeGlowTop = null;
|
||||
mEdgeGlowBottom = null;
|
||||
}
|
||||
super.setOverscrollMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables fast scrolling by letting the user quickly scroll through lists by
|
||||
* dragging the fast scroll thumb. The adapter attached to the list may want
|
||||
@@ -1074,6 +1140,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
int result;
|
||||
if (mSmoothScrollbarEnabled) {
|
||||
result = Math.max(mItemCount * 100, 0);
|
||||
if (mScrollY != 0) {
|
||||
// Compensate for overscroll
|
||||
result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
|
||||
}
|
||||
} else {
|
||||
result = mItemCount;
|
||||
}
|
||||
@@ -1146,6 +1216,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
|
||||
layoutChildren();
|
||||
mInLayout = false;
|
||||
|
||||
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1921,9 +1993,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
// Check if we have moved far enough that it looks more like a
|
||||
// scroll than a tap
|
||||
final int distance = Math.abs(deltaY);
|
||||
if (distance > mTouchSlop) {
|
||||
final boolean overscroll = mScrollY != 0;
|
||||
if (overscroll || distance > mTouchSlop) {
|
||||
createScrollingCache();
|
||||
mTouchMode = TOUCH_MODE_SCROLL;
|
||||
mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
|
||||
mMotionCorrection = deltaY;
|
||||
final Handler handler = getHandler();
|
||||
// Handler should not be null unless the AbsListView is not attached to a
|
||||
@@ -1959,6 +2032,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
// touch mode). Force an initial layout to get rid of the selection.
|
||||
layoutChildren();
|
||||
}
|
||||
} else {
|
||||
int touchMode = mTouchMode;
|
||||
if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
|
||||
if (mFlingRunnable != null) {
|
||||
mFlingRunnable.endFling();
|
||||
}
|
||||
|
||||
if (mScrollY != 0) {
|
||||
mScrollY = 0;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1989,49 +2074,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
int motionPosition = pointToPosition(x, y);
|
||||
if (!mDataChanged) {
|
||||
if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
|
||||
&& (getAdapter().isEnabled(motionPosition))) {
|
||||
// User clicked on an actual view (and was not stopping a fling). It might be a
|
||||
// click or a scroll. Assume it is a click until proven otherwise
|
||||
mTouchMode = TOUCH_MODE_DOWN;
|
||||
// FIXME Debounce
|
||||
if (mPendingCheckForTap == null) {
|
||||
mPendingCheckForTap = new CheckForTap();
|
||||
}
|
||||
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
|
||||
} else {
|
||||
if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
|
||||
// If we couldn't find a view to click on, but the down event was touching
|
||||
// the edge, we will bail out and try again. This allows the edge correcting
|
||||
// code in ViewRoot to try to find a nearby view to select
|
||||
return false;
|
||||
}
|
||||
switch (mTouchMode) {
|
||||
case TOUCH_MODE_OVERFLING: {
|
||||
mFlingRunnable.endFling();
|
||||
mTouchMode = TOUCH_MODE_OVERSCROLL;
|
||||
mMotionY = mLastY = (int) ev.getY();
|
||||
mMotionCorrection = 0;
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mTouchMode == TOUCH_MODE_FLING) {
|
||||
// Stopped a fling. It is a scroll.
|
||||
createScrollingCache();
|
||||
mTouchMode = TOUCH_MODE_SCROLL;
|
||||
mMotionCorrection = 0;
|
||||
motionPosition = findMotionRow(y);
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
|
||||
default: {
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
int motionPosition = pointToPosition(x, y);
|
||||
if (!mDataChanged) {
|
||||
if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
|
||||
&& (getAdapter().isEnabled(motionPosition))) {
|
||||
// User clicked on an actual view (and was not stopping a fling). It might be a
|
||||
// click or a scroll. Assume it is a click until proven otherwise
|
||||
mTouchMode = TOUCH_MODE_DOWN;
|
||||
// FIXME Debounce
|
||||
if (mPendingCheckForTap == null) {
|
||||
mPendingCheckForTap = new CheckForTap();
|
||||
}
|
||||
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
|
||||
} else {
|
||||
if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
|
||||
// If we couldn't find a view to click on, but the down event was touching
|
||||
// the edge, we will bail out and try again. This allows the edge correcting
|
||||
// code in ViewRoot to try to find a nearby view to select
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mTouchMode == TOUCH_MODE_FLING) {
|
||||
// Stopped a fling. It is a scroll.
|
||||
createScrollingCache();
|
||||
mTouchMode = TOUCH_MODE_SCROLL;
|
||||
mMotionCorrection = 0;
|
||||
motionPosition = findMotionRow(y);
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (motionPosition >= 0) {
|
||||
// Remember where the motion event started
|
||||
v = getChildAt(motionPosition - mFirstPosition);
|
||||
mMotionViewOriginalTop = v.getTop();
|
||||
if (motionPosition >= 0) {
|
||||
// Remember where the motion event started
|
||||
v = getChildAt(motionPosition - mFirstPosition);
|
||||
mMotionViewOriginalTop = v.getTop();
|
||||
}
|
||||
mMotionX = x;
|
||||
mMotionY = y;
|
||||
mMotionPosition = motionPosition;
|
||||
mLastY = Integer.MIN_VALUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mMotionX = x;
|
||||
mMotionY = y;
|
||||
mMotionPosition = motionPosition;
|
||||
mLastY = Integer.MIN_VALUE;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2064,9 +2163,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
|
||||
final int rawDeltaY = deltaY;
|
||||
deltaY -= mMotionCorrection;
|
||||
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
|
||||
|
||||
final int motionIndex;
|
||||
if (mMotionPosition >= 0) {
|
||||
motionIndex = mMotionPosition - mFirstPosition;
|
||||
} else {
|
||||
// If we don't have a motion position that we can reliably track,
|
||||
// pick something in the middle to make a best guess at things below.
|
||||
motionIndex = getChildCount() / 2;
|
||||
}
|
||||
|
||||
int motionViewPrevTop = 0;
|
||||
View motionView = this.getChildAt(motionIndex);
|
||||
if (motionView != null) {
|
||||
motionViewPrevTop = motionView.getTop();
|
||||
}
|
||||
|
||||
// No need to do all this work if we're not going to move anyway
|
||||
boolean atEdge = false;
|
||||
if (incrementalDeltaY != 0) {
|
||||
@@ -2074,23 +2189,92 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
}
|
||||
|
||||
// Check to see if we have bumped into the scroll limit
|
||||
if (atEdge && getChildCount() > 0) {
|
||||
// Treat this like we're starting a new scroll from the current
|
||||
// position. This will let the user start scrolling back into
|
||||
// content immediately rather than needing to scroll back to the
|
||||
// point where they hit the limit first.
|
||||
int motionPosition = findMotionRow(y);
|
||||
if (motionPosition >= 0) {
|
||||
final View motionView = getChildAt(motionPosition - mFirstPosition);
|
||||
mMotionViewOriginalTop = motionView.getTop();
|
||||
motionView = this.getChildAt(motionIndex);
|
||||
if (motionView != null) {
|
||||
// Check if the top of the motion view is where it is
|
||||
// supposed to be
|
||||
final int motionViewRealTop = motionView.getTop();
|
||||
if (atEdge) {
|
||||
// Apply overscroll
|
||||
|
||||
int overscroll = -incrementalDeltaY -
|
||||
(motionViewRealTop - motionViewPrevTop);
|
||||
overscrollBy(0, overscroll, 0, mScrollY, 0, 0,
|
||||
0, mOverscrollDistance, true);
|
||||
if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
|
||||
// Don't allow overfling if we're at the edge.
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
mTouchMode = TOUCH_MODE_OVERSCROLL;
|
||||
if (mEdgeGlowTop != null) {
|
||||
if (rawDeltaY > 0) {
|
||||
mEdgeGlowTop.onPull((float) overscroll / getHeight());
|
||||
} else if (rawDeltaY < 0) {
|
||||
mEdgeGlowBottom.onPull((float) overscroll / getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
mMotionY = y;
|
||||
mMotionPosition = motionPosition;
|
||||
invalidate();
|
||||
}
|
||||
mLastY = y;
|
||||
}
|
||||
break;
|
||||
|
||||
case TOUCH_MODE_OVERSCROLL:
|
||||
if (y != mLastY) {
|
||||
final int rawDeltaY = deltaY;
|
||||
deltaY -= mMotionCorrection;
|
||||
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
|
||||
|
||||
final int oldScroll = mScrollY;
|
||||
final int newScroll = oldScroll - incrementalDeltaY;
|
||||
|
||||
if ((oldScroll >= 0 && newScroll <= 0) ||
|
||||
(oldScroll <= 0 && newScroll >= 0)) {
|
||||
// Coming back to 'real' list scrolling
|
||||
incrementalDeltaY = -newScroll;
|
||||
mScrollY = 0;
|
||||
|
||||
// No need to do all this work if we're not going to move anyway
|
||||
if (incrementalDeltaY != 0) {
|
||||
trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
|
||||
}
|
||||
|
||||
// Check to see if we are back in
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
mTouchMode = TOUCH_MODE_SCROLL;
|
||||
|
||||
// We did not scroll the full amount. Treat this essentially like the
|
||||
// start of a new touch scroll
|
||||
final int motionPosition = findClosestMotionRow(y);
|
||||
|
||||
mMotionCorrection = 0;
|
||||
motionView = getChildAt(motionPosition - mFirstPosition);
|
||||
mMotionViewOriginalTop = motionView.getTop();
|
||||
mMotionY = y;
|
||||
mMotionPosition = motionPosition;
|
||||
}
|
||||
} else {
|
||||
overscrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0,
|
||||
0, mOverscrollDistance, true);
|
||||
if (mEdgeGlowTop != null) {
|
||||
if (rawDeltaY > 0) {
|
||||
mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight());
|
||||
} else if (rawDeltaY < 0) {
|
||||
mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight());
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
|
||||
// Don't allow overfling if we're at the edge.
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
mLastY = y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2162,18 +2346,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
case TOUCH_MODE_SCROLL:
|
||||
final int childCount = getChildCount();
|
||||
if (childCount > 0) {
|
||||
if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top &&
|
||||
final int firstChildTop = getChildAt(0).getTop();
|
||||
final int lastChildBottom = getChildAt(childCount - 1).getBottom();
|
||||
final int contentTop = mListPadding.top;
|
||||
final int contentBottom = getHeight() - mListPadding.bottom;
|
||||
if (mFirstPosition == 0 && firstChildTop >= contentTop &&
|
||||
mFirstPosition + childCount < mItemCount &&
|
||||
getChildAt(childCount - 1).getBottom() <=
|
||||
getHeight() - mListPadding.bottom) {
|
||||
lastChildBottom <= getHeight() - contentBottom) {
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
|
||||
} else {
|
||||
final VelocityTracker velocityTracker = mVelocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
|
||||
|
||||
if (Math.abs(initialVelocity) > mMinimumVelocity) {
|
||||
|
||||
// Fling if we have enough velocity and we aren't at a boundary.
|
||||
// Since we can potentially overfling more than we can overscroll, don't
|
||||
// allow the weird behavior where you can scroll to a boundary then
|
||||
// fling further.
|
||||
if (Math.abs(initialVelocity) > mMinimumVelocity &&
|
||||
!((mFirstPosition == 0 &&
|
||||
firstChildTop == contentTop - mOverscrollDistance) ||
|
||||
(mFirstPosition + childCount == mItemCount &&
|
||||
lastChildBottom == contentBottom + mOverscrollDistance))) {
|
||||
if (mFlingRunnable == null) {
|
||||
mFlingRunnable = new FlingRunnable();
|
||||
}
|
||||
@@ -2190,10 +2385,32 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
|
||||
}
|
||||
break;
|
||||
|
||||
case TOUCH_MODE_OVERSCROLL:
|
||||
if (mFlingRunnable == null) {
|
||||
mFlingRunnable = new FlingRunnable();
|
||||
}
|
||||
final VelocityTracker velocityTracker = mVelocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
|
||||
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
|
||||
if (Math.abs(initialVelocity) > mMinimumVelocity) {
|
||||
mFlingRunnable.startOverfling(-initialVelocity);
|
||||
} else {
|
||||
mFlingRunnable.startSpringback();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
setPressed(false);
|
||||
|
||||
if (mEdgeGlowTop != null) {
|
||||
mEdgeGlowTop.onRelease();
|
||||
mEdgeGlowBottom.onRelease();
|
||||
}
|
||||
|
||||
// Need to redraw since we probably aren't drawing the selector anymore
|
||||
invalidate();
|
||||
|
||||
@@ -2219,24 +2436,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
setPressed(false);
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
motionView.setPressed(false);
|
||||
}
|
||||
clearScrollingCache();
|
||||
switch (mTouchMode) {
|
||||
case TOUCH_MODE_OVERSCROLL:
|
||||
if (mFlingRunnable == null) {
|
||||
mFlingRunnable = new FlingRunnable();
|
||||
}
|
||||
mFlingRunnable.startSpringback();
|
||||
break;
|
||||
|
||||
final Handler handler = getHandler();
|
||||
if (handler != null) {
|
||||
handler.removeCallbacks(mPendingCheckForLongPress);
|
||||
}
|
||||
case TOUCH_MODE_OVERFLING:
|
||||
// Do nothing - let it play out.
|
||||
break;
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
default:
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
setPressed(false);
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
motionView.setPressed(false);
|
||||
}
|
||||
clearScrollingCache();
|
||||
|
||||
final Handler handler = getHandler();
|
||||
if (handler != null) {
|
||||
handler.removeCallbacks(mPendingCheckForLongPress);
|
||||
}
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (mEdgeGlowTop != null) {
|
||||
mEdgeGlowTop.onRelease();
|
||||
mEdgeGlowBottom.onRelease();
|
||||
}
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
break;
|
||||
}
|
||||
@@ -2260,11 +2495,61 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOverscrolled(int scrollX, int scrollY,
|
||||
boolean clampedX, boolean clampedY) {
|
||||
mScrollY = scrollY;
|
||||
|
||||
if (clampedY) {
|
||||
// Velocity is broken by hitting the limit; don't start a fling off of this.
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
awakenScrollBars();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
if (mEdgeGlowTop != null) {
|
||||
final int scrollY = mScrollY;
|
||||
if (!mEdgeGlowTop.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
final int width = getWidth();
|
||||
|
||||
canvas.translate(-width / 2, scrollY);
|
||||
mEdgeGlowTop.setSize(width * 2, getHeight());
|
||||
if (mEdgeGlowTop.draw(canvas)) {
|
||||
invalidate();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
if (!mEdgeGlowBottom.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
|
||||
canvas.translate(-width / 2, scrollY + height);
|
||||
canvas.rotate(180, width, 0);
|
||||
mEdgeGlowBottom.setSize(width * 2, height);
|
||||
if (mEdgeGlowBottom.draw(canvas)) {
|
||||
invalidate();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
}
|
||||
if (mFastScroller != null) {
|
||||
mFastScroller.draw(canvas);
|
||||
final int scrollY = mScrollY;
|
||||
if (scrollY != 0) {
|
||||
// Pin to the top/bottom during overscroll
|
||||
int restoreCount = canvas.save();
|
||||
canvas.translate(0, (float) scrollY);
|
||||
mFastScroller.draw(canvas);
|
||||
canvas.restoreToCount(restoreCount);
|
||||
} else {
|
||||
mFastScroller.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2283,6 +2568,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
int touchMode = mTouchMode;
|
||||
if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
|
||||
mMotionCorrection = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
@@ -2347,6 +2636,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mMotionX = (int) ev.getX(newPointerIndex);
|
||||
mMotionY = (int) ev.getY(newPointerIndex);
|
||||
mMotionCorrection = 0;
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
@@ -2402,7 +2692,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
/**
|
||||
* Tracks the decay of a fling scroll
|
||||
*/
|
||||
private final Scroller mScroller;
|
||||
private final OverScroller mScroller;
|
||||
|
||||
/**
|
||||
* Y value reported by mScroller on the previous fling
|
||||
@@ -2410,7 +2700,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
private int mLastFlingY;
|
||||
|
||||
FlingRunnable() {
|
||||
mScroller = new Scroller(getContext());
|
||||
mScroller = new OverScroller(getContext());
|
||||
}
|
||||
|
||||
void start(int initialVelocity) {
|
||||
@@ -2429,6 +2719,40 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
}
|
||||
}
|
||||
|
||||
void startSpringback() {
|
||||
if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
|
||||
mTouchMode = TOUCH_MODE_OVERFLING;
|
||||
invalidate();
|
||||
post(this);
|
||||
} else {
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
}
|
||||
}
|
||||
|
||||
void startOverfling(int initialVelocity) {
|
||||
final int min = mScrollY > 0 ? Integer.MIN_VALUE : 0;
|
||||
final int max = mScrollY > 0 ? 0 : Integer.MAX_VALUE;
|
||||
mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, min, max, 0, getHeight());
|
||||
mTouchMode = TOUCH_MODE_OVERFLING;
|
||||
invalidate();
|
||||
post(this);
|
||||
}
|
||||
|
||||
void edgeReached(int delta) {
|
||||
mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
|
||||
mTouchMode = TOUCH_MODE_OVERFLING;
|
||||
if (mEdgeGlowTop != null) {
|
||||
final int vel = (int) mScroller.getCurrVelocity();
|
||||
if (delta > 0) {
|
||||
mEdgeGlowTop.onAbsorb(vel);
|
||||
} else {
|
||||
mEdgeGlowBottom.onAbsorb(vel);
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
post(this);
|
||||
}
|
||||
|
||||
void startScroll(int distance, int duration) {
|
||||
int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
|
||||
mLastFlingY = initialY;
|
||||
@@ -2461,7 +2785,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
return;
|
||||
}
|
||||
|
||||
final Scroller scroller = mScroller;
|
||||
final OverScroller scroller = mScroller;
|
||||
boolean more = scroller.computeScrollOffset();
|
||||
final int y = scroller.getCurrY();
|
||||
|
||||
@@ -2490,7 +2814,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
|
||||
}
|
||||
|
||||
// Check to see if we have bumped into the scroll limit
|
||||
View motionView = getChildAt(mMotionPosition - mFirstPosition);
|
||||
int oldTop = 0;
|
||||
if (motionView != null) {
|
||||
oldTop = motionView.getTop();
|
||||
}
|
||||
|
||||
final boolean atEnd = trackMotionScroll(delta, delta);
|
||||
if (atEnd) {
|
||||
if (motionView != null) {
|
||||
// Tweak the scroll for how far we overshot
|
||||
int overshoot = -(delta - (motionView.getTop() - oldTop));
|
||||
overscrollBy(0, overshoot, 0, mScrollY, 0, 0,
|
||||
0, mOverflingDistance, false);
|
||||
}
|
||||
edgeReached(delta);
|
||||
break;
|
||||
}
|
||||
|
||||
if (more && !atEnd) {
|
||||
invalidate();
|
||||
@@ -2508,6 +2849,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOUCH_MODE_OVERFLING: {
|
||||
final OverScroller scroller = mScroller;
|
||||
if (scroller.computeScrollOffset()) {
|
||||
final int scrollY = mScrollY;
|
||||
final int deltaY = scroller.getCurrY() - scrollY;
|
||||
if (overscrollBy(0, deltaY, 0, scrollY, 0, 0,
|
||||
0, mOverflingDistance, false)) {
|
||||
startSpringback();
|
||||
} else {
|
||||
invalidate();
|
||||
post(this);
|
||||
}
|
||||
} else {
|
||||
endFling();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2864,16 +3223,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
|
||||
final int firstPosition = mFirstPosition;
|
||||
|
||||
if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
|
||||
if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) {
|
||||
// Don't need to move views down if the top of the first position
|
||||
// is already visible
|
||||
return true;
|
||||
return incrementalDeltaY != 0;
|
||||
}
|
||||
|
||||
if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
|
||||
if (firstPosition + childCount == mItemCount && lastBottom <= end &&
|
||||
incrementalDeltaY <= 0) {
|
||||
// Don't need to move views up if the bottom of the last position
|
||||
// is already visible
|
||||
return true;
|
||||
return incrementalDeltaY != 0;
|
||||
}
|
||||
|
||||
final boolean down = incrementalDeltaY < 0;
|
||||
|
||||
286
core/java/android/widget/EdgeGlow.java
Normal file
286
core/java/android/widget/EdgeGlow.java
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.widget;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* This class performs the glow effect used at the edges of scrollable widgets.
|
||||
* @hide
|
||||
*/
|
||||
public class EdgeGlow {
|
||||
private static final String TAG = "EdgeGlow";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
// Time it will take the effect to fully recede in ms
|
||||
private static final int RECEDE_TIME = 1000;
|
||||
|
||||
// Time it will take before a pulled glow begins receding
|
||||
private static final int PULL_TIME = 250;
|
||||
|
||||
// Time it will take for a pulled glow to decay to partial strength before release
|
||||
private static final int PULL_DECAY_TIME = 1000;
|
||||
|
||||
private static final float HELD_EDGE_ALPHA = 0.7f;
|
||||
private static final float HELD_EDGE_SCALE_Y = 0.5f;
|
||||
private static final float HELD_GLOW_ALPHA = 0.5f;
|
||||
private static final float HELD_GLOW_SCALE_Y = 0.5f;
|
||||
|
||||
private static final float PULL_GLOW_BEGIN = 0.5f;
|
||||
|
||||
// Minimum velocity that will be absorbed
|
||||
private static final int MIN_VELOCITY = 750;
|
||||
|
||||
private static final float EPSILON = 0.001f;
|
||||
|
||||
private Drawable mEdge;
|
||||
private Drawable mGlow;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
|
||||
private float mEdgeAlpha;
|
||||
private float mEdgeScaleY;
|
||||
private float mGlowAlpha;
|
||||
private float mGlowScaleY;
|
||||
|
||||
private float mEdgeAlphaStart;
|
||||
private float mEdgeAlphaFinish;
|
||||
private float mEdgeScaleYStart;
|
||||
private float mEdgeScaleYFinish;
|
||||
private float mGlowAlphaStart;
|
||||
private float mGlowAlphaFinish;
|
||||
private float mGlowScaleYStart;
|
||||
private float mGlowScaleYFinish;
|
||||
|
||||
private long mStartTime;
|
||||
private int mDuration;
|
||||
|
||||
private Interpolator mInterpolator;
|
||||
|
||||
private static final int STATE_IDLE = 0;
|
||||
private static final int STATE_PULL = 1;
|
||||
private static final int STATE_ABSORB = 2;
|
||||
private static final int STATE_RECEDE = 3;
|
||||
private static final int STATE_PULL_DECAY = 4;
|
||||
|
||||
private int mState = STATE_IDLE;
|
||||
|
||||
private float mPullDistance;
|
||||
|
||||
public EdgeGlow(Drawable edge, Drawable glow) {
|
||||
mEdge = edge;
|
||||
mGlow = glow;
|
||||
|
||||
mInterpolator = new DecelerateInterpolator();
|
||||
}
|
||||
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return mState == STATE_IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when the object is pulled by the user.
|
||||
* @param deltaDistance Change in distance since the last call
|
||||
*/
|
||||
public void onPull(float deltaDistance) {
|
||||
final long now = AnimationUtils.currentAnimationTimeMillis();
|
||||
if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
|
||||
return;
|
||||
}
|
||||
if (mState != STATE_PULL) {
|
||||
mGlowScaleY = PULL_GLOW_BEGIN;
|
||||
}
|
||||
mState = STATE_PULL;
|
||||
|
||||
mStartTime = now;
|
||||
mDuration = PULL_TIME;
|
||||
|
||||
mPullDistance += deltaDistance;
|
||||
float distance = Math.abs(mPullDistance);
|
||||
|
||||
mEdgeAlpha = mEdgeAlphaStart = Math.max(HELD_EDGE_ALPHA, Math.min(distance, 1.f));
|
||||
mEdgeScaleY = mEdgeScaleYStart = Math.max(HELD_EDGE_SCALE_Y, Math.min(distance, 2.f));
|
||||
|
||||
mGlowAlpha = mGlowAlphaStart = Math.max(0.5f,
|
||||
Math.min(mGlowAlpha + Math.abs(deltaDistance), 1.f));
|
||||
|
||||
float glowChange = Math.abs(deltaDistance);
|
||||
if (deltaDistance > 0 && mPullDistance < 0) {
|
||||
glowChange = -glowChange;
|
||||
}
|
||||
if (mPullDistance == 0) {
|
||||
mGlowScaleY = 0;
|
||||
}
|
||||
mGlowScaleY = mGlowScaleYStart = Math.max(0, mGlowScaleY + glowChange * 2);
|
||||
|
||||
mEdgeAlphaFinish = mEdgeAlpha;
|
||||
mEdgeScaleYFinish = mEdgeScaleY;
|
||||
mGlowAlphaFinish = mGlowAlpha;
|
||||
mGlowScaleYFinish = mGlowScaleY;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "onPull(" + distance + ", " + deltaDistance + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when the object is released after being pulled.
|
||||
*/
|
||||
public void onRelease() {
|
||||
mPullDistance = 0;
|
||||
|
||||
if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "onRelease");
|
||||
|
||||
mState = STATE_RECEDE;
|
||||
mEdgeAlphaStart = mEdgeAlpha;
|
||||
mEdgeScaleYStart = mEdgeScaleY;
|
||||
mGlowAlphaStart = mGlowAlpha;
|
||||
mGlowScaleYStart = mGlowScaleY;
|
||||
|
||||
mEdgeAlphaFinish = 0.f;
|
||||
mEdgeScaleYFinish = 0.1f;
|
||||
mGlowAlphaFinish = 0.f;
|
||||
mGlowScaleYFinish = 0.1f;
|
||||
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mDuration = RECEDE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when the effect absorbs an impact at the given velocity.
|
||||
* @param velocity Velocity at impact in pixels per second.
|
||||
*/
|
||||
public void onAbsorb(int velocity) {
|
||||
mState = STATE_ABSORB;
|
||||
if (DEBUG) Log.d(TAG, "onAbsorb uncooked velocity: " + velocity);
|
||||
velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
|
||||
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mDuration = (int) (velocity * 0.03f);
|
||||
|
||||
mEdgeAlphaStart = 0.5f;
|
||||
mEdgeScaleYStart = 0.2f;
|
||||
mGlowAlphaStart = 0.5f;
|
||||
mGlowScaleYStart = 0.f;
|
||||
|
||||
mEdgeAlphaFinish = Math.max(0, Math.min(velocity * 0.01f, 1));
|
||||
mEdgeScaleYFinish = 1.f;
|
||||
mGlowAlphaFinish = 1.f;
|
||||
mGlowScaleYFinish = Math.min(velocity * 0.001f, 1);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "onAbsorb(" + velocity + "): duration " + mDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw into the provided canvas.
|
||||
* Assumes that the canvas has been rotated accordingly and the size has been set.
|
||||
* The effect will be drawn the full width of X=0 to X=width, emitting from Y=0 and extending
|
||||
* to some factor < 1.f of height.
|
||||
*
|
||||
* @param canvas Canvas to draw into
|
||||
* @return true if drawing should continue beyond this frame to continue the animation
|
||||
*/
|
||||
public boolean draw(Canvas canvas) {
|
||||
update();
|
||||
|
||||
final int edgeHeight = mEdge.getIntrinsicHeight();
|
||||
final int glowHeight = mGlow.getIntrinsicHeight();
|
||||
|
||||
mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
|
||||
mGlow.setBounds(0, 0, mWidth, (int) (glowHeight * mGlowScaleY));
|
||||
mGlow.draw(canvas);
|
||||
|
||||
mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
|
||||
mEdge.setBounds(0,
|
||||
0,
|
||||
mWidth,
|
||||
(int) (edgeHeight * mEdgeScaleY));
|
||||
mEdge.draw(canvas);
|
||||
if (DEBUG) Log.d(TAG, "draw() glow(" + mGlowAlpha + ", " + mGlowScaleY + ") edge(" + mEdgeAlpha +
|
||||
", " + mEdgeScaleY + ")");
|
||||
|
||||
return mState != STATE_IDLE;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
final long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
final float t = Math.min((float) (time - mStartTime) / mDuration, 1.f);
|
||||
|
||||
final float interp = mInterpolator.getInterpolation(t);
|
||||
|
||||
mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
|
||||
mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
|
||||
mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
|
||||
mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
|
||||
|
||||
if (t >= 1.f - EPSILON) {
|
||||
switch (mState) {
|
||||
case STATE_ABSORB:
|
||||
mState = STATE_RECEDE;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mDuration = RECEDE_TIME;
|
||||
|
||||
mEdgeAlphaStart = mEdgeAlpha;
|
||||
mEdgeScaleYStart = mEdgeScaleY;
|
||||
mGlowAlphaStart = mGlowAlpha;
|
||||
mGlowScaleYStart = mGlowScaleY;
|
||||
|
||||
mEdgeAlphaFinish = 0.f;
|
||||
mEdgeScaleYFinish = 0.1f;
|
||||
mGlowAlphaFinish = 0.f;
|
||||
mGlowScaleYFinish = mGlowScaleY;
|
||||
if (DEBUG) Log.d(TAG, "STATE_ABSORB => STATE_RECEDE");
|
||||
break;
|
||||
case STATE_PULL:
|
||||
mState = STATE_PULL_DECAY;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mDuration = PULL_DECAY_TIME;
|
||||
|
||||
mEdgeAlphaStart = mEdgeAlpha;
|
||||
mEdgeScaleYStart = mEdgeScaleY;
|
||||
mGlowAlphaStart = mGlowAlpha;
|
||||
mGlowScaleYStart = mGlowScaleY;
|
||||
|
||||
mEdgeAlphaFinish = Math.min(mEdgeAlphaStart, HELD_EDGE_ALPHA);
|
||||
mEdgeScaleYFinish = Math.min(mEdgeScaleYStart, HELD_EDGE_SCALE_Y);
|
||||
mGlowAlphaFinish = Math.min(mGlowAlphaStart, HELD_GLOW_ALPHA);
|
||||
mGlowScaleYFinish = Math.min(mGlowScaleY, HELD_GLOW_SCALE_Y);
|
||||
if (DEBUG) Log.d(TAG, "STATE_PULL => STATE_PULL_DECAY");
|
||||
break;
|
||||
case STATE_PULL_DECAY:
|
||||
// Do nothing; wait for release
|
||||
break;
|
||||
case STATE_RECEDE:
|
||||
mState = STATE_IDLE;
|
||||
if (DEBUG) Log.d(TAG, "STATE_RECEDE => STATE_IDLE");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1876,7 +1876,12 @@ public class GridView extends AbsListView {
|
||||
// TODO: Account for vertical spacing too
|
||||
final int numColumns = mNumColumns;
|
||||
final int rowCount = (mItemCount + numColumns - 1) / numColumns;
|
||||
return Math.max(rowCount * 100, 0);
|
||||
int result = Math.max(rowCount * 100, 0);
|
||||
if (mScrollY != 0) {
|
||||
// Compensate for overscroll
|
||||
result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,19 +16,24 @@
|
||||
|
||||
package android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import com.android.internal.R;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.view.FocusFinder;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.FocusFinder;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -63,7 +68,9 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
private long mLastScroll;
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
private Scroller mScroller;
|
||||
private OverScroller mScroller;
|
||||
private EdgeGlow mEdgeGlowLeft;
|
||||
private EdgeGlow mEdgeGlowRight;
|
||||
|
||||
/**
|
||||
* Flag to indicate that we are moving focus ourselves. This is so the
|
||||
@@ -117,6 +124,9 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
private int mMinimumVelocity;
|
||||
private int mMaximumVelocity;
|
||||
|
||||
private int mOverscrollDistance;
|
||||
private int mOverflingDistance;
|
||||
|
||||
/**
|
||||
* ID of the active pointer. This is used to retain consistency during
|
||||
* drags/flings if multiple pointers are used.
|
||||
@@ -189,7 +199,7 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
|
||||
|
||||
private void initScrollView() {
|
||||
mScroller = new Scroller(getContext());
|
||||
mScroller = new OverScroller(getContext());
|
||||
setFocusable(true);
|
||||
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
||||
setWillNotDraw(false);
|
||||
@@ -197,6 +207,8 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
mOverscrollDistance = configuration.getScaledOverscrollDistance();
|
||||
mOverflingDistance = configuration.getScaledOverflingDistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -456,6 +468,9 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
/* Release the drag */
|
||||
mIsBeingDragged = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
|
||||
invalidate();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onSecondaryPointerUp(ev);
|
||||
@@ -513,7 +528,22 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
final int deltaX = (int) (mLastMotionX - x);
|
||||
mLastMotionX = x;
|
||||
|
||||
scrollBy(deltaX, 0);
|
||||
final int oldX = mScrollX;
|
||||
final int oldY = mScrollY;
|
||||
final int range = getScrollRange();
|
||||
if (overscrollBy(deltaX, 0, mScrollX, 0, range, 0,
|
||||
mOverscrollDistance, 0, true)) {
|
||||
// Break our velocity if we hit a scroll barrier.
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
|
||||
|
||||
final int pulledToX = oldX + deltaX;
|
||||
if (pulledToX < 0) {
|
||||
mEdgeGlowLeft.onPull((float) deltaX / getWidth());
|
||||
} else if (pulledToX > range) {
|
||||
mEdgeGlowRight.onPull((float) deltaX / getWidth());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
@@ -522,8 +552,15 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
|
||||
|
||||
if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
|
||||
fling(-initialVelocity);
|
||||
if (getChildCount() > 0) {
|
||||
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
|
||||
fling(-initialVelocity);
|
||||
} else {
|
||||
final int right = getScrollRange();
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0, right, 0, 0)) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
@@ -533,16 +570,27 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
if (mEdgeGlowLeft != null) {
|
||||
mEdgeGlowLeft.onRelease();
|
||||
mEdgeGlowRight.onRelease();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mIsBeingDragged && getChildCount() > 0) {
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
|
||||
invalidate();
|
||||
}
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsBeingDragged = false;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
if (mEdgeGlowLeft != null) {
|
||||
mEdgeGlowLeft.onRelease();
|
||||
mEdgeGlowRight.onRelease();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
@@ -569,6 +617,22 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOverscrolled(int scrollX, int scrollY,
|
||||
boolean clampedX, boolean clampedY) {
|
||||
// Treat animating scrolls differently; see #computeScroll() for why.
|
||||
if (!mScroller.isFinished()) {
|
||||
mScrollX = scrollX;
|
||||
mScrollY = scrollY;
|
||||
if (clampedX) {
|
||||
mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
|
||||
}
|
||||
} else {
|
||||
super.scrollTo(scrollX, scrollY);
|
||||
}
|
||||
awakenScrollBars();
|
||||
}
|
||||
|
||||
private int getScrollRange() {
|
||||
int scrollRange = 0;
|
||||
if (getChildCount() > 0) {
|
||||
@@ -951,7 +1015,16 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
return contentWidth;
|
||||
}
|
||||
|
||||
return getChildAt(0).getRight();
|
||||
int scrollRange = getChildAt(0).getRight();
|
||||
final int scrollX = mScrollX;
|
||||
final int overscrollRight = Math.max(0, scrollRange - contentWidth);
|
||||
if (scrollX < 0) {
|
||||
scrollRange -= scrollX;
|
||||
} else if (scrollX > overscrollRight) {
|
||||
scrollRange += scrollX - overscrollRight;
|
||||
}
|
||||
|
||||
return scrollRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1012,14 +1085,16 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
int x = mScroller.getCurrX();
|
||||
int y = mScroller.getCurrY();
|
||||
|
||||
if (getChildCount() > 0) {
|
||||
View child = getChildAt(0);
|
||||
x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
|
||||
y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
|
||||
if (x != oldX || y != oldY) {
|
||||
mScrollX = x;
|
||||
mScrollY = y;
|
||||
onScrollChanged(x, y, oldX, oldY);
|
||||
if (oldX != x || oldY != y) {
|
||||
overscrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0,
|
||||
mOverflingDistance, 0, false);
|
||||
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
|
||||
|
||||
final int range = getScrollRange();
|
||||
if (x < 0 && oldX >= 0) {
|
||||
mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
|
||||
} else if (x > range && oldX <= range) {
|
||||
mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
|
||||
}
|
||||
}
|
||||
awakenScrollBars();
|
||||
@@ -1256,7 +1331,7 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
int right = getChildAt(0).getWidth();
|
||||
|
||||
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
|
||||
Math.max(0, right - width), 0, 0);
|
||||
Math.max(0, right - width), 0, 0, width/2, 0);
|
||||
|
||||
final boolean movingRight = velocityX > 0;
|
||||
|
||||
@@ -1294,6 +1369,56 @@ public class HorizontalScrollView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverscrollMode(int mode) {
|
||||
if (mode != OVERSCROLL_NEVER) {
|
||||
if (mEdgeGlowLeft == null) {
|
||||
final Resources res = getContext().getResources();
|
||||
final Drawable edge = res.getDrawable(R.drawable.edge_light);
|
||||
final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
|
||||
mEdgeGlowLeft = new EdgeGlow(edge, glow);
|
||||
mEdgeGlowRight = new EdgeGlow(edge, glow);
|
||||
}
|
||||
} else {
|
||||
mEdgeGlowLeft = null;
|
||||
mEdgeGlowRight = null;
|
||||
}
|
||||
super.setOverscrollMode(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
if (mEdgeGlowLeft != null) {
|
||||
final int scrollX = mScrollX;
|
||||
if (!mEdgeGlowLeft.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
final int height = getHeight();
|
||||
|
||||
canvas.rotate(270);
|
||||
canvas.translate(-height * 1.5f, scrollX);
|
||||
mEdgeGlowLeft.setSize(getHeight() * 2, getWidth());
|
||||
if (mEdgeGlowLeft.draw(canvas)) {
|
||||
invalidate();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
if (!mEdgeGlowRight.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
|
||||
canvas.rotate(90);
|
||||
canvas.translate(-height / 2, -scrollX - width);
|
||||
mEdgeGlowRight.setSize(height * 2, width);
|
||||
if (mEdgeGlowRight.draw(canvas)) {
|
||||
invalidate();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int clamp(int n, int my, int child) {
|
||||
if (my >= child || n < 0) {
|
||||
return 0;
|
||||
|
||||
@@ -121,6 +121,9 @@ public class ListView extends AbsListView {
|
||||
Drawable mDivider;
|
||||
int mDividerHeight;
|
||||
|
||||
Drawable mOverscrollHeader;
|
||||
Drawable mOverscrollFooter;
|
||||
|
||||
private boolean mIsCacheColorOpaque;
|
||||
private boolean mDividerIsOpaque;
|
||||
private boolean mClipDivider;
|
||||
@@ -175,6 +178,16 @@ public class ListView extends AbsListView {
|
||||
setDivider(d);
|
||||
}
|
||||
|
||||
final Drawable osHeader = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollHeader);
|
||||
if (osHeader != null) {
|
||||
setOverscrollHeader(osHeader);
|
||||
}
|
||||
|
||||
final Drawable osFooter = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollFooter);
|
||||
if (osFooter != null) {
|
||||
setOverscrollFooter(osFooter);
|
||||
}
|
||||
|
||||
// Use the height specified, zero being the default
|
||||
final int dividerHeight = a.getDimensionPixelSize(
|
||||
com.android.internal.R.styleable.ListView_dividerHeight, 0);
|
||||
@@ -2945,14 +2958,52 @@ public class ListView extends AbsListView {
|
||||
}
|
||||
super.setCacheColorHint(color);
|
||||
}
|
||||
|
||||
|
||||
void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
|
||||
final int height = drawable.getMinimumHeight();
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRect(bounds);
|
||||
|
||||
final int span = bounds.bottom - bounds.top;
|
||||
if (span < height) {
|
||||
bounds.top = bounds.bottom - height;
|
||||
}
|
||||
|
||||
drawable.setBounds(bounds);
|
||||
drawable.draw(canvas);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
|
||||
final int height = drawable.getMinimumHeight();
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRect(bounds);
|
||||
|
||||
final int span = bounds.bottom - bounds.top;
|
||||
if (span < height) {
|
||||
bounds.bottom = bounds.top + height;
|
||||
}
|
||||
|
||||
drawable.setBounds(bounds);
|
||||
drawable.draw(canvas);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
// Draw the dividers
|
||||
final int dividerHeight = mDividerHeight;
|
||||
final Drawable overscrollHeader = mOverscrollHeader;
|
||||
final Drawable overscrollFooter = mOverscrollFooter;
|
||||
final boolean drawOverscrollHeader = overscrollHeader != null;
|
||||
final boolean drawOverscrollFooter = overscrollFooter != null;
|
||||
final boolean drawDividers = dividerHeight > 0 && mDivider != null;
|
||||
|
||||
if (drawDividers) {
|
||||
if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
|
||||
// Only modify the top and bottom in the loop, we set the left and right here
|
||||
final Rect bounds = mTempRect;
|
||||
bounds.left = mPaddingLeft;
|
||||
@@ -2983,14 +3034,28 @@ public class ListView extends AbsListView {
|
||||
if (!mStackFromBottom) {
|
||||
int bottom = 0;
|
||||
|
||||
// Draw top divider or header for overscroll
|
||||
final int scrollY = mScrollY;
|
||||
if (count > 0 && scrollY < 0) {
|
||||
if (drawOverscrollHeader) {
|
||||
bounds.bottom = 0;
|
||||
bounds.top = scrollY;
|
||||
drawOverscrollHeader(canvas, overscrollHeader, bounds);
|
||||
} else if (drawDividers) {
|
||||
bounds.bottom = 0;
|
||||
bounds.top = -dividerHeight;
|
||||
drawDivider(canvas, bounds, -1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if ((headerDividers || first + i >= headerCount) &&
|
||||
(footerDividers || first + i < footerLimit)) {
|
||||
View child = getChildAt(i);
|
||||
bottom = child.getBottom();
|
||||
// Don't draw dividers next to items that are not enabled
|
||||
if (drawDividers) {
|
||||
if (drawDividers &&
|
||||
(bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
|
||||
if ((areAllItemsSelectable ||
|
||||
(adapter.isEnabled(first + i) && (i == count - 1 ||
|
||||
adapter.isEnabled(first + i + 1))))) {
|
||||
@@ -3005,13 +3070,28 @@ public class ListView extends AbsListView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final int overFooterBottom = mBottom + mScrollY;
|
||||
if (drawOverscrollFooter && first + count == itemCount &&
|
||||
overFooterBottom > bottom) {
|
||||
bounds.top = bottom;
|
||||
bounds.bottom = overFooterBottom;
|
||||
drawOverscrollFooter(canvas, overscrollFooter, bounds);
|
||||
}
|
||||
} else {
|
||||
int top;
|
||||
int listTop = mListPadding.top;
|
||||
|
||||
final int scrollY = mScrollY;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (count > 0 && drawOverscrollHeader) {
|
||||
bounds.top = scrollY;
|
||||
bounds.bottom = getChildAt(0).getTop();
|
||||
drawOverscrollHeader(canvas, overscrollHeader, bounds);
|
||||
}
|
||||
|
||||
final int start = drawOverscrollHeader ? 1 : 0;
|
||||
for (int i = start; i < count; i++) {
|
||||
if ((headerDividers || first + i >= headerCount) &&
|
||||
(footerDividers || first + i < footerLimit)) {
|
||||
View child = getChildAt(i);
|
||||
@@ -3037,10 +3117,17 @@ public class ListView extends AbsListView {
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0 && scrollY > 0 && drawDividers) {
|
||||
bounds.top = listBottom;
|
||||
bounds.bottom = listBottom + dividerHeight;
|
||||
drawDivider(canvas, bounds, -1);
|
||||
if (count > 0 && scrollY > 0) {
|
||||
if (drawOverscrollFooter) {
|
||||
final int absListBottom = mBottom;
|
||||
bounds.top = absListBottom;
|
||||
bounds.bottom = absListBottom + scrollY;
|
||||
drawOverscrollFooter(canvas, overscrollFooter, bounds);
|
||||
} else if (drawDividers) {
|
||||
bounds.top = listBottom;
|
||||
bounds.bottom = listBottom + dividerHeight;
|
||||
drawDivider(canvas, bounds, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3149,6 +3236,45 @@ public class ListView extends AbsListView {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the drawable that will be drawn above all other list content.
|
||||
* This area can become visible when the user overscrolls the list.
|
||||
*
|
||||
* @param header The drawable to use
|
||||
*/
|
||||
public void setOverscrollHeader(Drawable header) {
|
||||
mOverscrollHeader = header;
|
||||
if (mScrollY < 0) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The drawable that will be drawn above all other list content
|
||||
*/
|
||||
public Drawable getOverscrollHeader() {
|
||||
return mOverscrollHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the drawable that will be drawn below all other list content.
|
||||
* This area can become visible when the user overscrolls the list,
|
||||
* or when the list's content does not fully fill the container area.
|
||||
*
|
||||
* @param footer The drawable to use
|
||||
*/
|
||||
public void setOverscrollFooter(Drawable footer) {
|
||||
mOverscrollFooter = footer;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The drawable that will be drawn below all other list content
|
||||
*/
|
||||
public Drawable getOverscrollFooter() {
|
||||
return mOverscrollFooter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
|
||||
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
|
||||
|
||||
849
core/java/android/widget/OverScroller.java
Normal file
849
core/java/android/widget/OverScroller.java
Normal file
@@ -0,0 +1,849 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.util.FloatMath;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* This class encapsulates scrolling with the ability to overshoot the bounds
|
||||
* of a scrolling operation. This class is a drop-in replacement for
|
||||
* {@link android.widget.Scroller} in most cases.
|
||||
*/
|
||||
public class OverScroller {
|
||||
private int mMode;
|
||||
|
||||
private MagneticOverScroller mScrollerX;
|
||||
private MagneticOverScroller mScrollerY;
|
||||
|
||||
private final Interpolator mInterpolator;
|
||||
|
||||
private static final int DEFAULT_DURATION = 250;
|
||||
private static final int SCROLL_MODE = 0;
|
||||
private static final int FLING_MODE = 1;
|
||||
|
||||
/**
|
||||
* Creates an OverScroller with a viscous fluid scroll interpolator.
|
||||
* @param context
|
||||
*/
|
||||
public OverScroller(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an OverScroller with default edge bounce coefficients.
|
||||
* @param context The context of this application.
|
||||
* @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
|
||||
* be used.
|
||||
*/
|
||||
public OverScroller(Context context, Interpolator interpolator) {
|
||||
this(context, interpolator, MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT,
|
||||
MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an OverScroller.
|
||||
* @param context The context of this application.
|
||||
* @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
|
||||
* be used.
|
||||
* @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
|
||||
* velocity which is preserved in the bounce when the horizontal edge is reached. A null value
|
||||
* means no bounce.
|
||||
* @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
|
||||
*/
|
||||
public OverScroller(Context context, Interpolator interpolator,
|
||||
float bounceCoefficientX, float bounceCoefficientY) {
|
||||
mInterpolator = interpolator;
|
||||
mScrollerX = new MagneticOverScroller();
|
||||
mScrollerY = new MagneticOverScroller();
|
||||
MagneticOverScroller.initializeFromContext(context);
|
||||
|
||||
mScrollerX.setBounceCoefficient(bounceCoefficientX);
|
||||
mScrollerY.setBounceCoefficient(bounceCoefficientY);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns whether the scroller has finished scrolling.
|
||||
*
|
||||
* @return True if the scroller has finished scrolling, false otherwise.
|
||||
*/
|
||||
public final boolean isFinished() {
|
||||
return mScrollerX.mFinished && mScrollerY.mFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the finished field to a particular value. Contrary to
|
||||
* {@link #abortAnimation()}, forcing the animation to finished
|
||||
* does NOT cause the scroller to move to the final x and y
|
||||
* position.
|
||||
*
|
||||
* @param finished The new finished value.
|
||||
*/
|
||||
public final void forceFinished(boolean finished) {
|
||||
mScrollerX.mFinished = mScrollerY.mFinished = finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current X offset in the scroll.
|
||||
*
|
||||
* @return The new X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getCurrX() {
|
||||
return mScrollerX.mCurrentPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Y offset in the scroll.
|
||||
*
|
||||
* @return The new Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getCurrY() {
|
||||
return mScrollerY.mCurrentPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Returns the current velocity.
|
||||
*
|
||||
* @return The original velocity less the deceleration, norm of the X and Y velocity vector.
|
||||
*/
|
||||
public float getCurrVelocity() {
|
||||
float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
|
||||
squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
|
||||
return FloatMath.sqrt(squaredNorm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start X offset in the scroll.
|
||||
*
|
||||
* @return The start X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getStartX() {
|
||||
return mScrollerX.mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start Y offset in the scroll.
|
||||
*
|
||||
* @return The start Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getStartY() {
|
||||
return mScrollerY.mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getFinalX() {
|
||||
return mScrollerX.mFinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getFinalY() {
|
||||
return mScrollerY.mFinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how long the scroll event will take, in milliseconds.
|
||||
*
|
||||
* @return The duration of the scroll in milliseconds.
|
||||
*
|
||||
* @hide Pending removal once nothing depends on it
|
||||
* @deprecated OverScrollers don't necessarily have a fixed duration.
|
||||
* This function will lie to the best of its ability.
|
||||
*/
|
||||
public final int getDuration() {
|
||||
return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the scroll animation. This allows a running animation to scroll
|
||||
* further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
|
||||
*
|
||||
* @param extend Additional time to scroll in milliseconds.
|
||||
* @see #setFinalX(int)
|
||||
* @see #setFinalY(int)
|
||||
*
|
||||
* @hide Pending removal once nothing depends on it
|
||||
* @deprecated OverScrollers don't necessarily have a fixed duration.
|
||||
* Instead of setting a new final position and extending
|
||||
* the duration of an existing scroll, use startScroll
|
||||
* to begin a new animation.
|
||||
*/
|
||||
public void extendDuration(int extend) {
|
||||
mScrollerX.extendDuration(extend);
|
||||
mScrollerY.extendDuration(extend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the final position (X) for this scroller.
|
||||
*
|
||||
* @param newX The new X offset as an absolute distance from the origin.
|
||||
* @see #extendDuration(int)
|
||||
* @see #setFinalY(int)
|
||||
*
|
||||
* @hide Pending removal once nothing depends on it
|
||||
* @deprecated OverScroller's final position may change during an animation.
|
||||
* Instead of setting a new final position and extending
|
||||
* the duration of an existing scroll, use startScroll
|
||||
* to begin a new animation.
|
||||
*/
|
||||
public void setFinalX(int newX) {
|
||||
mScrollerX.setFinalPosition(newX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the final position (Y) for this scroller.
|
||||
*
|
||||
* @param newY The new Y offset as an absolute distance from the origin.
|
||||
* @see #extendDuration(int)
|
||||
* @see #setFinalX(int)
|
||||
*
|
||||
* @hide Pending removal once nothing depends on it
|
||||
* @deprecated OverScroller's final position may change during an animation.
|
||||
* Instead of setting a new final position and extending
|
||||
* the duration of an existing scroll, use startScroll
|
||||
* to begin a new animation.
|
||||
*/
|
||||
public void setFinalY(int newY) {
|
||||
mScrollerY.setFinalPosition(newY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to know the new location. If it returns true, the
|
||||
* animation is not yet finished.
|
||||
*/
|
||||
public boolean computeScrollOffset() {
|
||||
if (isFinished()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (mMode) {
|
||||
case SCROLL_MODE:
|
||||
long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
// Any scroller can be used for time, since they were started
|
||||
// together in scroll mode. We use X here.
|
||||
final long elapsedTime = time - mScrollerX.mStartTime;
|
||||
|
||||
final int duration = mScrollerX.mDuration;
|
||||
if (elapsedTime < duration) {
|
||||
float q = (float) (elapsedTime) / duration;
|
||||
|
||||
if (mInterpolator == null)
|
||||
q = Scroller.viscousFluid(q);
|
||||
else
|
||||
q = mInterpolator.getInterpolation(q);
|
||||
|
||||
mScrollerX.updateScroll(q);
|
||||
mScrollerY.updateScroll(q);
|
||||
} else {
|
||||
abortAnimation();
|
||||
}
|
||||
break;
|
||||
|
||||
case FLING_MODE:
|
||||
if (!mScrollerX.mFinished) {
|
||||
if (!mScrollerX.update()) {
|
||||
if (!mScrollerX.continueWhenFinished()) {
|
||||
mScrollerX.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mScrollerY.mFinished) {
|
||||
if (!mScrollerY.update()) {
|
||||
if (!mScrollerY.continueWhenFinished()) {
|
||||
mScrollerY.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling by providing a starting point and the distance to travel.
|
||||
* The scroll will use the default value of 250 milliseconds for the
|
||||
* duration.
|
||||
*
|
||||
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||
* numbers will scroll the content to the left.
|
||||
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||
* will scroll the content up.
|
||||
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||
* content to the left.
|
||||
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||
* content up.
|
||||
*/
|
||||
public void startScroll(int startX, int startY, int dx, int dy) {
|
||||
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling by providing a starting point and the distance to travel.
|
||||
*
|
||||
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||
* numbers will scroll the content to the left.
|
||||
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||
* will scroll the content up.
|
||||
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||
* content to the left.
|
||||
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||
* content up.
|
||||
* @param duration Duration of the scroll in milliseconds.
|
||||
*/
|
||||
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
|
||||
mMode = SCROLL_MODE;
|
||||
mScrollerX.startScroll(startX, dx, duration);
|
||||
mScrollerY.startScroll(startY, dy, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to 'spring back' into a valid coordinate range.
|
||||
*
|
||||
* @param startX Starting X coordinate
|
||||
* @param startY Starting Y coordinate
|
||||
* @param minX Minimum valid X value
|
||||
* @param maxX Maximum valid X value
|
||||
* @param minY Minimum valid Y value
|
||||
* @param maxY Minimum valid Y value
|
||||
* @return true if a springback was initiated, false if startX and startY were
|
||||
* already within the valid range.
|
||||
*/
|
||||
public boolean springback(int startX, int startY, int minX, int maxX, int minY, int maxY) {
|
||||
mMode = FLING_MODE;
|
||||
|
||||
// Make sure both methods are called.
|
||||
final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
|
||||
final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
|
||||
return spingbackX || spingbackY;
|
||||
}
|
||||
|
||||
public void fling(int startX, int startY, int velocityX, int velocityY,
|
||||
int minX, int maxX, int minY, int maxY) {
|
||||
fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling based on a fling gesture. The distance traveled will
|
||||
* depend on the initial velocity of the fling.
|
||||
*
|
||||
* @param startX Starting point of the scroll (X)
|
||||
* @param startY Starting point of the scroll (Y)
|
||||
* @param velocityX Initial velocity of the fling (X) measured in pixels per
|
||||
* second.
|
||||
* @param velocityY Initial velocity of the fling (Y) measured in pixels per
|
||||
* second
|
||||
* @param minX Minimum X value. The scroller will not scroll past this point
|
||||
* unless overX > 0. If overfling is allowed, it will use minX as
|
||||
* a springback boundary.
|
||||
* @param maxX Maximum X value. The scroller will not scroll past this point
|
||||
* unless overX > 0. If overfling is allowed, it will use maxX as
|
||||
* a springback boundary.
|
||||
* @param minY Minimum Y value. The scroller will not scroll past this point
|
||||
* unless overY > 0. If overfling is allowed, it will use minY as
|
||||
* a springback boundary.
|
||||
* @param maxY Maximum Y value. The scroller will not scroll past this point
|
||||
* unless overY > 0. If overfling is allowed, it will use maxY as
|
||||
* a springback boundary.
|
||||
* @param overX Overfling range. If > 0, horizontal overfling in either
|
||||
* direction will be possible.
|
||||
* @param overY Overfling range. If > 0, vertical overfling in either
|
||||
* direction will be possible.
|
||||
*/
|
||||
public void fling(int startX, int startY, int velocityX, int velocityY,
|
||||
int minX, int maxX, int minY, int maxY, int overX, int overY) {
|
||||
mMode = FLING_MODE;
|
||||
mScrollerX.fling(startX, velocityX, minX, maxX, overX);
|
||||
mScrollerY.fling(startY, velocityY, minY, maxY, overY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the scroller that we've reached a horizontal boundary.
|
||||
* Normally the information to handle this will already be known
|
||||
* when the animation is started, such as in a call to one of the
|
||||
* fling functions. However there are cases where this cannot be known
|
||||
* in advance. This function will transition the current motion and
|
||||
* animate from startX to finalX as appropriate.
|
||||
*
|
||||
* @param startX Starting/current X position
|
||||
* @param finalX Desired final X position
|
||||
* @param overX Magnitude of overscroll allowed. This should be the maximum
|
||||
* desired distance from finalX. Absolute value - must be positive.
|
||||
*/
|
||||
public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
|
||||
mScrollerX.notifyEdgeReached(startX, finalX, overX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the scroller that we've reached a vertical boundary.
|
||||
* Normally the information to handle this will already be known
|
||||
* when the animation is started, such as in a call to one of the
|
||||
* fling functions. However there are cases where this cannot be known
|
||||
* in advance. This function will animate a parabolic motion from
|
||||
* startY to finalY.
|
||||
*
|
||||
* @param startY Starting/current Y position
|
||||
* @param finalY Desired final Y position
|
||||
* @param overY Magnitude of overscroll allowed. This should be the maximum
|
||||
* desired distance from finalY.
|
||||
*/
|
||||
public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
|
||||
mScrollerY.notifyEdgeReached(startY, finalY, overY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current Scroller is currently returning to a valid position.
|
||||
* Valid bounds were provided by the
|
||||
* {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
|
||||
*
|
||||
* One should check this value before calling
|
||||
* {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
|
||||
* to restore a valid position will then be stopped. The caller has to take into account
|
||||
* the fact that the started scroll will start from an overscrolled position.
|
||||
*
|
||||
* @return true when the current position is overscrolled and in the process of
|
||||
* interpolating back to a valid value.
|
||||
*/
|
||||
public boolean isOverscrolled() {
|
||||
return ((!mScrollerX.mFinished &&
|
||||
mScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
|
||||
(!mScrollerY.mFinished &&
|
||||
mScrollerY.mState != MagneticOverScroller.TO_EDGE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
|
||||
* aborting the animating causes the scroller to move to the final x and y
|
||||
* positions.
|
||||
*
|
||||
* @see #forceFinished(boolean)
|
||||
*/
|
||||
public void abortAnimation() {
|
||||
mScrollerX.finish();
|
||||
mScrollerY.finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed since the beginning of the scrolling.
|
||||
*
|
||||
* @return The elapsed time in milliseconds.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int timePassed() {
|
||||
final long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
|
||||
return (int) (time - startTime);
|
||||
}
|
||||
|
||||
static class MagneticOverScroller {
|
||||
// Initial position
|
||||
int mStart;
|
||||
|
||||
// Current position
|
||||
int mCurrentPosition;
|
||||
|
||||
// Final position
|
||||
int mFinal;
|
||||
|
||||
// Initial velocity
|
||||
int mVelocity;
|
||||
|
||||
// Current velocity
|
||||
float mCurrVelocity;
|
||||
|
||||
// Constant current deceleration
|
||||
float mDeceleration;
|
||||
|
||||
// Animation starting time, in system milliseconds
|
||||
long mStartTime;
|
||||
|
||||
// Animation duration, in milliseconds
|
||||
int mDuration;
|
||||
|
||||
// Whether the animation is currently in progress
|
||||
boolean mFinished;
|
||||
|
||||
// Constant gravity value, used to scale deceleration
|
||||
static float GRAVITY;
|
||||
|
||||
static void initializeFromContext(Context context) {
|
||||
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
|
||||
GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2)
|
||||
* 39.37f // inch/meter
|
||||
* ppi // pixels per inch
|
||||
* ViewConfiguration.getScrollFriction();
|
||||
}
|
||||
|
||||
private static final int TO_EDGE = 0;
|
||||
private static final int TO_BOUNDARY = 1;
|
||||
private static final int TO_BOUNCE = 2;
|
||||
|
||||
private int mState = TO_EDGE;
|
||||
|
||||
// The allowed overshot distance before boundary is reached.
|
||||
private int mOver;
|
||||
|
||||
// Duration in milliseconds to go back from edge to edge. Springback is half of it.
|
||||
private static final int OVERSCROLL_SPRINGBACK_DURATION = 200;
|
||||
|
||||
// Oscillation period
|
||||
private static final float TIME_COEF =
|
||||
1000.0f * (float) Math.PI / OVERSCROLL_SPRINGBACK_DURATION;
|
||||
|
||||
// If the velocity is smaller than this value, no bounce is triggered
|
||||
// when the edge limits are reached (would result in a zero pixels
|
||||
// displacement anyway).
|
||||
private static final float MINIMUM_VELOCITY_FOR_BOUNCE = 140.0f;
|
||||
|
||||
// Proportion of the velocity that is preserved when the edge is reached.
|
||||
private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.16f;
|
||||
|
||||
private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
|
||||
|
||||
MagneticOverScroller() {
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
void updateScroll(float q) {
|
||||
mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a signed deceleration that will reduce the velocity.
|
||||
*/
|
||||
static float getDeceleration(int velocity) {
|
||||
return velocity > 0 ? -GRAVITY : GRAVITY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the time (in milliseconds) it will take to go from start to end.
|
||||
*/
|
||||
static int computeDuration(int start, int end, float initialVelocity, float deceleration) {
|
||||
final int distance = start - end;
|
||||
final float discriminant = initialVelocity * initialVelocity - 2.0f * deceleration
|
||||
* distance;
|
||||
if (discriminant >= 0.0f) {
|
||||
float delta = (float) Math.sqrt(discriminant);
|
||||
if (deceleration < 0.0f) {
|
||||
delta = -delta;
|
||||
}
|
||||
return (int) (1000.0f * (-initialVelocity - delta) / deceleration);
|
||||
}
|
||||
|
||||
// End position can not be reached
|
||||
return 0;
|
||||
}
|
||||
|
||||
void startScroll(int start, int distance, int duration) {
|
||||
mFinished = false;
|
||||
|
||||
mStart = start;
|
||||
mFinal = start + distance;
|
||||
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mDuration = duration;
|
||||
|
||||
// Unused
|
||||
mDeceleration = 0.0f;
|
||||
mVelocity = 0;
|
||||
}
|
||||
|
||||
void fling(int start, int velocity, int min, int max) {
|
||||
mFinished = false;
|
||||
|
||||
mStart = start;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
|
||||
mVelocity = velocity;
|
||||
|
||||
mDeceleration = getDeceleration(velocity);
|
||||
|
||||
// A start from an invalid position immediately brings back to a valid position
|
||||
if (mStart < min) {
|
||||
mDuration = 0;
|
||||
mFinal = min;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mStart > max) {
|
||||
mDuration = 0;
|
||||
mFinal = max;
|
||||
return;
|
||||
}
|
||||
|
||||
// Duration are expressed in milliseconds
|
||||
mDuration = (int) (-1000.0f * velocity / mDeceleration);
|
||||
|
||||
mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
|
||||
|
||||
// Clamp to a valid final position
|
||||
if (mFinal < min) {
|
||||
mFinal = min;
|
||||
mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
|
||||
}
|
||||
|
||||
if (mFinal > max) {
|
||||
mFinal = max;
|
||||
mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
|
||||
}
|
||||
}
|
||||
|
||||
void finish() {
|
||||
mCurrentPosition = mFinal;
|
||||
// Not reset since WebView relies on this value for fast fling.
|
||||
// mCurrVelocity = 0.0f;
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
void setFinalPosition(int position) {
|
||||
mFinal = position;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
void extendDuration(int extend) {
|
||||
final long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
final int elapsedTime = (int) (time - mStartTime);
|
||||
mDuration = elapsedTime + extend;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
void setBounceCoefficient(float coefficient) {
|
||||
mBounceCoefficient = coefficient;
|
||||
}
|
||||
|
||||
boolean springback(int start, int min, int max) {
|
||||
mFinished = true;
|
||||
|
||||
mStart = start;
|
||||
mVelocity = 0;
|
||||
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mDuration = 0;
|
||||
|
||||
if (start < min) {
|
||||
startSpringback(start, min, false);
|
||||
} else if (start > max) {
|
||||
startSpringback(start, max, true);
|
||||
}
|
||||
|
||||
return !mFinished;
|
||||
}
|
||||
|
||||
private void startSpringback(int start, int end, boolean positive) {
|
||||
mFinished = false;
|
||||
mState = TO_BOUNCE;
|
||||
mStart = mFinal = end;
|
||||
mDuration = OVERSCROLL_SPRINGBACK_DURATION;
|
||||
mStartTime -= OVERSCROLL_SPRINGBACK_DURATION / 2;
|
||||
mVelocity = (int) (Math.abs(end - start) * TIME_COEF * (positive ? 1.0 : -1.0f));
|
||||
}
|
||||
|
||||
void fling(int start, int velocity, int min, int max, int over) {
|
||||
mState = TO_EDGE;
|
||||
mOver = over;
|
||||
|
||||
mFinished = false;
|
||||
|
||||
mStart = start;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
|
||||
mVelocity = velocity;
|
||||
|
||||
mDeceleration = getDeceleration(velocity);
|
||||
|
||||
// Duration are expressed in milliseconds
|
||||
mDuration = (int) (-1000.0f * velocity / mDeceleration);
|
||||
|
||||
mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
|
||||
|
||||
// Clamp to a valid final position
|
||||
if (mFinal < min) {
|
||||
mFinal = min;
|
||||
mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
|
||||
}
|
||||
|
||||
if (mFinal > max) {
|
||||
mFinal = max;
|
||||
mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
|
||||
}
|
||||
|
||||
if (start > max) {
|
||||
if (start >= max + over) {
|
||||
springback(max + over, min, max);
|
||||
} else {
|
||||
if (velocity <= 0) {
|
||||
springback(start, min, max);
|
||||
} else {
|
||||
long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
final double durationSinceEdge =
|
||||
Math.atan((start-max) * TIME_COEF / velocity) / TIME_COEF;
|
||||
mStartTime = (int) (time - 1000.0f * durationSinceEdge);
|
||||
|
||||
// Simulate a bounce that started from edge
|
||||
mStart = max;
|
||||
|
||||
mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
|
||||
|
||||
onEdgeReached();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (start < min) {
|
||||
if (start <= min - over) {
|
||||
springback(min - over, min, max);
|
||||
} else {
|
||||
if (velocity >= 0) {
|
||||
springback(start, min, max);
|
||||
} else {
|
||||
long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
final double durationSinceEdge =
|
||||
Math.atan((start-min) * TIME_COEF / velocity) / TIME_COEF;
|
||||
mStartTime = (int) (time - 1000.0f * durationSinceEdge);
|
||||
|
||||
// Simulate a bounce that started from edge
|
||||
mStart = min;
|
||||
|
||||
mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
|
||||
|
||||
onEdgeReached();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void notifyEdgeReached(int start, int end, int over) {
|
||||
mDeceleration = getDeceleration(mVelocity);
|
||||
|
||||
// Local time, used to compute edge crossing time.
|
||||
float timeCurrent = mCurrVelocity / mDeceleration;
|
||||
final int distance = end - start;
|
||||
float timeEdge = -(float) Math.sqrt((2.0f * distance / mDeceleration)
|
||||
+ (timeCurrent * timeCurrent));
|
||||
|
||||
mVelocity = (int) (mDeceleration * timeEdge);
|
||||
|
||||
// Simulate a symmetric bounce that started from edge
|
||||
mStart = end;
|
||||
|
||||
mOver = over;
|
||||
|
||||
long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
mStartTime = (int) (time - 1000.0f * (timeCurrent - timeEdge));
|
||||
|
||||
onEdgeReached();
|
||||
}
|
||||
|
||||
private void onEdgeReached() {
|
||||
// mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
|
||||
final float distance = mVelocity / TIME_COEF;
|
||||
|
||||
if (Math.abs(distance) < mOver) {
|
||||
// Spring force will bring us back to final position
|
||||
mState = TO_BOUNCE;
|
||||
mFinal = mStart;
|
||||
mDuration = OVERSCROLL_SPRINGBACK_DURATION;
|
||||
} else {
|
||||
// Velocity is too high, we will hit the boundary limit
|
||||
mState = TO_BOUNDARY;
|
||||
int over = mVelocity > 0 ? mOver : -mOver;
|
||||
mFinal = mStart + over;
|
||||
mDuration = (int) (1000.0f * Math.asin(over / distance) / TIME_COEF);
|
||||
}
|
||||
}
|
||||
|
||||
boolean continueWhenFinished() {
|
||||
switch (mState) {
|
||||
case TO_EDGE:
|
||||
// Duration from start to null velocity
|
||||
int duration = (int) (-1000.0f * mVelocity / mDeceleration);
|
||||
if (mDuration < duration) {
|
||||
// If the animation was clamped, we reached the edge
|
||||
mStart = mFinal;
|
||||
// Speed when edge was reached
|
||||
mVelocity = (int) (mVelocity + mDeceleration * mDuration / 1000.0f);
|
||||
mStartTime += mDuration;
|
||||
onEdgeReached();
|
||||
} else {
|
||||
// Normal stop, no need to continue
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case TO_BOUNDARY:
|
||||
mStartTime += mDuration;
|
||||
startSpringback(mFinal, mFinal - (mVelocity > 0 ? mOver:-mOver), mVelocity > 0);
|
||||
break;
|
||||
case TO_BOUNCE:
|
||||
//mVelocity = (int) (mVelocity * BOUNCE_COEFFICIENT);
|
||||
mVelocity = (int) (mVelocity * mBounceCoefficient);
|
||||
if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) {
|
||||
return false;
|
||||
}
|
||||
mStartTime += mDuration;
|
||||
break;
|
||||
}
|
||||
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the current position and velocity for current time. Returns
|
||||
* true if update has been done and false if animation duration has been
|
||||
* reached.
|
||||
*/
|
||||
boolean update() {
|
||||
final long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
final long duration = time - mStartTime;
|
||||
|
||||
if (duration > mDuration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double distance;
|
||||
final float t = duration / 1000.0f;
|
||||
if (mState == TO_EDGE) {
|
||||
mCurrVelocity = mVelocity + mDeceleration * t;
|
||||
distance = mVelocity * t + mDeceleration * t * t / 2.0f;
|
||||
} else {
|
||||
final float d = t * TIME_COEF;
|
||||
mCurrVelocity = mVelocity * (float)Math.cos(d);
|
||||
distance = mVelocity / TIME_COEF * Math.sin(d);
|
||||
}
|
||||
|
||||
mCurrentPosition = mStart + (int) distance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,11 @@ package android.widget;
|
||||
import com.android.internal.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.FocusFinder;
|
||||
import android.view.KeyEvent;
|
||||
@@ -59,7 +62,9 @@ public class ScrollView extends FrameLayout {
|
||||
private long mLastScroll;
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
private Scroller mScroller;
|
||||
private OverScroller mScroller;
|
||||
private EdgeGlow mEdgeGlowTop;
|
||||
private EdgeGlow mEdgeGlowBottom;
|
||||
|
||||
/**
|
||||
* Flag to indicate that we are moving focus ourselves. This is so the
|
||||
@@ -113,6 +118,9 @@ public class ScrollView extends FrameLayout {
|
||||
private int mMinimumVelocity;
|
||||
private int mMaximumVelocity;
|
||||
|
||||
private int mOverscrollDistance;
|
||||
private int mOverflingDistance;
|
||||
|
||||
/**
|
||||
* ID of the active pointer. This is used to retain consistency during
|
||||
* drags/flings if multiple pointers are used.
|
||||
@@ -185,7 +193,7 @@ public class ScrollView extends FrameLayout {
|
||||
|
||||
|
||||
private void initScrollView() {
|
||||
mScroller = new Scroller(getContext());
|
||||
mScroller = new OverScroller(getContext());
|
||||
setFocusable(true);
|
||||
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
||||
setWillNotDraw(false);
|
||||
@@ -193,6 +201,8 @@ public class ScrollView extends FrameLayout {
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
mOverscrollDistance = configuration.getScaledOverscrollDistance();
|
||||
mOverflingDistance = configuration.getScaledOverflingDistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -453,6 +463,9 @@ public class ScrollView extends FrameLayout {
|
||||
/* Release the drag */
|
||||
mIsBeingDragged = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
|
||||
invalidate();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onSecondaryPointerUp(ev);
|
||||
@@ -510,7 +523,22 @@ public class ScrollView extends FrameLayout {
|
||||
final int deltaY = (int) (mLastMotionY - y);
|
||||
mLastMotionY = y;
|
||||
|
||||
scrollBy(0, deltaY);
|
||||
final int oldX = mScrollX;
|
||||
final int oldY = mScrollY;
|
||||
final int range = getScrollRange();
|
||||
if (overscrollBy(0, deltaY, 0, mScrollY, 0, range,
|
||||
0, mOverscrollDistance, true)) {
|
||||
// Break our velocity if we hit a scroll barrier.
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
|
||||
|
||||
final int pulledToY = oldY + deltaY;
|
||||
if (pulledToY < 0) {
|
||||
mEdgeGlowTop.onPull((float) deltaY / getHeight());
|
||||
} else if (pulledToY > range) {
|
||||
mEdgeGlowBottom.onPull((float) deltaY / getHeight());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
@@ -519,8 +547,15 @@ public class ScrollView extends FrameLayout {
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
|
||||
|
||||
if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
|
||||
fling(-initialVelocity);
|
||||
if (getChildCount() > 0) {
|
||||
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
|
||||
fling(-initialVelocity);
|
||||
} else {
|
||||
final int bottom = getScrollRange();
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
@@ -530,16 +565,27 @@ public class ScrollView extends FrameLayout {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
if (mEdgeGlowTop != null) {
|
||||
mEdgeGlowTop.onRelease();
|
||||
mEdgeGlowBottom.onRelease();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mIsBeingDragged && getChildCount() > 0) {
|
||||
if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
|
||||
invalidate();
|
||||
}
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsBeingDragged = false;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
if (mEdgeGlowTop != null) {
|
||||
mEdgeGlowTop.onRelease();
|
||||
mEdgeGlowBottom.onRelease();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
@@ -566,6 +612,22 @@ public class ScrollView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOverscrolled(int scrollX, int scrollY,
|
||||
boolean clampedX, boolean clampedY) {
|
||||
// Treat animating scrolls differently; see #computeScroll() for why.
|
||||
if (!mScroller.isFinished()) {
|
||||
mScrollX = scrollX;
|
||||
mScrollY = scrollY;
|
||||
if (clampedY) {
|
||||
mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
|
||||
}
|
||||
} else {
|
||||
super.scrollTo(scrollX, scrollY);
|
||||
}
|
||||
awakenScrollBars();
|
||||
}
|
||||
|
||||
private int getScrollRange() {
|
||||
int scrollRange = 0;
|
||||
if (getChildCount() > 0) {
|
||||
@@ -952,7 +1014,16 @@ public class ScrollView extends FrameLayout {
|
||||
return contentHeight;
|
||||
}
|
||||
|
||||
return getChildAt(0).getBottom();
|
||||
int scrollRange = getChildAt(0).getBottom();
|
||||
final int scrollY = mScrollY;
|
||||
final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
|
||||
if (scrollY < 0) {
|
||||
scrollRange -= scrollY;
|
||||
} else if (scrollY > overscrollBottom) {
|
||||
scrollRange += scrollY - overscrollBottom;
|
||||
}
|
||||
|
||||
return scrollRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1013,14 +1084,16 @@ public class ScrollView extends FrameLayout {
|
||||
int x = mScroller.getCurrX();
|
||||
int y = mScroller.getCurrY();
|
||||
|
||||
if (getChildCount() > 0) {
|
||||
View child = getChildAt(0);
|
||||
x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
|
||||
y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
|
||||
if (x != oldX || y != oldY) {
|
||||
mScrollX = x;
|
||||
mScrollY = y;
|
||||
onScrollChanged(x, y, oldX, oldY);
|
||||
if (oldX != x || oldY != y) {
|
||||
overscrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(),
|
||||
0, mOverflingDistance, false);
|
||||
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
|
||||
|
||||
final int range = getScrollRange();
|
||||
if (y < 0 && oldY >= 0) {
|
||||
mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
|
||||
} else if (y > range && oldY <= range) {
|
||||
mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
|
||||
}
|
||||
}
|
||||
awakenScrollBars();
|
||||
@@ -1258,7 +1331,7 @@ public class ScrollView extends FrameLayout {
|
||||
int bottom = getChildAt(0).getHeight();
|
||||
|
||||
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
|
||||
Math.max(0, bottom - height));
|
||||
Math.max(0, bottom - height), 0, height/2);
|
||||
|
||||
final boolean movingDown = velocityY > 0;
|
||||
|
||||
@@ -1296,6 +1369,55 @@ public class ScrollView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverscrollMode(int mode) {
|
||||
if (mode != OVERSCROLL_NEVER) {
|
||||
if (mEdgeGlowTop == null) {
|
||||
final Resources res = getContext().getResources();
|
||||
final Drawable edge = res.getDrawable(R.drawable.edge_light);
|
||||
final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
|
||||
mEdgeGlowTop = new EdgeGlow(edge, glow);
|
||||
mEdgeGlowBottom = new EdgeGlow(edge, glow);
|
||||
}
|
||||
} else {
|
||||
mEdgeGlowTop = null;
|
||||
mEdgeGlowBottom = null;
|
||||
}
|
||||
super.setOverscrollMode(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
if (mEdgeGlowTop != null) {
|
||||
final int scrollY = mScrollY;
|
||||
if (!mEdgeGlowTop.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
final int width = getWidth();
|
||||
|
||||
canvas.translate(-width / 2, scrollY);
|
||||
mEdgeGlowTop.setSize(width * 2, getHeight());
|
||||
if (mEdgeGlowTop.draw(canvas)) {
|
||||
invalidate();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
if (!mEdgeGlowBottom.isFinished()) {
|
||||
final int restoreCount = canvas.save();
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
|
||||
canvas.translate(-width / 2, scrollY + height);
|
||||
canvas.rotate(180, width, 0);
|
||||
mEdgeGlowBottom.setSize(width * 2, height);
|
||||
if (mEdgeGlowBottom.draw(canvas)) {
|
||||
invalidate();
|
||||
}
|
||||
canvas.restoreToCount(restoreCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int clamp(int n, int my, int child) {
|
||||
if (my >= child || n < 0) {
|
||||
/* my >= child is this case:
|
||||
|
||||
@@ -50,8 +50,6 @@ public class Scroller {
|
||||
private float mDurationReciprocal;
|
||||
private float mDeltaX;
|
||||
private float mDeltaY;
|
||||
private float mViscousFluidScale;
|
||||
private float mViscousFluidNormalize;
|
||||
private boolean mFinished;
|
||||
private Interpolator mInterpolator;
|
||||
|
||||
@@ -65,6 +63,17 @@ public class Scroller {
|
||||
|
||||
private final float mDeceleration;
|
||||
|
||||
private static float sViscousFluidScale;
|
||||
private static float sViscousFluidNormalize;
|
||||
|
||||
static {
|
||||
// This controls the viscous fluid effect (how much of it)
|
||||
sViscousFluidScale = 8.0f;
|
||||
// must be set to 1.0 (used in viscousFluid())
|
||||
sViscousFluidNormalize = 1.0f;
|
||||
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Scroller with the default duration and interpolator.
|
||||
*/
|
||||
@@ -277,11 +286,6 @@ public class Scroller {
|
||||
mDeltaX = dx;
|
||||
mDeltaY = dy;
|
||||
mDurationReciprocal = 1.0f / (float) mDuration;
|
||||
// This controls the viscous fluid effect (how much of it)
|
||||
mViscousFluidScale = 8.0f;
|
||||
// must be set to 1.0 (used in viscousFluid())
|
||||
mViscousFluidNormalize = 1.0f;
|
||||
mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,11 +343,9 @@ public class Scroller {
|
||||
mFinalY = Math.max(mFinalY, mMinY);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private float viscousFluid(float x)
|
||||
static float viscousFluid(float x)
|
||||
{
|
||||
x *= mViscousFluidScale;
|
||||
x *= sViscousFluidScale;
|
||||
if (x < 1.0f) {
|
||||
x -= (1.0f - (float)Math.exp(-x));
|
||||
} else {
|
||||
@@ -351,7 +353,7 @@ public class Scroller {
|
||||
x = 1.0f - (float)Math.exp(1.0f - x);
|
||||
x = start + x * (1.0f - start);
|
||||
}
|
||||
x *= mViscousFluidNormalize;
|
||||
x *= sViscousFluidNormalize;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
BIN
core/res/res/drawable/edge_light.png
Normal file
BIN
core/res/res/drawable/edge_light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
core/res/res/drawable/overscroll_glow.png
Normal file
BIN
core/res/res/drawable/overscroll_glow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
@@ -80,7 +80,8 @@
|
||||
android:paddingTop="2dip"
|
||||
android:paddingBottom="12dip"
|
||||
android:paddingLeft="14dip"
|
||||
android:paddingRight="10dip">
|
||||
android:paddingRight="10dip"
|
||||
android:overscrollMode="ifContentScrolls">
|
||||
<TextView android:id="@+id/message"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
<!-- Layout used as the dialog's content View for EditTextPreference. -->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:overscrollMode="ifContentScrolls">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+android:id/edittext_container"
|
||||
|
||||
@@ -31,4 +31,5 @@
|
||||
android:layout_marginTop="5px"
|
||||
android:cacheColorHint="@null"
|
||||
android:divider="@android:drawable/divider_horizontal_bright"
|
||||
android:scrollbars="vertical" />
|
||||
android:scrollbars="vertical"
|
||||
android:overscrollMode="ifContentScrolls" />
|
||||
|
||||
@@ -1326,6 +1326,19 @@
|
||||
<code>public void sayHello(View v)</code> method of your context
|
||||
(typically, your Activity). -->
|
||||
<attr name="onClick" format="string" />
|
||||
|
||||
<!-- Defines overscrolling behavior. This property is used only if the
|
||||
View is scrollable. Overscrolling is the ability for the user to
|
||||
scroll a View beyond its content boundaries into empty space. -->
|
||||
<attr name="overscrollMode">
|
||||
<!-- Always allow the user to overscroll the content. -->
|
||||
<enum name="always" value="0" />
|
||||
<!-- Only allow the user to overscroll content if the content is large
|
||||
enough to meaningfully scroll. -->
|
||||
<enum name="ifContentScrolls" value="1" />
|
||||
<!-- Never overscroll. -->
|
||||
<enum name="never" value="2" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<!-- Attributes that can be used with a {@link android.view.ViewGroup} or any
|
||||
@@ -1759,6 +1772,10 @@
|
||||
<!-- When set to false, the ListView will not draw the divider before each footer view.
|
||||
The default value is true. -->
|
||||
<attr name="footerDividersEnabled" format="boolean" />
|
||||
<!-- Drawable to draw above list content. -->
|
||||
<attr name="overscrollHeader" format="reference|color" />
|
||||
<!-- Drawable to draw below list content. -->
|
||||
<attr name="overscrollFooter" format="reference|color" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="MenuView">
|
||||
<!-- Default appearance of menu item text. -->
|
||||
|
||||
@@ -1251,6 +1251,10 @@
|
||||
<public type="attr" name="logo" id="0x010102be" />
|
||||
<public type="attr" name="xlargeScreens" id="0x010102bf" />
|
||||
<public type="attr" name="immersive" id="0x010102c0" />
|
||||
<public type="attr" name="overscrollMode" id="0x010102c1" />
|
||||
<public type="attr" name="overscrollHeader" id="0x010102c2" />
|
||||
<public type="attr" name="overscrollFooter" id="0x010102c3" />
|
||||
|
||||
<public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
|
||||
|
||||
<public-padding type="id" name="kraken_resource_pad" end="0x01020040" />
|
||||
@@ -1262,7 +1266,8 @@
|
||||
<public type="drawable" name="presence_video_online" id="0x010800ae" />
|
||||
<public type="drawable" name="presence_audio_away" id="0x010800af" />
|
||||
<public type="drawable" name="presence_audio_busy" id="0x010800b0" />
|
||||
<public type="drawable" name="presence_audio_online" id="0x010800b1" />
|
||||
<public type="drawable" name="presence_audio_online" id="0x010800b1" />
|
||||
|
||||
<public-padding type="drawable" name="kraken_resource_pad" end="0x01080100" />
|
||||
|
||||
<public-padding type="style" name="kraken_resource_pad" end="0x01030090" />
|
||||
|
||||
Reference in New Issue
Block a user