1/* 2 * Copyright (C) 2016 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 */ 16package android.support.v7.widget; 17 18import org.hamcrest.BaseMatcher; 19import org.hamcrest.Description; 20 21import android.content.Context; 22import android.graphics.Color; 23import android.support.v4.util.Pair; 24import android.util.AttributeSet; 25import android.view.View; 26import android.view.ViewGroup; 27 28import java.util.ArrayList; 29import java.util.Collections; 30import java.util.List; 31import java.util.concurrent.atomic.AtomicLong; 32 33import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL; 34import static android.support.v7.widget.LinearLayoutManager.VERTICAL; 35 36abstract public class BaseWrapContentWithAspectRatioTest extends BaseRecyclerViewInstrumentationTest { 37 final BaseWrapContentTest.WrapContentConfig mWrapContentConfig; 38 39 protected BaseWrapContentWithAspectRatioTest( 40 BaseWrapContentTest.WrapContentConfig wrapContentConfig) { 41 mWrapContentConfig = wrapContentConfig; 42 } 43 44 int getSize(View view, int orientation) { 45 if (orientation == VERTICAL) { 46 return view.getHeight(); 47 } 48 return view.getWidth(); 49 } 50 51 static class LoggingView extends View { 52 53 MeasureBehavior mBehavior; 54 55 public void setBehavior(MeasureBehavior behavior) { 56 mBehavior = behavior; 57 } 58 59 public LoggingView(Context context) { 60 super(context); 61 } 62 63 public LoggingView(Context context, AttributeSet attrs) { 64 super(context, attrs); 65 } 66 67 public LoggingView(Context context, AttributeSet attrs, int defStyleAttr) { 68 super(context, attrs, defStyleAttr); 69 } 70 71 public LoggingView(Context context, AttributeSet attrs, int defStyleAttr, 72 int defStyleRes) { 73 super(context, attrs, defStyleAttr, defStyleRes); 74 } 75 76 @Override 77 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 78 mBehavior.onMeasure(this, widthMeasureSpec, heightMeasureSpec); 79 } 80 81 @Override 82 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 83 super.onLayout(changed, left, top, right, bottom); 84 mBehavior.onLayout(changed, left, top, right, bottom); 85 } 86 87 public void setMeasured(int w, int h) { 88 setMeasuredDimension(w, h); 89 } 90 91 public void prepareLayoutParams() { 92 mBehavior.setLayoutParams(this); 93 } 94 } 95 96 static class AspectRatioMeasureBehavior extends MeasureBehavior { 97 98 Float ratio; 99 int control; 100 101 public AspectRatioMeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) { 102 super(desiredW, desiredH, wMode, hMode); 103 } 104 105 public AspectRatioMeasureBehavior aspectRatio(int control, float ratio) { 106 this.control = control; 107 this.ratio = ratio; 108 return this; 109 } 110 111 @Override 112 public void onMeasure(LoggingView view, int wSpec, 113 int hSpec) { 114 super.onMeasure(view, wSpec, hSpec); 115 if (control == VERTICAL) { 116 view.setMeasured(getSecondary(view.getMeasuredHeight()), 117 view.getMeasuredHeight()); 118 } else if (control == HORIZONTAL) { 119 view.setMeasured(view.getMeasuredWidth(), 120 getSecondary(view.getMeasuredWidth())); 121 } 122 } 123 124 public int getSecondary(int controlSize) { 125 return (int) (controlSize * ratio); 126 } 127 } 128 129 static class MeasureBehavior { 130 private static final AtomicLong idCounter = new AtomicLong(0); 131 public List<Pair<Integer, Integer>> measureSpecs = new ArrayList<>(); 132 public List<Pair<Integer, Integer>> layouts = new ArrayList<>(); 133 int desiredW, desiredH; 134 final long mId = idCounter.incrementAndGet(); 135 136 ViewGroup.MarginLayoutParams layoutParams; 137 138 public MeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) { 139 this.desiredW = desiredW; 140 this.desiredH = desiredH; 141 layoutParams = new ViewGroup.MarginLayoutParams( 142 wMode, hMode 143 ); 144 } 145 146 public MeasureBehavior withMargins(int left, int top, int right, int bottom) { 147 layoutParams.leftMargin = left; 148 layoutParams.topMargin = top; 149 layoutParams.rightMargin = right; 150 layoutParams.bottomMargin = bottom; 151 return this; 152 } 153 154 public long getId() { 155 return mId; 156 } 157 158 public void onMeasure(LoggingView view, int wSpec, int hSpec) { 159 measureSpecs.add(new Pair<>(wSpec, hSpec)); 160 view.setMeasured( 161 RecyclerView.LayoutManager.chooseSize(wSpec, desiredW, 0), 162 RecyclerView.LayoutManager.chooseSize(hSpec, desiredH, 0)); 163 } 164 165 public int getSpec(int position, int orientation) { 166 if (orientation == VERTICAL) { 167 return measureSpecs.get(position).second; 168 } else { 169 return measureSpecs.get(position).first; 170 } 171 } 172 173 public void setLayoutParams(LoggingView view) { 174 view.setLayoutParams(layoutParams); 175 } 176 177 public void onLayout(boolean changed, int left, int top, int right, int bottom) { 178 if (changed) { 179 layouts.add(new Pair<>(right - left, bottom - top)); 180 } 181 } 182 } 183 184 185 static class WrapContentViewHolder extends RecyclerView.ViewHolder { 186 187 LoggingView mView; 188 189 public WrapContentViewHolder(ViewGroup parent) { 190 super(new LoggingView(parent.getContext())); 191 mView = (LoggingView) itemView; 192 mView.setBackgroundColor(Color.GREEN); 193 } 194 } 195 196 static class WrapContentAdapter extends RecyclerView.Adapter<WrapContentViewHolder> { 197 198 List<MeasureBehavior> behaviors = new ArrayList<>(); 199 200 public WrapContentAdapter(MeasureBehavior... behaviors) { 201 Collections.addAll(this.behaviors, behaviors); 202 } 203 204 @Override 205 public WrapContentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 206 return new WrapContentViewHolder(parent); 207 } 208 209 @Override 210 public void onBindViewHolder(WrapContentViewHolder holder, int position) { 211 holder.mView.setBehavior(behaviors.get(position)); 212 holder.mView.prepareLayoutParams(); 213 } 214 215 @Override 216 public int getItemCount() { 217 return behaviors.size(); 218 } 219 } 220 221 static class MeasureSpecMatcher extends BaseMatcher<Integer> { 222 223 private boolean checkSize = false; 224 private boolean checkMode = false; 225 private int mSize; 226 private int mMode; 227 228 public static MeasureSpecMatcher is(int size, int mode) { 229 MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, mode); 230 matcher.checkSize = true; 231 matcher.checkMode = true; 232 return matcher; 233 } 234 235 public static MeasureSpecMatcher size(int size) { 236 MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, 0); 237 matcher.checkSize = true; 238 matcher.checkMode = false; 239 return matcher; 240 } 241 242 public static MeasureSpecMatcher mode(int mode) { 243 MeasureSpecMatcher matcher = new MeasureSpecMatcher(0, mode); 244 matcher.checkSize = false; 245 matcher.checkMode = true; 246 return matcher; 247 } 248 249 private MeasureSpecMatcher(int size, int mode) { 250 mSize = size; 251 mMode = mode; 252 253 } 254 255 @Override 256 public boolean matches(Object item) { 257 if (item == null) { 258 return false; 259 } 260 Integer intValue = (Integer) item; 261 final int size = View.MeasureSpec.getSize(intValue); 262 final int mode = View.MeasureSpec.getMode(intValue); 263 if (checkSize && size != mSize) { 264 return false; 265 } 266 if (checkMode && mode != mMode) { 267 return false; 268 } 269 return true; 270 } 271 272 @Override 273 public void describeMismatch(Object item, Description description) { 274 Integer intValue = (Integer) item; 275 final int size = View.MeasureSpec.getSize(intValue); 276 final int mode = View.MeasureSpec.getMode(intValue); 277 if (checkSize && size != mSize) { 278 description.appendText(" Expected size was ").appendValue(mSize) 279 .appendText(" but received size is ").appendValue(size); 280 } 281 if (checkMode && mode != mMode) { 282 description.appendText(" Expected mode was ").appendValue(modeName(mMode)) 283 .appendText(" but received mode is ").appendValue(modeName(mode)); 284 } 285 } 286 287 @Override 288 public void describeTo(Description description) { 289 if (checkSize) { 290 description.appendText(" Measure spec size:").appendValue(mSize); 291 } 292 if (checkMode) { 293 description.appendText(" Measure spec mode:").appendValue(modeName(mMode)); 294 } 295 } 296 297 private static String modeName(int mode) { 298 switch (mode) { 299 case View.MeasureSpec.AT_MOST: 300 return "at most"; 301 case View.MeasureSpec.EXACTLY: 302 return "exactly"; 303 default: 304 return "unspecified"; 305 } 306 } 307 } 308} 309