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