import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Interpolator;
+import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.net.Uri;
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;
// 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 boolean mWrapContent;
private static final int MOTIONLESS_FALSE = 0;
mViewManager = new ViewManager(this);
mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
mDatabase = WebViewDatabase.getInstance(context);
- mScroller = new Scroller(context);
+ mScroller = new OverScroller(context);
mZoomButtonsController = new ZoomButtonsController(this);
mZoomButtonsController.setOnZoomListener(mZoomListener);
* 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);
}
/*
// Expects x in view coordinates
private int pinLocX(int x) {
+ if (mInOverScrollMode) return x;
return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
}
// Expects y in view coordinates
private int pinLocY(int y) {
+ if (mInOverScrollMode) return y;
int titleH = getTitleHeight();
// if the titlebar is still visible, just pin against 0
if (y <= titleH) {
scrollBar.draw(canvas);
}
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY, boolean clampedX,
+ boolean clampedY) {
+ mInOverScrollMode = false;
+ int maxX = computeMaxScrollX();
+ if (Math.abs(mMinZoomScale - mMaxZoomScale) < 0.01f && maxX == 0) {
+ // do not over scroll x if the page can't be zoomed and it 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
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) {
- // as onScrollChanged() is not called, sendOurVisibleRect()
- // needs to be call explicitly
- sendOurVisibleRect();
+ if (oldX != x || oldY != y) {
+ overscrollBy(x - oldX, y - oldY, oldX, oldY,
+ computeMaxScrollX(), computeMaxScrollY(),
+ getViewWidth() / 3, getViewHeight() / 3);
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
}
} else {
super.computeScroll();
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);
}
}
int saveCount = canvas.save();
+ if (mInOverScrollMode) {
+ if (mOverScrollBackground == null) {
+ mOverScrollBackground = new Paint();
+ Bitmap bm = BitmapFactory.decodeResource(
+ mContext.getResources(),
+ com.android.internal.R.drawable.pattern_underwear);
+ mOverScrollBackground.setShader(new BitmapShader(bm,
+ Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
+ }
+ int top = getTitleHeight();
+ // first draw the background and anchor to the top of the view
+ canvas.save();
+ canvas.translate(mScrollX, mScrollY);
+ canvas.clipRect(-mScrollX, top - mScrollY,
+ computeHorizontalScrollRange() - mScrollX, top
+ + computeVerticalScrollRange() - mScrollY,
+ Region.Op.DIFFERENCE);
+ canvas.drawPaint(mOverScrollBackground);
+ canvas.restore();
+ // next clip the region for the content
+ canvas.clipRect(0, top, computeHorizontalScrollRange(), top
+ + computeVerticalScrollRange());
+ }
if (mTitleBar != null) {
canvas.translate(0, (int) mTitleBar.getHeight());
}
canvas.restoreToCount(saveCount);
// Now draw the shadow.
- if (mTitleBar != null) {
- int y = mScrollY + getVisibleTitleHeight();
+ int titleH = getVisibleTitleHeight();
+ if (mTitleBar != null && titleH == 0) {
int height = (int) (5f * getContext().getResources()
.getDisplayMetrics().density);
- mTitleShadow.setBounds(mScrollX, y, mScrollX + getWidth(),
- y + height);
+ mTitleShadow.setBounds(mScrollX, mScrollY, mScrollX + getWidth(),
+ mScrollY + height);
mTitleShadow.draw(canvas);
}
if (AUTO_REDRAW_HACK && mAutoRedraw) {
}
// 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) {
}
}
if ((deltaX | deltaY) != 0) {
- scrollBy(deltaX, deltaY);
+ overscrollBy(deltaX, deltaY, mScrollX, mScrollY,
+ computeMaxScrollX(), computeMaxScrollY(),
+ getViewWidth() / 3, getViewHeight() / 3);
if (deltaX != 0) {
mLastTouchX = x;
}
Log.w(LOGTAG, "Miss a drag as we are waiting for" +
" WebCore's response for touch down.");
if (mFullScreenHolder == null
- && (computeHorizontalScrollExtent() < computeHorizontalScrollRange()
- || computeVerticalScrollExtent() < computeVerticalScrollRange())) {
+ && (computeMaxScrollX() > 0
+ || computeMaxScrollY() > 0)) {
// remove the pending TOUCH_EVENT and send a
// cancel
mWebViewCore
mVelocityTracker.addMovement(ev);
doFling();
break;
+ } else {
+ if (mScroller.springback(mScrollX, mScrollY, 0,
+ computeMaxScrollX(), 0,
+ computeMaxScrollY())) {
+ invalidate();
+ }
}
mLastVelocity = 0;
WebViewCore.resumePriority();
}
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ if (mScroller.springback(mScrollX, mScrollY, 0,
+ computeMaxScrollX(), 0, computeMaxScrollY())) {
+ invalidate();
+ }
+ }
break;
}
}
}
}
+ private int computeMaxScrollX() {
+ return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+ }
+
private int computeMaxScrollY() {
- int maxContentH = computeVerticalScrollRange() + getTitleHeight();
- return Math.max(maxContentH - getViewHeightWithTitle(), getTitleHeight());
+ return Math.max(computeVerticalScrollRange() + getTitleHeight()
+ - getViewHeightWithTitle(), getTitleHeight());
}
public void flingScroll(int vx, int vy) {
- int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
- int maxY = computeMaxScrollY();
-
- mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
+ mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
+ computeMaxScrollY(), getViewWidth() / 3, getViewHeight() / 3);
invalidate();
}
if (mVelocityTracker == null) {
return;
}
- int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+ int maxX = computeMaxScrollX();
int maxY = computeMaxScrollY();
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
}
if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
WebViewCore.resumePriority();
+ if (mScroller.springback(mScrollX, mScrollY, 0, computeMaxScrollX(),
+ 0, computeMaxScrollY())) {
+ invalidate();
+ }
return;
}
float currentVelocity = mScroller.getCurrVelocity();
mLastVelY = vy;
mLastVelocity = (float) Math.hypot(vx, vy);
- mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
+ mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY,
+ 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