1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.core.view; 18 19 import android.os.Build; 20 import android.view.InputDevice; 21 import android.view.MotionEvent; 22 import android.view.ScrollFeedbackProvider; 23 import android.view.View; 24 import android.view.ViewConfiguration; 25 26 import androidx.annotation.RequiresApi; 27 28 import org.jspecify.annotations.NonNull; 29 30 /** Compat to access {@link ScrollFeedbackProvider} across different build versions. */ 31 public class ScrollFeedbackProviderCompat { 32 33 private final ScrollFeedbackProviderImpl mImpl; 34 ScrollFeedbackProviderCompat(@onNull View view)35 private ScrollFeedbackProviderCompat(@NonNull View view) { 36 if (Build.VERSION.SDK_INT >= 35) { 37 mImpl = new ScrollFeedbackProviderApi35Impl(view); 38 } else { 39 mImpl = new ScrollFeedbackProviderBaseImpl(); 40 } 41 } 42 43 /** Creates an instance of {@link ScrollFeedbackProviderCompat}. */ createProvider(@onNull View view)44 public static @NonNull ScrollFeedbackProviderCompat createProvider(@NonNull View view) { 45 return new ScrollFeedbackProviderCompat(view); 46 } 47 48 /** 49 * Call this when the view has snapped to an item. 50 * 51 * @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering 52 * the snap. 53 * @param source the input source of the motion causing the snap. 54 * @param axis the axis of {@code event} that caused the item to snap. 55 */ onSnapToItem(int inputDeviceId, int source, int axis)56 public void onSnapToItem(int inputDeviceId, int source, int axis) { 57 mImpl.onSnapToItem(inputDeviceId, source, axis); 58 } 59 60 /** 61 * Call this when the view has reached the scroll limit. 62 * 63 * <p>Note that a feedback may not be provided on every call to this method. This interface, for 64 * instance, may provide feedback on every `N`th scroll limit event. For the interface to 65 * properly provide feedback when needed, call this method for each scroll limit event that you 66 * want to be accounted to scroll limit feedback. 67 * 68 * @param inputDeviceId the ID of the {@link InputDevice} that caused scrolling to hit limit. 69 * @param source the input source of the motion that caused scrolling to hit the limit. 70 * @param axis the axis of {@code event} that caused scrolling to hit the limit. 71 * @param isStart {@code true} if scrolling hit limit at the start of the scrolling list, and 72 * {@code false} if the scrolling hit limit at the end of the scrolling list. 73 * <i>start</i> and <i>end</i> in this context are not geometrical references. 74 * Instead, they refer to the start and end of a scrolling experience. As such, 75 * "start" for some views may be at the bottom of a scrolling list, while it may 76 * be at the top of scrolling list for others. 77 */ onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart)78 public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) { 79 mImpl.onScrollLimit(inputDeviceId, source, axis, isStart); 80 } 81 82 /** 83 * Call this when the view has scrolled. 84 * 85 * <p>Different axes have different ways to map their raw axis values to pixels for scrolling. 86 * When calling this method, use the scroll values in pixels by which the view was scrolled; do 87 * not use the raw axis values. That is, use whatever value is passed to one of View's scrolling 88 * methods (example: {@link View#scrollBy(int, int)}). For example, for vertical scrolling on 89 * {@link MotionEvent#AXIS_SCROLL}, convert the raw axis value to the equivalent pixels by using 90 * {@link ViewConfiguration#getScaledVerticalScrollFactor()}, and use that value for this method 91 * call. 92 * 93 * <p>Note that a feedback may not be provided on every call to this method. This interface, for 94 * instance, may provide feedback for every `x` pixels scrolled. For the interface to properly 95 * track scroll progress and provide feedback when needed, call this method for each scroll 96 * event that you want to be accounted to scroll feedback. 97 * 98 * @param inputDeviceId the ID of the {@link InputDevice} that caused scroll progress. 99 * @param source the input source of the motion that caused scroll progress. 100 * @param axis the axis of {@code event} that caused scroll progress. 101 * @param deltaInPixels the amount of scroll progress, in pixels. 102 */ onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels)103 public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) { 104 mImpl.onScrollProgress(inputDeviceId, source, axis, deltaInPixels); 105 } 106 107 @RequiresApi(35) 108 private static class ScrollFeedbackProviderApi35Impl implements ScrollFeedbackProviderImpl { 109 private final ScrollFeedbackProvider mProvider; 110 ScrollFeedbackProviderApi35Impl(View view)111 ScrollFeedbackProviderApi35Impl(View view) { 112 mProvider = ScrollFeedbackProvider.createProvider(view); 113 } 114 115 @Override onSnapToItem(int inputDeviceId, int source, int axis)116 public void onSnapToItem(int inputDeviceId, int source, int axis) { 117 mProvider.onSnapToItem(inputDeviceId, source, axis); 118 } 119 120 @Override onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart)121 public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) { 122 mProvider.onScrollLimit(inputDeviceId, source, axis, isStart); 123 } 124 125 @Override onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels)126 public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) { 127 mProvider.onScrollProgress(inputDeviceId, source, axis, deltaInPixels); 128 } 129 } 130 131 private static class ScrollFeedbackProviderBaseImpl implements ScrollFeedbackProviderImpl { 132 @Override onSnapToItem(int inputDeviceId, int source, int axis)133 public void onSnapToItem(int inputDeviceId, int source, int axis) {} 134 135 @Override onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart)136 public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) {} 137 138 @Override onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels)139 public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) {} 140 } 141 142 /** 143 * An interface parallel to {@link ScrollFeedbackProvider}, to allow different compat 144 * implementations based on Build SDK version. 145 */ 146 private interface ScrollFeedbackProviderImpl { onSnapToItem(int inputDeviceId, int source, int axis)147 void onSnapToItem(int inputDeviceId, int source, int axis); onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart)148 void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart); onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels)149 void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels); 150 } 151 } 152