1 /* 2 * Copyright (C) 2019 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 package com.android.car.ui.toolbar; 17 18 import static com.android.car.ui.core.CarUi.MIN_TARGET_API; 19 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId; 20 21 import android.annotation.TargetApi; 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.graphics.drawable.Drawable; 25 import android.util.AttributeSet; 26 import android.view.LayoutInflater; 27 import android.view.View; 28 import android.widget.ImageView; 29 import android.widget.LinearLayout; 30 import android.widget.TextView; 31 32 import androidx.annotation.LayoutRes; 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 36 import com.android.car.ui.R; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.function.Consumer; 42 43 /** 44 * Custom tab layout which supports adding tabs dynamically 45 * 46 * <p>It supports two layout modes: 47 * <ul><li>Flexible layout which will fill the width 48 * <li>Non-flexible layout which wraps content with a minimum tab width. By setting tab gravity, 49 * it can left aligned, right aligned or center aligned. 50 * 51 * <p>Scrolling function is not supported. If a tab item runs out of the tab layout bound, there 52 * is no way to access it. It's better to set the layout mode to flexible in this case. 53 * 54 * <p>Default tab item inflates from R.layout.car_ui_tab_item, but it also supports custom layout 55 * id, by overlaying R.layout.car_ui_tab_item_layout. By doing this, appearance of tab item view 56 * can be customized. 57 * 58 * <p>Touch feedback is using @android:attr/selectableItemBackground. 59 */ 60 @SuppressWarnings("AndroidJdkLibsChecker") 61 @TargetApi(MIN_TARGET_API) 62 public class TabLayout extends LinearLayout { 63 @LayoutRes 64 private final int mTabLayoutRes; 65 @NonNull 66 private List<com.android.car.ui.toolbar.Tab> mTabs = Collections.emptyList(); 67 private int mSelectedTab = -1; 68 TabLayout(@onNull Context context)69 public TabLayout(@NonNull Context context) { 70 this(context, null); 71 } 72 TabLayout(@onNull Context context, @Nullable AttributeSet attrs)73 public TabLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 74 this(context, attrs, 0); 75 } 76 TabLayout(@onNull Context context, @Nullable AttributeSet attrs, int defStyle)77 public TabLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { 78 super(context, attrs, defStyle); 79 Resources resources = context.getResources(); 80 81 boolean tabFlexibleLayout = resources.getBoolean(R.bool.car_ui_toolbar_tab_flexible_layout); 82 mTabLayoutRes = tabFlexibleLayout 83 ? R.layout.car_ui_toolbar_tab_item_layout_flexible 84 : R.layout.car_ui_toolbar_tab_item_layout; 85 } 86 87 /** Sets the tabs to show */ setTabs(List<com.android.car.ui.toolbar.Tab> tabs, int selectedTab)88 public void setTabs(List<com.android.car.ui.toolbar.Tab> tabs, int selectedTab) { 89 if (tabs == null) { 90 mTabs = Collections.emptyList(); 91 } else { 92 mTabs = Collections.unmodifiableList(new ArrayList<>(tabs)); 93 } 94 mSelectedTab = selectedTab; 95 recreateViews(); 96 } 97 getTabs()98 public List<com.android.car.ui.toolbar.Tab> getTabs() { 99 return mTabs; 100 } 101 102 /** Returns the currently selected tab, or -1 if no tabs exist */ getSelectedTab()103 public int getSelectedTab() { 104 if (mTabs.isEmpty() && mSelectedTab != -1) { 105 throw new IllegalStateException("mSelectedTab should've been -1"); 106 } 107 return mSelectedTab; 108 } 109 110 /** 111 * Returns if this TabLayout has tabs. That is, if the most recent call to 112 * {@link #setTabs(List, int)} contained a non-empty list. 113 */ hasTabs()114 public boolean hasTabs() { 115 return !mTabs.isEmpty(); 116 } 117 118 /** Set the tab at given position as the current selected tab. */ selectTab(int position)119 public void selectTab(int position) { 120 if (position < 0 || position >= mTabs.size()) { 121 throw new IllegalArgumentException("Tab position is invalid: " + position); 122 } 123 if (position == mSelectedTab) { 124 return; 125 } 126 127 int oldPosition = mSelectedTab; 128 mSelectedTab = position; 129 presentTabView(oldPosition); 130 presentTabView(position); 131 132 com.android.car.ui.toolbar.Tab tab = mTabs.get(position); 133 Consumer<com.android.car.ui.toolbar.Tab> listener = tab.getSelectedListener(); 134 if (listener != null) { 135 listener.accept(tab); 136 } 137 } 138 recreateViews()139 private void recreateViews() { 140 removeAllViews(); 141 for (int i = 0; i < mTabs.size(); i++) { 142 View tabView = LayoutInflater.from(getContext()) 143 .inflate(mTabLayoutRes, this, false); 144 addView(tabView); 145 presentTabView(i); 146 } 147 } 148 presentTabView(int position)149 private void presentTabView(int position) { 150 if (position < 0 || position >= mTabs.size()) { 151 throw new IllegalArgumentException("Tab position is invalid: " + position); 152 } 153 View tabView = getChildAt(position); 154 com.android.car.ui.toolbar.Tab tab = mTabs.get(position); 155 ImageView iconView = requireViewByRefId(tabView, R.id.car_ui_toolbar_tab_item_icon); 156 TextView textView = requireViewByRefId(tabView, R.id.car_ui_toolbar_tab_item_text); 157 158 tabView.setOnClickListener(view -> selectTab(position)); 159 tabView.setActivated(position == mSelectedTab); 160 161 if (tab.isTinted()) { 162 iconView.setImageTintList(getContext() 163 .getColorStateList(R.color.car_ui_toolbar_tab_item_selector)); 164 } else { 165 iconView.setImageTintList(null); 166 } 167 iconView.setImageDrawable(tab.getIcon()); 168 169 textView.setText(tab.getText()); 170 textView.setTextAppearance(position == mSelectedTab 171 ? R.style.TextAppearance_CarUi_Widget_Toolbar_Tab_Selected 172 : R.style.TextAppearance_CarUi_Widget_Toolbar_Tab); 173 } 174 175 /** 176 * Tab entity. 177 * 178 * @deprecated Use {@link com.android.car.ui.toolbar.Tab} instead. 179 */ 180 @Deprecated 181 public static class Tab { 182 private final Drawable mIcon; 183 private final CharSequence mText; 184 Tab(@ullable Drawable icon, @Nullable CharSequence text)185 public Tab(@Nullable Drawable icon, @Nullable CharSequence text) { 186 mIcon = icon; 187 mText = text; 188 } 189 190 /** Set tab text. */ bindText(TextView textView)191 protected void bindText(TextView textView) { 192 textView.setText(mText); 193 } 194 195 /** Set icon drawable. TODO(b/139444064): revise this api.*/ bindIcon(ImageView imageView)196 protected void bindIcon(ImageView imageView) { 197 imageView.setImageDrawable(mIcon); 198 } 199 } 200 } 201