PagerSnapHelper.java revision 849542dfdc5e83411c8b959251eb6f2a1556fc9d
1/* 2 * Copyright (C) 2016 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 17package android.support.v7.widget; 18 19import android.graphics.PointF; 20import android.support.annotation.NonNull; 21import android.support.annotation.Nullable; 22import android.view.View; 23 24/** 25 * Implementation of the {@link SnapHelper} supporting pager style snapping in either vertical or 26 * horizontal orientation. 27 */ 28public class PagerSnapHelper extends SnapHelper { 29 // Orientation helpers are lazily created per LayoutManager. 30 @Nullable 31 private OrientationHelper mVerticalHelper; 32 @Nullable 33 private OrientationHelper mHorizontalHelper; 34 35 @Nullable 36 @Override 37 public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, 38 @NonNull View targetView) { 39 int[] out = new int[2]; 40 if (layoutManager.canScrollHorizontally()) { 41 out[0] = distanceToCenter(layoutManager, targetView, 42 getHorizontalHelper(layoutManager)); 43 } else { 44 out[0] = 0; 45 } 46 47 if (layoutManager.canScrollVertically()) { 48 out[1] = distanceToCenter(layoutManager, targetView, 49 getVerticalHelper(layoutManager)); 50 } else { 51 out[1] = 0; 52 } 53 return out; 54 } 55 56 @Nullable 57 @Override 58 public View findSnapView(RecyclerView.LayoutManager layoutManager) { 59 if (layoutManager.canScrollVertically()) { 60 return findCenterView(layoutManager, getVerticalHelper(layoutManager)); 61 } else if (layoutManager.canScrollHorizontally()) { 62 return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); 63 } 64 return null; 65 } 66 67 @Override 68 public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, 69 int velocityY) { 70 final int itemCount = layoutManager.getItemCount(); 71 if (itemCount == 0) { 72 return RecyclerView.NO_POSITION; 73 } 74 75 View mStartMostChildView = null; 76 if (layoutManager.canScrollVertically()) { 77 mStartMostChildView = findStartView(layoutManager, getVerticalHelper(layoutManager)); 78 } else if (layoutManager.canScrollHorizontally()) { 79 mStartMostChildView = findStartView(layoutManager, getHorizontalHelper(layoutManager)); 80 } 81 82 if (mStartMostChildView == null) { 83 return RecyclerView.NO_POSITION; 84 } 85 final int centerPosition = layoutManager.getPosition(mStartMostChildView); 86 if (centerPosition == RecyclerView.NO_POSITION) { 87 return RecyclerView.NO_POSITION; 88 } 89 90 final boolean forwardDirection; 91 if (layoutManager.canScrollHorizontally()) { 92 forwardDirection = velocityX > 0; 93 } else { 94 forwardDirection = velocityY > 0; 95 } 96 boolean reverseLayout = false; 97 if ((layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { 98 RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = 99 (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; 100 PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); 101 if (vectorForEnd != null) { 102 reverseLayout = vectorForEnd.x < 0 || vectorForEnd.y < 0; 103 } 104 } 105 return reverseLayout 106 ? (forwardDirection ? centerPosition - 1 : centerPosition) 107 : (forwardDirection ? centerPosition + 1 : centerPosition); 108 } 109 110 private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, 111 @NonNull View targetView, OrientationHelper helper) { 112 final int childCenter = helper.getDecoratedStart(targetView) 113 + (helper.getDecoratedMeasurement(targetView) / 2); 114 final int containerCenter; 115 if (layoutManager.getClipToPadding()) { 116 containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; 117 } else { 118 containerCenter = helper.getEnd() / 2; 119 } 120 return childCenter - containerCenter; 121 } 122 123 /** 124 * Return the child view that is currently closest to the center of this parent. 125 * 126 * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached 127 * {@link RecyclerView}. 128 * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. 129 * 130 * @return the child view that is currently closest to the center of this parent. 131 */ 132 @Nullable 133 private View findCenterView(RecyclerView.LayoutManager layoutManager, 134 OrientationHelper helper) { 135 int childCount = layoutManager.getChildCount(); 136 if (childCount == 0) { 137 return null; 138 } 139 140 View closestChild = null; 141 final int center; 142 if (layoutManager.getClipToPadding()) { 143 center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; 144 } else { 145 center = helper.getEnd() / 2; 146 } 147 int absClosest = Integer.MAX_VALUE; 148 149 for (int i = 0; i < childCount; i++) { 150 final View child = layoutManager.getChildAt(i); 151 int childCenter = helper.getDecoratedStart(child) 152 + (helper.getDecoratedMeasurement(child) / 2); 153 int absDistance = Math.abs(childCenter - center); 154 155 /** if child center is closer than previous closest, set it as closest **/ 156 if (absDistance < absClosest) { 157 absClosest = absDistance; 158 closestChild = child; 159 } 160 } 161 return closestChild; 162 } 163 164 /** 165 * Return the child view that is currently closest to the start of this parent. 166 * 167 * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached 168 * {@link RecyclerView}. 169 * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. 170 * 171 * @return the child view that is currently closest to the start of this parent. 172 */ 173 @Nullable 174 private View findStartView(RecyclerView.LayoutManager layoutManager, 175 OrientationHelper helper) { 176 int childCount = layoutManager.getChildCount(); 177 if (childCount == 0) { 178 return null; 179 } 180 181 View closestChild = null; 182 int startest = Integer.MAX_VALUE; 183 184 for (int i = 0; i < childCount; i++) { 185 final View child = layoutManager.getChildAt(i); 186 int childStart = helper.getDecoratedStart(child); 187 188 /** if child is more to start than previous closest, set it as closest **/ 189 if (childStart < startest) { 190 startest = childStart; 191 closestChild = child; 192 } 193 } 194 return closestChild; 195 } 196 197 @NonNull 198 private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { 199 if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) { 200 mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); 201 } 202 return mVerticalHelper; 203 } 204 205 @NonNull 206 private OrientationHelper getHorizontalHelper( 207 @NonNull RecyclerView.LayoutManager layoutManager) { 208 if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) { 209 mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); 210 } 211 return mHorizontalHelper; 212 } 213} 214