1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.util.DisplayMetrics;
9 import android.view.MotionEvent;
12 * This objects controls the scroll snapping behavior based on scroll updates.
14 class SnapScrollController {
15 private static final String TAG = "SnapScrollController";
16 private static final int SNAP_NONE = 0;
17 private static final int SNAP_HORIZ = 1;
18 private static final int SNAP_VERT = 2;
19 private static final int SNAP_BOUND = 16;
21 private float mChannelDistance = 16f;
22 private int mSnapScrollMode = SNAP_NONE;
23 private int mFirstTouchX = -1;
24 private int mFirstTouchY = -1;
25 private float mDistanceX = 0;
26 private float mDistanceY = 0;
27 private ZoomManager mZoomManager;
29 SnapScrollController(Context context, ZoomManager zoomManager) {
30 calculateChannelDistance(context);
31 mZoomManager = zoomManager;
35 * Updates the snap scroll mode based on the given X and Y distance to be moved on scroll.
36 * If the scroll update is above a threshold, the snapping behavior is reset.
37 * @param distanceX X distance for the current scroll update.
38 * @param distanceY Y distance for the current scroll update.
40 void updateSnapScrollMode(float distanceX, float distanceY) {
41 if (mSnapScrollMode == SNAP_HORIZ || mSnapScrollMode == SNAP_VERT) {
42 mDistanceX += Math.abs(distanceX);
43 mDistanceY += Math.abs(distanceY);
44 if (mSnapScrollMode == SNAP_HORIZ) {
45 if (mDistanceY > mChannelDistance) {
46 mSnapScrollMode = SNAP_NONE;
47 } else if (mDistanceX > mChannelDistance) {
52 if (mDistanceX > mChannelDistance) {
53 mSnapScrollMode = SNAP_NONE;
54 } else if (mDistanceY > mChannelDistance) {
63 * Sets the snap scroll mode based on the event type.
64 * @param event The received MotionEvent.
66 void setSnapScrollingMode(MotionEvent event) {
67 switch(event.getAction()) {
68 case MotionEvent.ACTION_DOWN:
69 mSnapScrollMode = SNAP_NONE;
70 mFirstTouchX = (int) event.getX();
71 mFirstTouchY = (int) event.getY();
73 // Set scrolling mode to SNAP_X if scroll towards x-axis exceeds SNAP_BOUND
74 // and movement towards y-axis is trivial.
75 // Set scrolling mode to SNAP_Y if scroll towards y-axis exceeds SNAP_BOUND
76 // and movement towards x-axis is trivial.
77 // Scrolling mode will remain in SNAP_NONE for other conditions.
78 case MotionEvent.ACTION_MOVE:
79 if (!mZoomManager.isScaleGestureDetectionInProgress() &&
80 mSnapScrollMode == SNAP_NONE) {
81 int xDiff = (int) Math.abs(event.getX() - mFirstTouchX);
82 int yDiff = (int) Math.abs(event.getY() - mFirstTouchY);
83 if (xDiff > SNAP_BOUND && yDiff < SNAP_BOUND) {
84 mSnapScrollMode = SNAP_HORIZ;
85 } else if (xDiff < SNAP_BOUND && yDiff > SNAP_BOUND) {
86 mSnapScrollMode = SNAP_VERT;
90 case MotionEvent.ACTION_UP:
91 case MotionEvent.ACTION_CANCEL:
102 private void calculateChannelDistance(Context context) {
103 // The channel distance is adjusted for density and screen size.
104 final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
105 final double screenSize = Math.hypot((double) metrics.widthPixels / metrics.densityDpi,
106 (double) metrics.heightPixels / metrics.densityDpi);
107 if (screenSize < 3.0) {
108 mChannelDistance = 16f;
109 } else if (screenSize < 5.0) {
110 mChannelDistance = 22f;
111 } else if (screenSize < 7.0) {
112 mChannelDistance = 28f;
114 mChannelDistance = 34f;
116 mChannelDistance = mChannelDistance * metrics.density;
117 if (mChannelDistance < 16f) mChannelDistance = 16f;
121 * @return Whether current snap scroll mode is vertical.
123 boolean isSnapVertical() {
124 return mSnapScrollMode == SNAP_VERT;
128 * @return Whether current snap scroll mode is horizontal.
130 boolean isSnapHorizontal() {
131 return mSnapScrollMode == SNAP_HORIZ;
135 * @return Whether currently snapping scrolls.
137 boolean isSnappingScrolls() {
138 return mSnapScrollMode != SNAP_NONE;