• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 
17 package com.google.android.setupdesign.view;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.util.AttributeSet;
22 import android.view.View;
23 import android.widget.FrameLayout;
24 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
25 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
26 import com.google.android.setupdesign.R;
27 
28 /**
29  * A layout that will measure its children size based on the space it is given, by using its {@code
30  * android:minWidth}, {@code android:minHeight}, {@code android:maxWidth}, and {@code
31  * android:maxHeight} values.
32  *
33  * <p>Typically this is used to show an illustration image or video on the screen. For optimal UX,
34  * those assets typically want to occupy the remaining space available on screen within a certain
35  * range, and then stop scaling beyond the min/max size attributes. Therefore this view is typically
36  * used inside a ScrollView with {@code fillViewport} set to true, together with a linear layout
37  * weight or relative layout to fill the remaining space visible on screen.
38  *
39  * <p>When measuring, this view ignores its children and simply layout according to the minWidth /
40  * minHeight given. Therefore it is common for children of this layout to have width / height set to
41  * {@code match_parent}. The maxWidth / maxHeight values will then be applied to the children to
42  * make sure they are not too big.
43  */
44 public class FillContentLayout extends FrameLayout {
45 
46   private int maxWidth;
47   private int maxHeight;
48 
FillContentLayout(Context context)49   public FillContentLayout(Context context) {
50     this(context, null);
51   }
52 
FillContentLayout(Context context, AttributeSet attrs)53   public FillContentLayout(Context context, AttributeSet attrs) {
54     this(context, attrs, R.attr.sudFillContentLayoutStyle);
55   }
56 
FillContentLayout(Context context, AttributeSet attrs, int defStyleAttr)57   public FillContentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
58     super(context, attrs, defStyleAttr);
59     init(context, attrs, defStyleAttr);
60   }
61 
init(Context context, AttributeSet attrs, int defStyleAttr)62   private void init(Context context, AttributeSet attrs, int defStyleAttr) {
63     if (isInEditMode()) {
64       return;
65     }
66 
67     TypedArray a =
68         context.obtainStyledAttributes(attrs, R.styleable.SudFillContentLayout, defStyleAttr, 0);
69 
70     if (PartnerConfigHelper.get(context)
71         .isPartnerConfigAvailable(PartnerConfig.CONFIG_ILLUSTRATION_MAX_HEIGHT)) {
72       maxHeight =
73           (int)
74               PartnerConfigHelper.get(context)
75                   .getDimension(context, PartnerConfig.CONFIG_ILLUSTRATION_MAX_HEIGHT);
76     } else {
77       maxHeight = a.getDimensionPixelSize(R.styleable.SudFillContentLayout_android_maxHeight, -1);
78     }
79 
80     if (PartnerConfigHelper.get(context)
81         .isPartnerConfigAvailable(PartnerConfig.CONFIG_ILLUSTRATION_MAX_WIDTH)) {
82       maxWidth =
83           (int)
84               PartnerConfigHelper.get(context)
85                   .getDimension(context, PartnerConfig.CONFIG_ILLUSTRATION_MAX_WIDTH);
86     } else {
87       maxWidth = a.getDimensionPixelSize(R.styleable.SudFillContentLayout_android_maxWidth, -1);
88     }
89 
90     a.recycle();
91   }
92 
93   @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)94   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
95     // Measure this view with the minWidth and minHeight, without asking the children.
96     // (Children size is the drawable's intrinsic size, and we don't want that to influence
97     // the size of the illustration).
98     setMeasuredDimension(
99         getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
100         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
101 
102     int childCount = getChildCount();
103     for (int i = 0; i < childCount; i++) {
104       measureIllustrationChild(getChildAt(i), getMeasuredWidth(), getMeasuredHeight());
105     }
106   }
107 
measureIllustrationChild(View child, int parentWidth, int parentHeight)108   private void measureIllustrationChild(View child, int parentWidth, int parentHeight) {
109     // Modified from ViewGroup#measureChildWithMargins
110     final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
111 
112     // Create measure specs that are no bigger than min(parentSize, maxSize)
113     int childWidthMeasureSpec =
114         getMaxSizeMeasureSpec(
115             Math.min(maxWidth, parentWidth),
116             getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin,
117             lp.width);
118     int childHeightMeasureSpec =
119         getMaxSizeMeasureSpec(
120             Math.min(maxHeight, parentHeight),
121             getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin,
122             lp.height);
123 
124     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
125   }
126 
getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension)127   private static int getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension) {
128     // Modified from ViewGroup#getChildMeasureSpec
129     int size = Math.max(0, maxSize - padding);
130 
131     if (childDimension >= 0) {
132       // Child wants a specific size... so be it
133       return MeasureSpec.makeMeasureSpec(childDimension, MeasureSpec.EXACTLY);
134     } else if (childDimension == LayoutParams.MATCH_PARENT) {
135       // Child wants to be our size. So be it.
136       return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
137     } else if (childDimension == LayoutParams.WRAP_CONTENT) {
138       // Child wants to determine its own size. It can't be
139       // bigger than us.
140       return MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
141     }
142     return 0;
143   }
144 }
145