1 /* 2 * Copyright (C) 2015 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.android.packageinstaller; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.util.AttributeSet; 22 import android.view.Gravity; 23 import android.view.View; 24 import android.widget.LinearLayout; 25 26 import com.android.packageinstaller.R; 27 28 /** 29 * An extension of LinearLayout that automatically switches to vertical 30 * orientation when it can't fit its child views horizontally. 31 */ 32 public class ButtonBarLayout extends LinearLayout { 33 /** Amount of the second button to "peek" above the fold when stacked. */ 34 private static final int PEEK_BUTTON_DP = 16; 35 36 /** Whether the current configuration allows stacking. */ 37 private boolean mAllowStacking; 38 39 private int mLastWidthSize = -1; 40 41 private int mMinimumHeight = 0; 42 ButtonBarLayout(Context context, AttributeSet attrs)43 public ButtonBarLayout(Context context, AttributeSet attrs) { 44 super(context, attrs); 45 46 final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout); 47 mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true); 48 ta.recycle(); 49 } 50 setAllowStacking(boolean allowStacking)51 public void setAllowStacking(boolean allowStacking) { 52 if (mAllowStacking != allowStacking) { 53 mAllowStacking = allowStacking; 54 if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) { 55 setStacked(false); 56 } 57 requestLayout(); 58 } 59 } 60 61 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)62 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 63 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 64 65 if (mAllowStacking) { 66 if (widthSize > mLastWidthSize && isStacked()) { 67 // We're being measured wider this time, try un-stacking. 68 setStacked(false); 69 } 70 71 mLastWidthSize = widthSize; 72 } 73 74 boolean needsRemeasure = false; 75 76 // If we're not stacked, make sure the measure spec is AT_MOST rather 77 // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we 78 // know to stack the buttons. 79 final int initialWidthMeasureSpec; 80 if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { 81 initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST); 82 83 // We'll need to remeasure again to fill excess space. 84 needsRemeasure = true; 85 } else { 86 initialWidthMeasureSpec = widthMeasureSpec; 87 } 88 89 super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); 90 91 if (mAllowStacking && !isStacked()) { 92 final int measuredWidth = getMeasuredWidthAndState(); 93 final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK; 94 if (measuredWidthState == MEASURED_STATE_TOO_SMALL) { 95 setStacked(true); 96 97 // Measure again in the new orientation. 98 needsRemeasure = true; 99 } 100 } 101 102 if (needsRemeasure) { 103 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 104 } 105 106 // Compute minimum height such that, when stacked, some portion of the 107 // second button is visible. 108 int minHeight = 0; 109 final int firstVisible = getNextVisibleChildIndex(0); 110 if (firstVisible >= 0) { 111 final View firstButton = getChildAt(firstVisible); 112 final LayoutParams firstParams = (LayoutParams) firstButton.getLayoutParams(); 113 minHeight += getPaddingTop() + firstButton.getMeasuredHeight() 114 + firstParams.topMargin + firstParams.bottomMargin; 115 if (isStacked()) { 116 final int secondVisible = getNextVisibleChildIndex(firstVisible + 1); 117 if (secondVisible >= 0) { 118 minHeight += getChildAt(secondVisible).getPaddingTop() 119 + PEEK_BUTTON_DP * getResources().getDisplayMetrics().density; 120 } 121 } else { 122 minHeight += getPaddingBottom(); 123 } 124 } 125 126 if (getMinimumHeight() != minHeight) { 127 setMinimumHeight(minHeight); 128 } 129 } 130 getNextVisibleChildIndex(int index)131 private int getNextVisibleChildIndex(int index) { 132 for (int i = index, count = getChildCount(); i < count; i++) { 133 if (getChildAt(i).getVisibility() == View.VISIBLE) { 134 return i; 135 } 136 } 137 return -1; 138 } 139 140 @Override getMinimumHeight()141 public int getMinimumHeight() { 142 return Math.max(mMinimumHeight, super.getMinimumHeight()); 143 } 144 setStacked(boolean stacked)145 private void setStacked(boolean stacked) { 146 setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); 147 setGravity(stacked ? Gravity.END : Gravity.BOTTOM); 148 149 final View spacer = findViewById(R.id.spacer); 150 if (spacer != null) { 151 spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE); 152 } 153 154 // Reverse the child order. This is specific to the Material button 155 // bar's layout XML and will probably not generalize. 156 final int childCount = getChildCount(); 157 for (int i = childCount - 2; i >= 0; i--) { 158 bringChildToFront(getChildAt(i)); 159 } 160 } 161 isStacked()162 private boolean isStacked() { 163 return getOrientation() == LinearLayout.VERTICAL; 164 } 165 } 166