/* * Copyright (C) 2016 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 android.support.v7.widget; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import android.content.Context; import android.graphics.Color; import android.support.v4.util.Pair; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL; import static android.support.v7.widget.LinearLayoutManager.VERTICAL; 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> measureSpecs = new ArrayList<>(); public List> layouts = new ArrayList<>(); int desiredW, desiredH; final long mId = idCounter.incrementAndGet(); ViewGroup.LayoutParams layoutParams; public MeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) { this.desiredW = desiredW; this.desiredH = desiredH; layoutParams = new ViewGroup.LayoutParams( wMode, hMode ); } 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 { List behaviors = new ArrayList<>(); public WrapContentAdapter(MeasureBehavior... behaviors) { Collections.addAll(this.behaviors, behaviors); } @Override public WrapContentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new WrapContentViewHolder(parent); } @Override public void onBindViewHolder(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 { 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"; } } } }