10d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez/* 20d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Copyright (C) 2017 The Android Open Source Project 30d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * 40d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Licensed under the Apache License, Version 2.0 (the "License"); 50d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * you may not use this file except in compliance with the License. 60d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * You may obtain a copy of the License at 70d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * 80d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * http://www.apache.org/licenses/LICENSE-2.0 90d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * 100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Unless required by applicable law or agreed to in writing, software 110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * distributed under the License is distributed on an "AS IS" BASIS, 120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * See the License for the specific language governing permissions and 140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * limitations under the License. 150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */ 160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezpackage androidx.car.widget; 180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport static java.lang.annotation.RetentionPolicy.SOURCE; 200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.content.Context; 220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.content.res.TypedArray; 230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.graphics.drawable.Drawable; 24ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.DrawableRes; 25ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.IntDef; 26ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull; 27ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.Nullable; 28ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.VisibleForTesting; 29ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.interpolator.view.animation.FastOutSlowInInterpolator; 300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.ChangeBounds; 310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.Fade; 320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.TransitionManager; 330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.transition.TransitionSet; 340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.util.AttributeSet; 350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.util.SparseArray; 360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.view.Gravity; 370d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.view.View; 380d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.view.ViewGroup; 390d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.FrameLayout; 400d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.ImageButton; 410d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.LinearLayout; 420d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.RelativeLayout; 430d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport android.widget.Space; 440d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 450d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport java.lang.annotation.Retention; 460d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport java.util.Locale; 470d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 480d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezimport androidx.car.R; 490d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 500d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez/** 510d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * An actions panel with three distinctive zones: 520d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <ul> 530d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <li>Main control: located in the bottom center it shows a highlighted icon and a circular 540d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * progress bar. 550d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <li>Secondary controls: these are displayed at the left and at the right of the main control. 560d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * <li>Overflow controls: these are displayed at the left and at the right of the secondary controls 570d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * (if the space allows) and on the additional space if the panel is expanded. 580d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * </ul> 590d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */ 600d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perezpublic class ActionBar extends RelativeLayout { 610d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private static final String TAG = "ActionBar"; 620d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 630d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // ActionBar container 640d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private ViewGroup mActionBarWrapper; 650d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Rows container 660d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private ViewGroup mRowsContainer; 670d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // All slots in this action bar where 0 is the bottom-start corner of the matrix, and 680d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // mNumColumns * nNumRows - 1 is the top-end corner 690d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private FrameLayout[] mSlots; 700d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** Views to set in particular {@link SlotPosition}s */ 710d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private final SparseArray<View> mFixedViews = new SparseArray<>(); 720d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // View to be used for the expand/collapse action 730d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private @Nullable View mExpandCollapseView; 740d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Default expand/collapse view to use one is not provided. 750d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private View mDefaultExpandCollapseView; 760d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Number of rows in actual use. This is the number of extra rows that will be displayed when 770d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // the action bar is expanded 780d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private int mNumExtraRowsInUse; 790d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Whether the action bar is expanded or not. 800d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private boolean mIsExpanded; 810d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Views to accomodate in the slots. 820d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private @Nullable View[] mViews; 830d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Number of columns of slots to use. 840d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private int mNumColumns; 850d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Maximum number of rows to use. 860d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private int mNumRows; 870d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 880d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez @Retention(SOURCE) 890d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez @IntDef({SLOT_MAIN, SLOT_LEFT, SLOT_RIGHT, SLOT_EXPAND_COLLAPSE}) 900d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public @interface SlotPosition {} 910d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 920d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** Slot used for main actions {@link ActionBar}, usually at the bottom center */ 930d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public static final int SLOT_MAIN = 0; 940d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** Slot used to host 'move left', 'rewind', 'previous' or similar secondary actions, 950d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * usually at the left of the main action on the bottom row */ 960d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public static final int SLOT_LEFT = 1; 970d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** Slot used to host 'move right', 'fast-forward', 'next' or similar secondary actions, 980d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * usually at the right of the main action on the bottom row */ 990d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public static final int SLOT_RIGHT = 2; 1000d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** Slot reserved for the expand/collapse button */ 1010d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public static final int SLOT_EXPAND_COLLAPSE = 3; 1020d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1030d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Minimum number of columns supported 1040d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private static final int MIN_COLUMNS = 3; 1050d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Weight for the spacers used at the start and end of each slots row. 1060d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private static final float SPACERS_WEIGHT = 0.5f; 1070d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1080d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public ActionBar(Context context) { 1090d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez super(context); 1100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez init(context, null, 0, 0); 1110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public ActionBar(Context context, AttributeSet attrs) { 1140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez super(context, attrs); 1150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez init(context, attrs, 0, 0); 1160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public ActionBar(Context context, AttributeSet attrs, int defStyleAttrs) { 1190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez super(context, attrs, defStyleAttrs); 1200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez init(context, attrs, defStyleAttrs, 0); 1210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public ActionBar(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) { 1240d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez super(context, attrs, defStyleAttrs, defStyleRes); 1250d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez init(context, attrs, defStyleAttrs, defStyleRes); 1260d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1270d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1280d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private void init(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) { 1290d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez inflate(context, R.layout.action_bar, this); 1300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ActionBar, 1320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez defStyleAttrs, defStyleRes); 1330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mNumColumns = Math.max(ta.getInteger(R.styleable.ActionBar_columns, MIN_COLUMNS), 1340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez MIN_COLUMNS); 1350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ta.recycle(); 1360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1370d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mActionBarWrapper = findViewById(R.id.action_bar_wrapper); 1380d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mRowsContainer = findViewById(R.id.rows_container); 1390d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mNumRows = mRowsContainer.getChildCount(); 1400d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mSlots = new FrameLayout[mNumColumns * mNumRows]; 1410d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1420d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez for (int i = 0; i < mNumRows; i++) { 1430d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Slots are reserved in reverse order (first slots are in the bottom row) 1440d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ViewGroup mRow = (ViewGroup) mRowsContainer.getChildAt(mNumRows - i - 1); 1450d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Inflate space on the left 1460d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez Space space = new Space(context); 1470d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mRow.addView(space); 1480d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez space.setLayoutParams(new LinearLayout.LayoutParams(0, 1490d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ViewGroup.LayoutParams.MATCH_PARENT, SPACERS_WEIGHT)); 1500d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Inflate necessary number of columns 1510d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez for (int j = 0; j < mNumColumns; j++) { 1520d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez int pos = i * mNumColumns + j; 1530d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mSlots[pos] = (FrameLayout) inflate(context, R.layout.action_bar_slot, null); 1540d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mSlots[pos].setLayoutParams(new LinearLayout.LayoutParams(0, 1550d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ViewGroup.LayoutParams.MATCH_PARENT, 1f)); 1560d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mRow.addView(mSlots[pos]); 1570d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1580d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Inflate space on the right 1590d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez space = new Space(context); 1600d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mRow.addView(space); 1610d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez space.setLayoutParams(new LinearLayout.LayoutParams(0, 1620d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ViewGroup.LayoutParams.MATCH_PARENT, SPACERS_WEIGHT)); 1630d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1640d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1650d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mDefaultExpandCollapseView = createIconButton(context, R.drawable.ic_overflow); 1660d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mDefaultExpandCollapseView.setContentDescription(context.getString( 1670d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez R.string.action_bar_expand_collapse_button)); 1680d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mDefaultExpandCollapseView.setOnClickListener(v -> onExpandCollapse()); 1690d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1700d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1710d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** 1720d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Returns an index in the {@link #mSlots} array, given a well-known slot position. 1730d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */ 1740d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private int getSlotIndex(@SlotPosition int slotPosition) { 1750d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez switch (slotPosition) { 1760d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez case SLOT_MAIN: 1770d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez return mNumColumns / 2; 1780d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez case SLOT_LEFT: 1790d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez return mNumColumns < 3 ? -1 : (mNumColumns / 2) - 1; 1800d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez case SLOT_RIGHT: 1810d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez return mNumColumns < 2 ? -1 : (mNumColumns / 2) + 1; 1820d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez case SLOT_EXPAND_COLLAPSE: 1830d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez return mNumColumns - 1; 1840d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez default: 1850d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez throw new IllegalArgumentException("Unknown position: " + slotPosition); 1860d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1870d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 1880d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 1890d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** 1900d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Sets or clears the view to be displayed at a particular position. 1910d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * 1920d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * @param view view to be displayed, or null to leave the position available. 1930d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * @param slotPosition position to update 1940d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */ 1950d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public void setView(@Nullable View view, @SlotPosition int slotPosition) { 1960d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez if (view != null) { 1970d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mFixedViews.put(slotPosition, view); 1980d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } else { 1990d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mFixedViews.remove(slotPosition); 2000d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2010d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez updateViewsLayout(); 2020d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2030d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2040d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** 2050d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Sets the view to use for the expand/collapse action. If not provided, a default 2060d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * {@link ImageButton} will be used. The provided {@link View} should be able be able to display 2070d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * changes in the "activated" state appropriately. 2080d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * 2090d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * @param view {@link View} to use for the expand/collapse action. 2100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */ 2110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public void setExpandCollapseView(@NonNull View view) { 2120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mExpandCollapseView = view; 2130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mExpandCollapseView.setOnClickListener(v -> onExpandCollapse()); 2140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez updateViewsLayout(); 2150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private View getExpandCollapseView() { 2180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez return mExpandCollapseView != null ? mExpandCollapseView : mDefaultExpandCollapseView; 2190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private ImageButton createIconButton(Context context, @DrawableRes int iconResId) { 2220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ImageButton button = (ImageButton) inflate(context, R.layout.action_bar_button, null); 2230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez Drawable icon = context.getDrawable(iconResId); 2240d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez button.setImageDrawable(icon); 2250d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez return button; 2260d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2270d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2280d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** 2290d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Sets the views to include in each available slot of the action bar. Slots will be filled from 2300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * start to end (i.e: left to right) and from bottom to top. If more views than available slots 2310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * are provided, all extra views will be ignored. 2320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * 2330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * @param views array of views to include in each available slot. 2340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */ 2350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez public void setViews(@Nullable View[] views) { 2360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mViews = views; 2370d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez updateViewsLayout(); 2380d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2390d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2400d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private void updateViewsLayout() { 2410d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Prepare an array of positions taken 2420d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez int totalSlots = mSlots.length; 2430d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez View[] slotViews = new View[totalSlots]; 2440d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2450d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Take all known positions 2460d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez for (int i = 0; i < mFixedViews.size(); i++) { 2470d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez int index = getSlotIndex(mFixedViews.keyAt(i)); 2480d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez if (index >= 0 && index < slotViews.length) { 2490d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez slotViews[index] = mFixedViews.valueAt(i); 2500d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2510d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2520d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2530d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Set all views using both the fixed and flexible positions 2540d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez int expandCollapseIndex = getSlotIndex(SLOT_EXPAND_COLLAPSE); 2550d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez int lastUsedIndex = 0; 2560d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez int viewsIndex = 0; 2570d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez for (int i = 0; i < totalSlots; i++) { 2580d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez View viewToUse = null; 2590d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2600d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez if (slotViews[i] != null) { 2610d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // If there is a view assigned for this slot, use it. 2620d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez viewToUse = slotViews[i]; 2630d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } else if (i == expandCollapseIndex && mViews != null 2640d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez && viewsIndex < mViews.length - 1) { 2650d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // If this is the expand/collapse slot, use the corresponding view 2660d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez viewToUse = getExpandCollapseView(); 2670d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } else if (mViews != null && viewsIndex < mViews.length) { 2680d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // Otherwise, if the slot is not reserved, and if we still have views to assign, 2690d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez // take one and assign it to this slot. 2700d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez viewToUse = mViews[viewsIndex]; 2710d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez viewsIndex++; 2720d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2730d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez setView(viewToUse, mSlots[i]); 2740d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez if (viewToUse != null) { 2750d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez lastUsedIndex = i; 2760d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2770d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2780d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2790d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mNumExtraRowsInUse = lastUsedIndex / mNumColumns; 2800d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2810d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2820d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private void setView(@Nullable View view, FrameLayout container) { 2830d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez container.removeAllViews(); 2840d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez if (view != null) { 2850d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez container.addView(view); 2860d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez container.setVisibility(VISIBLE); 2870d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2880d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); 2890d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } else { 2900d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez container.setVisibility(INVISIBLE); 2910d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2920d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 2930d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2940d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez private void onExpandCollapse() { 2950d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mIsExpanded = !mIsExpanded; 2960d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mSlots[getSlotIndex(SLOT_EXPAND_COLLAPSE)].setActivated(mIsExpanded); 2970d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 2980d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez int animationDuration = getContext().getResources().getInteger(mIsExpanded 2990d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez ? R.integer.car_action_bar_expand_anim_duration 3000d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez : R.integer.car_action_bar_collapse_anim_duration); 3010d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez TransitionSet set = new TransitionSet() 3020d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez .addTransition(new ChangeBounds()) 3030d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez .addTransition(new Fade()) 3040d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez .setDuration(animationDuration) 3050d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez .setInterpolator(new FastOutSlowInInterpolator()); 3060d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez TransitionManager.beginDelayedTransition(mActionBarWrapper, set); 3070d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez for (int i = 0; i < mNumExtraRowsInUse; i++) { 3080d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez mRowsContainer.getChildAt(i).setVisibility(mIsExpanded ? View.VISIBLE : View.GONE); 3090d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 3100d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 3110d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez 3120d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez /** 3130d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * Returns the view assigned to the given row and column, after layout. 3140d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * 3150d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * @param rowIdx row index from 0 being the top row, and {@link #mNumRows{ -1 being the bottom 3160d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * row. 3170d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez * @param colIdx column index from 0 on start (left), to {@link #mNumColumns} on end (right) 3180d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez */ 3190d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez @VisibleForTesting 3200d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez @Nullable 3210d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez View getViewAt(int rowIdx, int colIdx) { 3220d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez if (rowIdx < 0 || rowIdx > mRowsContainer.getChildCount()) { 3230d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez throw new IllegalArgumentException(String.format((Locale) null, 3240d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez "Row index out of range (requested: %d, max: %d)", 3250d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez rowIdx, mRowsContainer.getChildCount())); 3260d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 3270d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez if (colIdx < 0 || colIdx > mNumColumns) { 3280d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez throw new IllegalArgumentException(String.format((Locale) null, 3290d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez "Column index out of range (requested: %d, max: %d)", 3300d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez colIdx, mNumColumns)); 3310d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 3320d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez FrameLayout slot = (FrameLayout) ((LinearLayout) mRowsContainer.getChildAt(rowIdx)) 3330d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez .getChildAt(colIdx + 1); 3340d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez return slot.getChildCount() > 0 ? slot.getChildAt(0) : null; 3350d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez } 3360d1e61ea5cb641644ba15c8650cd269ac41982e7Roberto Perez} 337