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