1 /*
2  * Copyright (C) 2023 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.chassis.car.ui.plugin.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.util.AttributeSet;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.widget.ImageView;
26 import android.widget.LinearLayout;
27 import android.widget.TextView;
28 
29 import androidx.annotation.LayoutRes;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 
33 import com.chassis.car.ui.plugin.R;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.function.Consumer;
39 
40 /**
41  * Custom tab layout which supports adding tabs dynamically
42  *
43  * <p>It supports two layout modes:
44  * <ul><li>Flexible layout which will fill the width
45  * <li>Non-flexible layout which wraps content with a minimum tab width. By setting tab gravity,
46  * it can left aligned, right aligned or center aligned.
47  *
48  * <p>Scrolling function is not supported. If a tab item runs out of the tab layout bound, there
49  * is no way to access it. It's better to set the layout mode to flexible in this case.
50  *
51  * <p>Default tab item inflates from R.layout.car_ui_tab_item, but it also supports custom layout
52  * id, by overlaying R.layout.car_ui_tab_item_layout. By doing this, appearance of tab item view
53  * can be customized.
54  *
55  * <p>Touch feedback is using @android:attr/selectableItemBackground.
56  */
57 public class TabLayout extends LinearLayout {
58     @LayoutRes
59     private final int mTabLayoutRes;
60     @NonNull
61     private List<com.android.car.ui.toolbar.Tab> mTabs = Collections.emptyList();
62     private int mSelectedTab = -1;
63 
TabLayout(@onNull Context context)64     public TabLayout(@NonNull Context context) {
65         this(context, null);
66     }
67 
TabLayout(@onNull Context context, @Nullable AttributeSet attrs)68     public TabLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
69         this(context, attrs, 0);
70     }
71 
TabLayout(@onNull Context context, @Nullable AttributeSet attrs, int defStyle)72     public TabLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
73         super(context, attrs, defStyle);
74         Resources resources = context.getResources();
75 
76         mTabLayoutRes = R.layout.car_ui_portrait_toolbar_tab_item;
77     }
78 
79     /** Sets the tabs to show */
setTabs(List<com.android.car.ui.toolbar.Tab> tabs, int selectedTab)80     public void setTabs(List<com.android.car.ui.toolbar.Tab> tabs, int selectedTab) {
81         if (tabs == null) {
82             mTabs = Collections.emptyList();
83         } else {
84             mTabs = Collections.unmodifiableList(new ArrayList<>(tabs));
85         }
86         mSelectedTab = selectedTab;
87         recreateViews();
88     }
89 
getTabs()90     public List<com.android.car.ui.toolbar.Tab> getTabs() {
91         return mTabs;
92     }
93 
94     /** Returns the currently selected tab, or -1 if no tabs exist */
getSelectedTab()95     public int getSelectedTab() {
96         if (mTabs.isEmpty() && mSelectedTab != -1) {
97             throw new IllegalStateException("Selected tab is invalid: " + mSelectedTab);
98         }
99         return mSelectedTab;
100     }
101 
102     /**
103      * Returns if this TabLayout has tabs. That is, if the most recent call to
104      * {@link #setTabs(List, int)} contained a non-empty list.
105      */
hasTabs()106     public boolean hasTabs() {
107         return !mTabs.isEmpty();
108     }
109 
110     /** Set the tab at given position as the current selected tab. */
selectTab(int position)111     public void selectTab(int position) {
112         if (position < 0 || position >= mTabs.size()) {
113             throw new IllegalArgumentException("Tab position is invalid: " + position);
114         }
115         if (position == mSelectedTab) {
116             return;
117         }
118 
119         int oldPosition = mSelectedTab;
120         mSelectedTab = position;
121         presentTabView(oldPosition);
122         presentTabView(position);
123 
124         com.android.car.ui.toolbar.Tab tab = mTabs.get(position);
125         Consumer<com.android.car.ui.toolbar.Tab> listener = tab.getSelectedListener();
126         if (listener != null) {
127             listener.accept(tab);
128         }
129     }
130 
recreateViews()131     private void recreateViews() {
132         removeAllViews();
133         for (int i = 0; i < mTabs.size(); i++) {
134             View tabView = LayoutInflater.from(getContext())
135                     .inflate(mTabLayoutRes, this, false);
136             addView(tabView);
137             presentTabView(i);
138         }
139     }
140 
presentTabView(int position)141     private void presentTabView(int position) {
142         if (position < 0 || position >= mTabs.size()) {
143             throw new IllegalArgumentException("Tab position is invalid: " + position);
144         }
145         View tabView = getChildAt(position);
146         com.android.car.ui.toolbar.Tab tab = mTabs.get(position);
147         ImageView iconView = requireViewByRefId(tabView,
148                 R.id.car_ui_portrait_toolbar_tab_item_icon);
149         TextView textView = requireViewByRefId(tabView, R.id.car_ui_portrait_toolbar_tab_item_text);
150 
151         tabView.setOnClickListener(view -> selectTab(position));
152         tabView.setActivated(position == mSelectedTab);
153 
154         iconView.setImageDrawable(tab.getIcon());
155 
156         textView.setText(tab.getText());
157     }
158 }
159