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