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 TypedArray a = 62 context.obtainStyledAttributes(attrs, R.styleable.SudFillContentLayout, defStyleAttr, 0); 63 64 maxHeight = a.getDimensionPixelSize(R.styleable.SudFillContentLayout_android_maxHeight, -1); 65 maxWidth = a.getDimensionPixelSize(R.styleable.SudFillContentLayout_android_maxWidth, -1); 66 67 a.recycle(); 68 } 69 70 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)71 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 72 // Measure this view with the minWidth and minHeight, without asking the children. 73 // (Children size is the drawable's intrinsic size, and we don't want that to influence 74 // the size of the illustration). 75 setMeasuredDimension( 76 getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 77 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 78 79 int childCount = getChildCount(); 80 for (int i = 0; i < childCount; i++) { 81 measureIllustrationChild(getChildAt(i), getMeasuredWidth(), getMeasuredHeight()); 82 } 83 } 84 measureIllustrationChild(View child, int parentWidth, int parentHeight)85 private void measureIllustrationChild(View child, int parentWidth, int parentHeight) { 86 // Modified from ViewGroup#measureChildWithMargins 87 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 88 89 // Create measure specs that are no bigger than min(parentSize, maxSize) 90 91 int childWidthMeasureSpec = 92 getMaxSizeMeasureSpec( 93 Math.min(maxWidth, parentWidth), 94 getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, 95 lp.width); 96 int childHeightMeasureSpec = 97 getMaxSizeMeasureSpec( 98 Math.min(maxHeight, parentHeight), 99 getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, 100 lp.height); 101 102 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 103 } 104 getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension)105 private static int getMaxSizeMeasureSpec(int maxSize, int padding, int childDimension) { 106 // Modified from ViewGroup#getChildMeasureSpec 107 int size = Math.max(0, maxSize - padding); 108 109 if (childDimension >= 0) { 110 // Child wants a specific size... so be it 111 return MeasureSpec.makeMeasureSpec(childDimension, MeasureSpec.EXACTLY); 112 } else if (childDimension == LayoutParams.MATCH_PARENT) { 113 // Child wants to be our size. So be it. 114 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 115 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 116 // Child wants to determine its own size. It can't be 117 // bigger than us. 118 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); 119 } 120 return 0; 121 } 122 } 123