• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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