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