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;
28 SnapScrollController(Context context) {
29 calculateChannelDistance(context);
33 * Updates the snap scroll mode based on the given X and Y distance to be moved on scroll.
34 * If the scroll update is above a threshold, the snapping behavior is reset.
35 * @param distanceX X distance for the current scroll update.
36 * @param distanceY Y distance for the current scroll update.
38 void updateSnapScrollMode(float distanceX, float distanceY) {
39 if (mSnapScrollMode == SNAP_HORIZ || mSnapScrollMode == SNAP_VERT) {
40 mDistanceX += Math.abs(distanceX);
41 mDistanceY += Math.abs(distanceY);
42 if (mSnapScrollMode == SNAP_HORIZ) {
43 if (mDistanceY > mChannelDistance) {
44 mSnapScrollMode = SNAP_NONE;
45 } else if (mDistanceX > mChannelDistance) {
50 if (mDistanceX > mChannelDistance) {
51 mSnapScrollMode = SNAP_NONE;
52 } else if (mDistanceY > mChannelDistance) {
61 * Sets the snap scroll mode based on the event type.
62 * @param event The received MotionEvent.
64 void setSnapScrollingMode(MotionEvent event, boolean isScaleGestureDetectionInProgress) {
65 switch(event.getAction()) {
66 case MotionEvent.ACTION_DOWN:
67 mSnapScrollMode = SNAP_NONE;
68 mFirstTouchX = (int) event.getX();
69 mFirstTouchY = (int) event.getY();
71 // Set scrolling mode to SNAP_X if scroll towards x-axis exceeds SNAP_BOUND
72 // and movement towards y-axis is trivial.
73 // Set scrolling mode to SNAP_Y if scroll towards y-axis exceeds SNAP_BOUND
74 // and movement towards x-axis is trivial.
75 // Scrolling mode will remain in SNAP_NONE for other conditions.
76 case MotionEvent.ACTION_MOVE:
77 if (!isScaleGestureDetectionInProgress &&
78 mSnapScrollMode == SNAP_NONE) {
79 int xDiff = (int) Math.abs(event.getX() - mFirstTouchX);
80 int yDiff = (int) Math.abs(event.getY() - mFirstTouchY);
81 if (xDiff > SNAP_BOUND && yDiff < SNAP_BOUND) {
82 mSnapScrollMode = SNAP_HORIZ;
83 } else if (xDiff < SNAP_BOUND && yDiff > SNAP_BOUND) {
84 mSnapScrollMode = SNAP_VERT;
88 case MotionEvent.ACTION_UP:
89 case MotionEvent.ACTION_CANCEL:
100 private void calculateChannelDistance(Context context) {
101 // The channel distance is adjusted for density and screen size.
102 final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
103 final double screenSize = Math.hypot((double) metrics.widthPixels / metrics.densityDpi,
104 (double) metrics.heightPixels / metrics.densityDpi);
105 if (screenSize < 3.0) {
106 mChannelDistance = 16f;
107 } else if (screenSize < 5.0) {
108 mChannelDistance = 22f;
109 } else if (screenSize < 7.0) {
110 mChannelDistance = 28f;
112 mChannelDistance = 34f;
114 mChannelDistance = mChannelDistance * metrics.density;
115 if (mChannelDistance < 16f) mChannelDistance = 16f;
119 * @return Whether current snap scroll mode is vertical.
121 boolean isSnapVertical() {
122 return mSnapScrollMode == SNAP_VERT;
126 * @return Whether current snap scroll mode is horizontal.
128 boolean isSnapHorizontal() {
129 return mSnapScrollMode == SNAP_HORIZ;
133 * @return Whether currently snapping scrolls.
135 boolean isSnappingScrolls() {
136 return mSnapScrollMode != SNAP_NONE;