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