blob: f2e91bb5eacfef82a32584577be479d4b100e0f4 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.recyclerview.widget;
import static junit.framework.Assert.assertTrue;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.core.view.NestedScrollingParent2;
import androidx.core.view.ViewCompat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TestedFrameLayout extends FrameLayout implements NestedScrollingParent2 {
private NestedScrollingParent2 mNestedScrollingDelegate;
private CountDownLatch mDrawLatch;
private CountDownLatch mLayoutLatch;
public TestedFrameLayout(Context context) {
super(context);
setWillNotDraw(false);
}
public void expectDraws(int count) {
mDrawLatch = new CountDownLatch(count);
}
public void waitForDraw(int seconds) throws InterruptedException {
assertTrue(mDrawLatch.await(seconds, TimeUnit.SECONDS));
}
public void expectLayouts(int count) {
mLayoutLatch = new CountDownLatch(count);
}
public void waitForLayout(int seconds) throws InterruptedException {
assertTrue(mLayoutLatch.await(seconds, TimeUnit.SECONDS));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
RecyclerView recyclerView = getRvChild();
if (recyclerView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
FullControlLayoutParams lp = (FullControlLayoutParams) recyclerView.getLayoutParams();
if (lp.wSpec == null && lp.hSpec == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
final int childWidthMeasureSpec;
if (lp.wSpec != null) {
childWidthMeasureSpec = lp.wSpec;
} else if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
lp.leftMargin + lp.rightMargin, lp.width);
}
final int childHeightMeasureSpec;
if (lp.hSpec != null) {
childHeightMeasureSpec = lp.hSpec;
} else if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
lp.topMargin + lp.bottomMargin, lp.height);
}
recyclerView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec)
);
} else {
setMeasuredDimension(
chooseSize(widthMeasureSpec,
recyclerView.getWidth() + getPaddingLeft() + getPaddingRight(),
getMinimumWidth()),
chooseSize(heightMeasureSpec,
recyclerView.getHeight() + getPaddingTop() + getPaddingBottom(),
getMinimumHeight()));
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mLayoutLatch != null) {
mLayoutLatch.countDown();
}
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
if (mDrawLatch != null) {
mDrawLatch.countDown();
}
}
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, desired);
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
private RecyclerView getRvChild() {
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) instanceof RecyclerView) {
return (RecyclerView) getChildAt(i);
}
}
return null;
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof FullControlLayoutParams;
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new FullControlLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new FullControlLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new FullControlLayoutParams(getWidth(), getHeight());
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH);
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed) {
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
ViewCompat.TYPE_TOUCH);
}
@Override
public void onStopNestedScroll(View target) {
onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
}
@Override
public int getNestedScrollAxes() {
return mNestedScrollingDelegate != null
? mNestedScrollingDelegate.getNestedScrollAxes()
: 0;
}
@Override
public boolean onStartNestedScroll(@NonNull View child, @NonNull View target,
@ViewCompat.ScrollAxis int axes, @ViewCompat.NestedScrollType int type) {
return mNestedScrollingDelegate != null
&& mNestedScrollingDelegate.onStartNestedScroll(child, target, axes, type);
}
@Override
public void onNestedScrollAccepted(@NonNull View child, @NonNull View target,
@ViewCompat.ScrollAxis int axes, @ViewCompat.NestedScrollType int type) {
if (mNestedScrollingDelegate != null) {
mNestedScrollingDelegate.onNestedScrollAccepted(child, target, axes, type);
}
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return mNestedScrollingDelegate != null
&& mNestedScrollingDelegate.onNestedPreFling(target, velocityX, velocityY);
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return mNestedScrollingDelegate != null
&& mNestedScrollingDelegate.onNestedFling(target, velocityX, velocityY, consumed);
}
@Override
public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @ViewCompat.NestedScrollType int type) {
if (mNestedScrollingDelegate != null) {
mNestedScrollingDelegate.onNestedScroll(target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type);
}
}
@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
@ViewCompat.NestedScrollType int type) {
if (mNestedScrollingDelegate != null) {
mNestedScrollingDelegate.onNestedPreScroll(target, dx, dy, consumed, type);
}
}
@Override
public void onStopNestedScroll(@NonNull View target, @ViewCompat.NestedScrollType int type) {
if (mNestedScrollingDelegate != null) {
mNestedScrollingDelegate.onStopNestedScroll(target, type);
}
}
public void setNestedScrollingDelegate(NestedScrollingParent2 delegate) {
mNestedScrollingDelegate = delegate;
}
public static class FullControlLayoutParams extends FrameLayout.LayoutParams {
Integer wSpec;
Integer hSpec;
public FullControlLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public FullControlLayoutParams(int width, int height) {
super(width, height);
}
public FullControlLayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public FullControlLayoutParams(FrameLayout.LayoutParams source) {
super(source);
}
public FullControlLayoutParams(MarginLayoutParams source) {
super(source);
}
}
}