1/* 2 * Copyright 2018 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 androidx.recyclerview.widget; 18 19import static junit.framework.Assert.assertTrue; 20 21import android.content.Context; 22import android.graphics.Canvas; 23import android.util.AttributeSet; 24import android.view.View; 25import android.view.ViewGroup; 26import android.widget.FrameLayout; 27 28import androidx.annotation.NonNull; 29import androidx.core.view.NestedScrollingParent2; 30import androidx.core.view.ViewCompat; 31 32import java.util.concurrent.CountDownLatch; 33import java.util.concurrent.TimeUnit; 34 35public class TestedFrameLayout extends FrameLayout implements NestedScrollingParent2 { 36 37 private NestedScrollingParent2 mNestedScrollingDelegate; 38 private CountDownLatch mDrawLatch; 39 private CountDownLatch mLayoutLatch; 40 41 public TestedFrameLayout(Context context) { 42 super(context); 43 setWillNotDraw(false); 44 } 45 46 public void expectDraws(int count) { 47 mDrawLatch = new CountDownLatch(count); 48 } 49 50 public void waitForDraw(int seconds) throws InterruptedException { 51 assertTrue(mDrawLatch.await(seconds, TimeUnit.SECONDS)); 52 } 53 54 public void expectLayouts(int count) { 55 mLayoutLatch = new CountDownLatch(count); 56 } 57 58 public void waitForLayout(int seconds) throws InterruptedException { 59 assertTrue(mLayoutLatch.await(seconds, TimeUnit.SECONDS)); 60 } 61 62 @Override 63 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 64 RecyclerView recyclerView = getRvChild(); 65 if (recyclerView == null) { 66 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 67 return; 68 } 69 FullControlLayoutParams lp = (FullControlLayoutParams) recyclerView.getLayoutParams(); 70 if (lp.wSpec == null && lp.hSpec == null) { 71 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 72 return; 73 } 74 final int childWidthMeasureSpec; 75 if (lp.wSpec != null) { 76 childWidthMeasureSpec = lp.wSpec; 77 } else if (lp.width == LayoutParams.MATCH_PARENT) { 78 final int width = Math.max(0, getMeasuredWidth() 79 - lp.leftMargin - lp.rightMargin); 80 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 81 } else { 82 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 83 lp.leftMargin + lp.rightMargin, lp.width); 84 } 85 86 final int childHeightMeasureSpec; 87 if (lp.hSpec != null) { 88 childHeightMeasureSpec = lp.hSpec; 89 } else if (lp.height == LayoutParams.MATCH_PARENT) { 90 final int height = Math.max(0, getMeasuredHeight() 91 - lp.topMargin - lp.bottomMargin); 92 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 93 } else { 94 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 95 lp.topMargin + lp.bottomMargin, lp.height); 96 } 97 recyclerView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 98 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && 99 MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) { 100 setMeasuredDimension( 101 MeasureSpec.getSize(widthMeasureSpec), 102 MeasureSpec.getSize(heightMeasureSpec) 103 ); 104 } else { 105 setMeasuredDimension( 106 chooseSize(widthMeasureSpec, 107 recyclerView.getWidth() + getPaddingLeft() + getPaddingRight(), 108 getMinimumWidth()), 109 chooseSize(heightMeasureSpec, 110 recyclerView.getHeight() + getPaddingTop() + getPaddingBottom(), 111 getMinimumHeight())); 112 } 113 } 114 115 @Override 116 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 117 super.onLayout(changed, left, top, right, bottom); 118 if (mLayoutLatch != null) { 119 mLayoutLatch.countDown(); 120 } 121 } 122 123 @Override 124 public void onDraw(Canvas c) { 125 super.onDraw(c); 126 if (mDrawLatch != null) { 127 mDrawLatch.countDown(); 128 } 129 } 130 131 public static int chooseSize(int spec, int desired, int min) { 132 final int mode = View.MeasureSpec.getMode(spec); 133 final int size = View.MeasureSpec.getSize(spec); 134 switch (mode) { 135 case View.MeasureSpec.EXACTLY: 136 return size; 137 case View.MeasureSpec.AT_MOST: 138 return Math.min(size, desired); 139 case View.MeasureSpec.UNSPECIFIED: 140 default: 141 return Math.max(desired, min); 142 } 143 } 144 145 private RecyclerView getRvChild() { 146 for (int i = 0; i < getChildCount(); i++) { 147 if (getChildAt(i) instanceof RecyclerView) { 148 return (RecyclerView) getChildAt(i); 149 } 150 } 151 return null; 152 } 153 154 @Override 155 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 156 return p instanceof FullControlLayoutParams; 157 } 158 159 @Override 160 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 161 return new FullControlLayoutParams(p); 162 } 163 164 @Override 165 public LayoutParams generateLayoutParams(AttributeSet attrs) { 166 return new FullControlLayoutParams(getContext(), attrs); 167 } 168 169 @Override 170 protected LayoutParams generateDefaultLayoutParams() { 171 return new FullControlLayoutParams(getWidth(), getHeight()); 172 } 173 174 @Override 175 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 176 return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH); 177 } 178 179 @Override 180 public void onNestedScrollAccepted(View child, View target, int axes) { 181 onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH); 182 } 183 184 @Override 185 public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 186 onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH); 187 } 188 189 @Override 190 public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, 191 int dyUnconsumed) { 192 onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 193 ViewCompat.TYPE_TOUCH); 194 } 195 196 @Override 197 public void onStopNestedScroll(View target) { 198 onStopNestedScroll(target, ViewCompat.TYPE_TOUCH); 199 } 200 201 @Override 202 public int getNestedScrollAxes() { 203 return mNestedScrollingDelegate != null 204 ? mNestedScrollingDelegate.getNestedScrollAxes() 205 : 0; 206 } 207 208 @Override 209 public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, 210 @ViewCompat.ScrollAxis int axes, @ViewCompat.NestedScrollType int type) { 211 return mNestedScrollingDelegate != null 212 && mNestedScrollingDelegate.onStartNestedScroll(child, target, axes, type); 213 } 214 215 @Override 216 public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, 217 @ViewCompat.ScrollAxis int axes, @ViewCompat.NestedScrollType int type) { 218 if (mNestedScrollingDelegate != null) { 219 mNestedScrollingDelegate.onNestedScrollAccepted(child, target, axes, type); 220 } 221 } 222 223 @Override 224 public boolean onNestedPreFling(View target, float velocityX, float velocityY) { 225 return mNestedScrollingDelegate != null 226 && mNestedScrollingDelegate.onNestedPreFling(target, velocityX, velocityY); 227 } 228 229 @Override 230 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 231 return mNestedScrollingDelegate != null 232 && mNestedScrollingDelegate.onNestedFling(target, velocityX, velocityY, consumed); 233 } 234 235 @Override 236 public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, 237 int dxUnconsumed, int dyUnconsumed, @ViewCompat.NestedScrollType int type) { 238 if (mNestedScrollingDelegate != null) { 239 mNestedScrollingDelegate.onNestedScroll(target, dxConsumed, dyConsumed, 240 dxUnconsumed, dyUnconsumed, type); 241 } 242 } 243 244 @Override 245 public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, 246 @ViewCompat.NestedScrollType int type) { 247 if (mNestedScrollingDelegate != null) { 248 mNestedScrollingDelegate.onNestedPreScroll(target, dx, dy, consumed, type); 249 } 250 } 251 252 @Override 253 public void onStopNestedScroll(@NonNull View target, @ViewCompat.NestedScrollType int type) { 254 if (mNestedScrollingDelegate != null) { 255 mNestedScrollingDelegate.onStopNestedScroll(target, type); 256 } 257 } 258 259 public void setNestedScrollingDelegate(NestedScrollingParent2 delegate) { 260 mNestedScrollingDelegate = delegate; 261 } 262 263 public static class FullControlLayoutParams extends FrameLayout.LayoutParams { 264 265 Integer wSpec; 266 Integer hSpec; 267 268 public FullControlLayoutParams(Context c, AttributeSet attrs) { 269 super(c, attrs); 270 } 271 272 public FullControlLayoutParams(int width, int height) { 273 super(width, height); 274 } 275 276 public FullControlLayoutParams(ViewGroup.LayoutParams source) { 277 super(source); 278 } 279 280 public FullControlLayoutParams(FrameLayout.LayoutParams source) { 281 super(source); 282 } 283 284 public FullControlLayoutParams(MarginLayoutParams source) { 285 super(source); 286 } 287 } 288} 289