blob: d00595d4b4f58d49952ac64939298ca781f2d575 [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 androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL;
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
abstract public class BaseWrapContentWithAspectRatioTest extends BaseRecyclerViewInstrumentationTest {
final BaseWrapContentTest.WrapContentConfig mWrapContentConfig;
protected BaseWrapContentWithAspectRatioTest(
BaseWrapContentTest.WrapContentConfig wrapContentConfig) {
mWrapContentConfig = wrapContentConfig;
}
int getSize(View view, int orientation) {
if (orientation == VERTICAL) {
return view.getHeight();
}
return view.getWidth();
}
static class LoggingView extends View {
MeasureBehavior mBehavior;
public void setBehavior(MeasureBehavior behavior) {
mBehavior = behavior;
}
public LoggingView(Context context) {
super(context);
}
public LoggingView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LoggingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public LoggingView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mBehavior.onMeasure(this, widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mBehavior.onLayout(changed, left, top, right, bottom);
}
public void setMeasured(int w, int h) {
setMeasuredDimension(w, h);
}
public void prepareLayoutParams() {
mBehavior.setLayoutParams(this);
}
}
static class AspectRatioMeasureBehavior extends MeasureBehavior {
Float ratio;
int control;
public AspectRatioMeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
super(desiredW, desiredH, wMode, hMode);
}
public AspectRatioMeasureBehavior aspectRatio(int control, float ratio) {
this.control = control;
this.ratio = ratio;
return this;
}
@Override
public void onMeasure(LoggingView view, int wSpec,
int hSpec) {
super.onMeasure(view, wSpec, hSpec);
if (control == VERTICAL) {
view.setMeasured(getSecondary(view.getMeasuredHeight()),
view.getMeasuredHeight());
} else if (control == HORIZONTAL) {
view.setMeasured(view.getMeasuredWidth(),
getSecondary(view.getMeasuredWidth()));
}
}
public int getSecondary(int controlSize) {
return (int) (controlSize * ratio);
}
}
static class MeasureBehavior {
private static final AtomicLong idCounter = new AtomicLong(0);
public List<Pair<Integer, Integer>> measureSpecs = new ArrayList<>();
public List<Pair<Integer, Integer>> layouts = new ArrayList<>();
int desiredW, desiredH;
final long mId = idCounter.incrementAndGet();
ViewGroup.MarginLayoutParams layoutParams;
public MeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
this.desiredW = desiredW;
this.desiredH = desiredH;
layoutParams = new ViewGroup.MarginLayoutParams(
wMode, hMode
);
}
public MeasureBehavior withMargins(int left, int top, int right, int bottom) {
layoutParams.leftMargin = left;
layoutParams.topMargin = top;
layoutParams.rightMargin = right;
layoutParams.bottomMargin = bottom;
return this;
}
public long getId() {
return mId;
}
public void onMeasure(LoggingView view, int wSpec, int hSpec) {
measureSpecs.add(new Pair<>(wSpec, hSpec));
view.setMeasured(
RecyclerView.LayoutManager.chooseSize(wSpec, desiredW, 0),
RecyclerView.LayoutManager.chooseSize(hSpec, desiredH, 0));
}
public int getSpec(int position, int orientation) {
if (orientation == VERTICAL) {
return measureSpecs.get(position).second;
} else {
return measureSpecs.get(position).first;
}
}
public void setLayoutParams(LoggingView view) {
view.setLayoutParams(layoutParams);
}
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
layouts.add(new Pair<>(right - left, bottom - top));
}
}
}
static class WrapContentViewHolder extends RecyclerView.ViewHolder {
LoggingView mView;
public WrapContentViewHolder(ViewGroup parent) {
super(new LoggingView(parent.getContext()));
mView = (LoggingView) itemView;
mView.setBackgroundColor(Color.GREEN);
}
}
static class WrapContentAdapter extends RecyclerView.Adapter<WrapContentViewHolder> {
List<MeasureBehavior> behaviors = new ArrayList<>();
public WrapContentAdapter(MeasureBehavior... behaviors) {
Collections.addAll(this.behaviors, behaviors);
}
@Override
public WrapContentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new WrapContentViewHolder(parent);
}
@Override
public void onBindViewHolder(@NonNull WrapContentViewHolder holder, int position) {
holder.mView.setBehavior(behaviors.get(position));
holder.mView.prepareLayoutParams();
}
@Override
public int getItemCount() {
return behaviors.size();
}
}
static class MeasureSpecMatcher extends BaseMatcher<Integer> {
private boolean checkSize = false;
private boolean checkMode = false;
private int mSize;
private int mMode;
public static MeasureSpecMatcher is(int size, int mode) {
MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, mode);
matcher.checkSize = true;
matcher.checkMode = true;
return matcher;
}
public static MeasureSpecMatcher size(int size) {
MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, 0);
matcher.checkSize = true;
matcher.checkMode = false;
return matcher;
}
public static MeasureSpecMatcher mode(int mode) {
MeasureSpecMatcher matcher = new MeasureSpecMatcher(0, mode);
matcher.checkSize = false;
matcher.checkMode = true;
return matcher;
}
private MeasureSpecMatcher(int size, int mode) {
mSize = size;
mMode = mode;
}
@Override
public boolean matches(Object item) {
if (item == null) {
return false;
}
Integer intValue = (Integer) item;
final int size = View.MeasureSpec.getSize(intValue);
final int mode = View.MeasureSpec.getMode(intValue);
if (checkSize && size != mSize) {
return false;
}
if (checkMode && mode != mMode) {
return false;
}
return true;
}
@Override
public void describeMismatch(Object item, Description description) {
Integer intValue = (Integer) item;
final int size = View.MeasureSpec.getSize(intValue);
final int mode = View.MeasureSpec.getMode(intValue);
if (checkSize && size != mSize) {
description.appendText(" Expected size was ").appendValue(mSize)
.appendText(" but received size is ").appendValue(size);
}
if (checkMode && mode != mMode) {
description.appendText(" Expected mode was ").appendValue(modeName(mMode))
.appendText(" but received mode is ").appendValue(modeName(mode));
}
}
@Override
public void describeTo(Description description) {
if (checkSize) {
description.appendText(" Measure spec size:").appendValue(mSize);
}
if (checkMode) {
description.appendText(" Measure spec mode:").appendValue(modeName(mMode));
}
}
private static String modeName(int mode) {
switch (mode) {
case View.MeasureSpec.AT_MOST:
return "at most";
case View.MeasureSpec.EXACTLY:
return "exactly";
default:
return "unspecified";
}
}
}
}