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.setupcompat.view; 18 19 import android.content.Context; 20 import android.util.AttributeSet; 21 import android.view.Gravity; 22 import android.view.View; 23 import android.widget.LinearLayout; 24 import com.google.android.setupcompat.R; 25 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; 26 import com.google.android.setupcompat.template.FooterActionButton; 27 import com.google.android.setupcompat.util.Logger; 28 import java.util.ArrayList; 29 import java.util.Collections; 30 31 /** 32 * An extension of LinearLayout that automatically switches to vertical orientation when it can't 33 * fit its child views horizontally. 34 * 35 * <p>Modified from {@code com.android.internal.widget.ButtonBarLayout} 36 */ 37 public class ButtonBarLayout extends LinearLayout { 38 39 private static final Logger LOG = new Logger(ButtonBarLayout.class); 40 41 private boolean stacked = false; 42 private int originalPaddingLeft; 43 private int originalPaddingRight; 44 ButtonBarLayout(Context context)45 public ButtonBarLayout(Context context) { 46 super(context); 47 } 48 ButtonBarLayout(Context context, AttributeSet attrs)49 public ButtonBarLayout(Context context, AttributeSet attrs) { 50 super(context, attrs); 51 } 52 53 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)54 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 55 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 56 57 setStacked(false); 58 59 boolean needsRemeasure = false; 60 61 int initialWidthMeasureSpec = widthMeasureSpec; 62 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { 63 // Measure with WRAP_CONTENT, so that we can compare the measured size with the 64 // available size to see if we need to stack. 65 initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 66 67 // We'll need to remeasure again to fill excess space. 68 needsRemeasure = true; 69 } 70 71 super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); 72 73 final boolean childrenLargerThanContainer = (widthSize > 0) && (getMeasuredWidth() > widthSize); 74 if (!isFooterButtonsEvenlyWeighted(getContext()) && childrenLargerThanContainer) { 75 setStacked(true); 76 77 // Measure again in the new orientation. 78 needsRemeasure = true; 79 } 80 81 if (needsRemeasure) { 82 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 83 } 84 } 85 setStacked(boolean stacked)86 private void setStacked(boolean stacked) { 87 if (this.stacked == stacked) { 88 return; 89 } 90 this.stacked = stacked; 91 boolean isUnstack = false; 92 int primaryStyleButtonCount = 0; 93 int childCount = getChildCount(); 94 for (int i = 0; i < childCount; i++) { 95 View child = getChildAt(i); 96 LayoutParams childParams = (LayoutParams) child.getLayoutParams(); 97 if (stacked) { 98 child.setTag(R.id.suc_customization_original_weight, childParams.weight); 99 childParams.weight = 0; 100 childParams.leftMargin = 0; 101 } else { 102 Float weight = (Float) child.getTag(R.id.suc_customization_original_weight); 103 if (weight != null) { 104 childParams.weight = weight; 105 } else { 106 // If the tag in the child is gone, it will be unstack and the child in the container will 107 // be disorder. 108 isUnstack = true; 109 } 110 if (isPrimaryButtonStyle(child)) { 111 primaryStyleButtonCount++; 112 } 113 } 114 child.setLayoutParams(childParams); 115 } 116 117 setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); 118 if (isUnstack) { 119 LOG.w("Reorder the FooterActionButtons in the container"); 120 ArrayList<View> childViewsInContainerInOrder = 121 getChildViewsInContainerInOrder( 122 childCount, /* isOnePrimaryButton= */ (primaryStyleButtonCount <= 1)); 123 for (int i = 0; i < childCount; i++) { 124 View view = childViewsInContainerInOrder.get(i); 125 if (view != null) { 126 bringChildToFront(view); 127 } 128 } 129 } else { 130 for (int i = childCount - 1; i >= 0; i--) { 131 bringChildToFront(getChildAt(i)); 132 } 133 } 134 135 if (stacked) { 136 // When stacked, the buttons need to be kept in the center of the button bar. 137 setHorizontalGravity(Gravity.CENTER); 138 // HACK: In the default button bar style, the left and right paddings are not 139 // balanced to compensate for different alignment for borderless (left) button and 140 // the raised (right) button. When it's stacked, we want the buttons to be centered, 141 // so we balance out the paddings here. 142 originalPaddingLeft = getPaddingLeft(); 143 originalPaddingRight = getPaddingRight(); 144 int paddingHorizontal = Math.max(originalPaddingLeft, originalPaddingRight); 145 setPadding(paddingHorizontal, getPaddingTop(), paddingHorizontal, getPaddingBottom()); 146 } else { 147 setPadding(originalPaddingLeft, getPaddingTop(), originalPaddingRight, getPaddingBottom()); 148 } 149 } 150 isPrimaryButtonStyle(View child)151 private boolean isPrimaryButtonStyle(View child) { 152 return child instanceof FooterActionButton 153 && ((FooterActionButton) child).isPrimaryButtonStyle(); 154 } 155 156 /** 157 * Return a array which store child views in the container and in the order (secondary button, 158 * space view, primary button), if only one primary button, the child views will replace null 159 * value in specific proper position, if there are two primary buttons, expected get the original 160 * child by the order (space view, secondary button, primary button), so insert the space view to 161 * the middle in the array. 162 */ getChildViewsInContainerInOrder( int childCount, boolean isOnePrimaryButton)163 private ArrayList<View> getChildViewsInContainerInOrder( 164 int childCount, boolean isOnePrimaryButton) { 165 int childViewsInContainerCount = 3; 166 int secondaryButtonIndex = 0; 167 int spaceViewIndex = 1; 168 int primaryButtonIndex = 2; 169 170 ArrayList<View> childFooterButtons = new ArrayList<>(); 171 172 if (isOnePrimaryButton) { 173 childFooterButtons.addAll(Collections.nCopies(childViewsInContainerCount, null)); 174 } 175 176 for (int i = 0; i < childCount; i++) { 177 View childAt = getChildAt(i); 178 if (isOnePrimaryButton) { 179 if (isPrimaryButtonStyle(childAt)) { 180 childFooterButtons.set(primaryButtonIndex, childAt); 181 } else if (!(childAt instanceof FooterActionButton)) { 182 childFooterButtons.set(spaceViewIndex, childAt); 183 } else { 184 childFooterButtons.set(secondaryButtonIndex, childAt); 185 } 186 } else { 187 if (!(childAt instanceof FooterActionButton)) { 188 childFooterButtons.add(spaceViewIndex, childAt); 189 } else { 190 childFooterButtons.add(getChildAt(i)); 191 } 192 } 193 } 194 return childFooterButtons; 195 } 196 isFooterButtonsEvenlyWeighted(Context context)197 private boolean isFooterButtonsEvenlyWeighted(Context context) { 198 int childCount = getChildCount(); 199 int primaryButtonCount = 0; 200 for (int i = 0; i < childCount; i++) { 201 View child = getChildAt(i); 202 if (child instanceof FooterActionButton) { 203 if (((FooterActionButton) child).isPrimaryButtonStyle()) { 204 primaryButtonCount += 1; 205 } 206 } 207 } 208 if (primaryButtonCount != 2) { 209 return false; 210 } 211 212 // TODO: Support neutral button style in glif layout for phone and tablet 213 if (context.getResources().getConfiguration().smallestScreenWidthDp >= 600 214 && PartnerConfigHelper.shouldApplyExtendedPartnerConfig(context)) { 215 return true; 216 } else { 217 return false; 218 } 219 } 220 } 221