1/*
2 * Copyright (C) 2015 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.content.Context;
20import android.support.v4.view.NestedScrollingParent;
21import android.support.v4.view.ViewCompat;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.view.View;
25import android.view.ViewGroup;
26import android.widget.FrameLayout;
27
28public class TestedFrameLayout extends FrameLayout implements NestedScrollingParent {
29
30    static final int TEST_NESTED_SCROLL_MODE_IGNORE = 0;
31    static final int TEST_NESTED_SCROLL_MODE_CONSUME = 1;
32
33    private int mNestedScrollMode;
34    private int mNestedFlingMode;
35    private boolean mNestedStopNestedScrollCalled;
36
37    public TestedFrameLayout(Context context) {
38        super(context);
39    }
40
41    @Override
42    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
43        RecyclerView recyclerView = getRvChild();
44        if (recyclerView == null) {
45            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
46            return;
47        }
48        FullControlLayoutParams lp = (FullControlLayoutParams) recyclerView.getLayoutParams();
49        if (lp.wSpec == null && lp.hSpec == null) {
50            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
51            return;
52        }
53        final int childWidthMeasureSpec;
54        if (lp.wSpec != null) {
55            childWidthMeasureSpec = lp.wSpec;
56        } else if (lp.width == LayoutParams.MATCH_PARENT) {
57            final int width = Math.max(0, getMeasuredWidth()
58                    - lp.leftMargin - lp.rightMargin);
59            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
60        } else {
61            childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
62                    lp.leftMargin + lp.rightMargin, lp.width);
63        }
64
65        final int childHeightMeasureSpec;
66        if (lp.hSpec != null) {
67            childHeightMeasureSpec = lp.hSpec;
68        } else if (lp.height == LayoutParams.MATCH_PARENT) {
69            final int height = Math.max(0, getMeasuredHeight()
70                    - lp.topMargin - lp.bottomMargin);
71            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
72        } else {
73            childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
74                    lp.topMargin + lp.bottomMargin, lp.height);
75        }
76        recyclerView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
77        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
78                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
79            setMeasuredDimension(
80                    MeasureSpec.getSize(widthMeasureSpec),
81                    MeasureSpec.getSize(heightMeasureSpec)
82            );
83        } else {
84            setMeasuredDimension(
85                    chooseSize(widthMeasureSpec,
86                            recyclerView.getWidth() + getPaddingLeft() + getPaddingRight(),
87                            getMinimumWidth()),
88                    chooseSize(heightMeasureSpec,
89                            recyclerView.getHeight() + getPaddingTop() + getPaddingBottom(),
90                            getMinimumHeight()));
91        }
92    }
93
94    public static int chooseSize(int spec, int desired, int min) {
95        final int mode = View.MeasureSpec.getMode(spec);
96        final int size = View.MeasureSpec.getSize(spec);
97        switch (mode) {
98            case View.MeasureSpec.EXACTLY:
99                return size;
100            case View.MeasureSpec.AT_MOST:
101                return Math.min(size, desired);
102            case View.MeasureSpec.UNSPECIFIED:
103            default:
104                return Math.max(desired, min);
105        }
106    }
107
108
109    private RecyclerView getRvChild() {
110        for (int i = 0; i < getChildCount(); i++) {
111            if (getChildAt(i) instanceof RecyclerView) {
112                return (RecyclerView) getChildAt(i);
113            }
114        }
115        return null;
116    }
117
118    @Override
119    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
120        return p instanceof FullControlLayoutParams;
121    }
122
123    @Override
124    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
125        return new FullControlLayoutParams(p);
126    }
127
128    @Override
129    public LayoutParams generateLayoutParams(AttributeSet attrs) {
130        return new FullControlLayoutParams(getContext(), attrs);
131    }
132
133    @Override
134    protected LayoutParams generateDefaultLayoutParams() {
135        return new FullControlLayoutParams(getWidth(), getHeight());
136    }
137
138    @Override
139    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
140        // Always start nested scroll
141        return mNestedFlingMode == TEST_NESTED_SCROLL_MODE_CONSUME
142                || mNestedScrollMode == TEST_NESTED_SCROLL_MODE_CONSUME;
143    }
144
145    @Override
146    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
147        Log.d("TestedFrameLayout", "onNestedPreFling: " + mNestedFlingMode);
148
149        return mNestedFlingMode == TEST_NESTED_SCROLL_MODE_CONSUME;
150    }
151
152    @Override
153    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
154        if (mNestedScrollMode == TEST_NESTED_SCROLL_MODE_CONSUME) {
155            // We consume all scroll deltas
156            consumed[0] = dx;
157            consumed[1] = dy;
158        }
159    }
160
161    @Override
162    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
163            int dyUnconsumed) {
164        // ignore
165    }
166
167    @Override
168    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
169        // ignore
170        return false;
171    }
172
173    @Override
174    public void onNestedScrollAccepted(View child, View target, int axes) {
175        // ignore
176    }
177
178    @Override
179    public int getNestedScrollAxes() {
180        // We can scroll in both direction
181        return ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL;
182    }
183
184    @Override
185    public void onStopNestedScroll(View target) {
186        mNestedStopNestedScrollCalled = true;
187    }
188
189    public boolean stopNestedScrollCalled() {
190        return mNestedStopNestedScrollCalled;
191    }
192
193    public void setNestedScrollMode(int mode) {
194        mNestedScrollMode = mode;
195    }
196
197    public void setNestedFlingMode(int mode) {
198        mNestedFlingMode = mode;
199    }
200
201    public static class FullControlLayoutParams extends FrameLayout.LayoutParams {
202
203        Integer wSpec;
204        Integer hSpec;
205
206        public FullControlLayoutParams(Context c, AttributeSet attrs) {
207            super(c, attrs);
208        }
209
210        public FullControlLayoutParams(int width, int height) {
211            super(width, height);
212        }
213
214        public FullControlLayoutParams(ViewGroup.LayoutParams source) {
215            super(source);
216        }
217
218        public FullControlLayoutParams(FrameLayout.LayoutParams source) {
219            super(source);
220        }
221
222        public FullControlLayoutParams(MarginLayoutParams source) {
223            super(source);
224        }
225    }
226}
227