2 * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.views;
22 import android.content.Context;
23 import android.util.AttributeSet;
24 import android.view.MotionEvent;
25 import android.webkit.WebView;
27 import androidx.core.view.NestedScrollingChild2;
28 import androidx.core.view.NestedScrollingChildHelper;
29 import androidx.core.view.ViewCompat;
31 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
32 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
33 // The nested scrolling child helper is used throughout the class.
34 private NestedScrollingChildHelper nestedScrollingChildHelper;
36 // The previous Y position needs to be tracked between motion events.
37 private int previousYPosition;
40 public NestedScrollWebView(Context context) {
41 // Roll up to the next constructor.
45 // Intermediate constructor.
46 public NestedScrollWebView(Context context, AttributeSet attributeSet) {
47 // Roll up to the next constructor.
48 this(context, attributeSet, android.R.attr.webViewStyle);
52 public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
53 // Run the default commands.
54 super(context, attributeSet, defaultStyle);
56 // Initialize the nested scrolling child helper.
57 nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
59 // Enable nested scrolling by default.
60 nestedScrollingChildHelper.setNestedScrollingEnabled(true);
64 public boolean onTouchEvent(MotionEvent motionEvent) {
65 // Initialize a tracker to return if this motion event is handled.
66 boolean motionEventHandled;
68 // Run the commands for the given motion event action.
69 switch (motionEvent.getAction()) {
70 case MotionEvent.ACTION_DOWN:
71 // Start nested scrolling along the vertical axis. `ViewCompat` must be used until the minimum API >= 21.
72 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
74 // Save the current Y position. Action down will not be called again until a new motion starts.
75 previousYPosition = (int) motionEvent.getY();
77 // Run the default commands.
78 motionEventHandled = super.onTouchEvent(motionEvent);
81 case MotionEvent.ACTION_MOVE:
82 // Get the current Y position.
83 int currentYMotionPosition = (int) motionEvent.getY();
85 // Calculate the pre-scroll delta Y.
86 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
88 // Initialize a variable to track how much of the scroll is consumed.
89 int[] consumedScroll = new int[2];
91 // Initialize a variable to track the offset in the window.
92 int[] offsetInWindow = new int[2];
94 // Get the WebView Y position.
95 int webViewYPosition = getScrollY();
97 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
98 int scrollDeltaY = preScrollDeltaY;
100 // Dispatch the nested pre-school. This scrolls the app bar if it needs it. `offsetInWindow` will be returned with an updated value.
101 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
102 // Update the scroll delta Y if some of it was consumed.
103 scrollDeltaY = preScrollDeltaY - consumedScroll[1];
106 // Check to see if the WebView is at the top and and the scroll action is downward.
107 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) { // Swipe to refresh is being engaged.
108 // Stop the nested scroll so that swipe to refresh has complete control.
110 } else { // Swipe to refresh is not being engaged.
111 // Start the nested scroll so that the app bar can scroll off the screen.
112 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
114 // Dispatch the nested scroll. This scrolls the WebView. The delta Y unconsumed normally controls the swipe refresh layout, but that is handled with the `if` statement above.
115 dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
117 // Store the current Y position for use in the next action move.
118 previousYPosition = previousYPosition - scrollDeltaY;
121 // Run the default commands.
122 motionEventHandled = super.onTouchEvent(motionEvent);
127 // Stop nested scrolling.
130 // Run the default commands.
131 motionEventHandled = super.onTouchEvent(motionEvent);
134 // Perform a click. This is required by the Android accessibility guidelines.
137 // Return the status of the motion event.
138 return motionEventHandled;
141 // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
143 public boolean performClick() {
144 return super.performClick();
148 // Method from NestedScrollingChild.
150 public void setNestedScrollingEnabled(boolean status) {
151 // Set the status of the nested scrolling.
152 nestedScrollingChildHelper.setNestedScrollingEnabled(status);
155 // Method from NestedScrollingChild.
157 public boolean isNestedScrollingEnabled() {
158 // Return the status of nested scrolling.
159 return nestedScrollingChildHelper.isNestedScrollingEnabled();
163 // Method from NestedScrollingChild.
165 public boolean startNestedScroll(int axes) {
166 // Start a nested scroll along the indicated axes.
167 return nestedScrollingChildHelper.startNestedScroll(axes);
170 // Method from NestedScrollingChild2.
172 public boolean startNestedScroll(int axes, int type) {
173 // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
174 return nestedScrollingChildHelper.startNestedScroll(axes, type);
178 // Method from NestedScrollingChild.
180 public void stopNestedScroll() {
181 // Stop the nested scroll.
182 nestedScrollingChildHelper.stopNestedScroll();
185 // Method from NestedScrollingChild2.
187 public void stopNestedScroll(int type) {
188 // Stop the nested scroll of the given type of input which caused the scroll event.
189 nestedScrollingChildHelper.stopNestedScroll(type);
193 // Method from NestedScrollingChild.
195 public boolean hasNestedScrollingParent() {
196 // Return the status of the nested scrolling parent.
197 return nestedScrollingChildHelper.hasNestedScrollingParent();
200 // Method from NestedScrollingChild2.
202 public boolean hasNestedScrollingParent(int type) {
203 // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
204 return nestedScrollingChildHelper.hasNestedScrollingParent(type);
208 // Method from NestedScrollingChild.
210 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
211 // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
212 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
215 // Method from NestedScrollingChild2.
217 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
218 // Dispatch a nested pre-scroll with the specified deltas for the given type of input which caused the scroll event, which lets a parent to consume some of the scroll if desired.
219 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
223 // Method from NestedScrollingChild.
225 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
226 // Dispatch a nested scroll with the specified deltas.
227 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
230 // Method from NestedScrollingChild2.
232 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
233 // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
234 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
238 // Method from NestedScrollingChild.
240 public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
241 // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
242 return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
245 // Method from NestedScrollingChild.
247 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
248 // Dispatch a nested fling with the specified velocity.
249 return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);