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