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