Home | History | Annotate | Download | only in widget
      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 
     17 package androidx.recyclerview.widget;
     18 
     19 import static junit.framework.Assert.assertTrue;
     20 
     21 import android.content.Context;
     22 import android.graphics.Canvas;
     23 import android.util.AttributeSet;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.widget.FrameLayout;
     27 
     28 import androidx.annotation.NonNull;
     29 import androidx.core.view.NestedScrollingParent2;
     30 import androidx.core.view.ViewCompat;
     31 
     32 import java.util.concurrent.CountDownLatch;
     33 import java.util.concurrent.TimeUnit;
     34 
     35 public 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