• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.contacts.common.list;
17 
18 import android.content.Context;
19 import android.content.res.ColorStateList;
20 import android.content.res.TypedArray;
21 import android.graphics.Outline;
22 import android.support.v4.view.PagerAdapter;
23 import android.support.v4.view.ViewPager;
24 import android.util.AttributeSet;
25 import android.util.TypedValue;
26 import android.view.Gravity;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewOutlineProvider;
30 import android.widget.FrameLayout;
31 import android.widget.HorizontalScrollView;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 import android.widget.Toast;
35 import com.android.contacts.common.R;
36 import com.android.dialer.compat.CompatUtils;
37 
38 /**
39  * Lightweight implementation of ViewPager tabs. This looks similar to traditional actionBar tabs,
40  * but allows for the view containing the tabs to be placed anywhere on screen. Text-related
41  * attributes can also be assigned in XML - these will get propogated to the child TextViews
42  * automatically.
43  */
44 public class ViewPagerTabs extends HorizontalScrollView implements ViewPager.OnPageChangeListener {
45 
46   private static final ViewOutlineProvider VIEW_BOUNDS_OUTLINE_PROVIDER;
47   private static final int TAB_SIDE_PADDING_IN_DPS = 10;
48   // TODO: This should use <declare-styleable> in the future
49   private static final int[] ATTRS =
50       new int[] {
51         android.R.attr.textSize,
52         android.R.attr.textStyle,
53         android.R.attr.textColor,
54         android.R.attr.textAllCaps
55       };
56 
57   static {
58     if (CompatUtils.isLollipopCompatible()) {
59       VIEW_BOUNDS_OUTLINE_PROVIDER =
60           new ViewOutlineProvider() {
61             @Override
62             public void getOutline(View view, Outline outline) {
63               outline.setRect(0, 0, view.getWidth(), view.getHeight());
64             }
65           };
66     } else {
67       VIEW_BOUNDS_OUTLINE_PROVIDER = null;
68     }
69   }
70 
71   /**
72    * Linearlayout that will contain the TextViews serving as tabs. This is the only child of the
73    * parent HorizontalScrollView.
74    */
75   final int mTextStyle;
76 
77   final ColorStateList mTextColor;
78   final int mTextSize;
79   final boolean mTextAllCaps;
80   ViewPager mPager;
81   int mPrevSelected = -1;
82   int mSidePadding;
83   private ViewPagerTabStrip mTabStrip;
84   private int[] mTabIcons;
85   // For displaying the unread count next to the tab icon.
86   private int[] mUnreadCounts;
87 
ViewPagerTabs(Context context)88   public ViewPagerTabs(Context context) {
89     this(context, null);
90   }
91 
ViewPagerTabs(Context context, AttributeSet attrs)92   public ViewPagerTabs(Context context, AttributeSet attrs) {
93     this(context, attrs, 0);
94   }
95 
ViewPagerTabs(Context context, AttributeSet attrs, int defStyle)96   public ViewPagerTabs(Context context, AttributeSet attrs, int defStyle) {
97     super(context, attrs, defStyle);
98     setFillViewport(true);
99 
100     mSidePadding = (int) (getResources().getDisplayMetrics().density * TAB_SIDE_PADDING_IN_DPS);
101 
102     final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
103     mTextSize = a.getDimensionPixelSize(0, 0);
104     mTextStyle = a.getInt(1, 0);
105     mTextColor = a.getColorStateList(2);
106     mTextAllCaps = a.getBoolean(3, false);
107 
108     mTabStrip = new ViewPagerTabStrip(context);
109     addView(
110         mTabStrip,
111         new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
112     a.recycle();
113 
114     if (CompatUtils.isLollipopCompatible()) {
115       // enable shadow casting from view bounds
116       setOutlineProvider(VIEW_BOUNDS_OUTLINE_PROVIDER);
117     }
118   }
119 
setViewPager(ViewPager viewPager)120   public void setViewPager(ViewPager viewPager) {
121     mPager = viewPager;
122     addTabs(mPager.getAdapter());
123   }
124 
125   /**
126    * Set the tab icons and initialize an array for unread counts the same length as the icon array.
127    *
128    * @param tabIcons An array representing the tab icons in order.
129    */
configureTabIcons(int[] tabIcons)130   public void configureTabIcons(int[] tabIcons) {
131     mTabIcons = tabIcons;
132     mUnreadCounts = new int[tabIcons.length];
133   }
134 
setUnreadCount(int count, int position)135   public void setUnreadCount(int count, int position) {
136     if (mUnreadCounts == null || position >= mUnreadCounts.length) {
137       return;
138     }
139     mUnreadCounts[position] = count;
140   }
141 
addTabs(PagerAdapter adapter)142   private void addTabs(PagerAdapter adapter) {
143     mTabStrip.removeAllViews();
144 
145     final int count = adapter.getCount();
146     for (int i = 0; i < count; i++) {
147       addTab(adapter.getPageTitle(i), i);
148     }
149   }
150 
addTab(CharSequence tabTitle, final int position)151   private void addTab(CharSequence tabTitle, final int position) {
152     View tabView;
153     if (mTabIcons != null && position < mTabIcons.length) {
154       View layout = LayoutInflater.from(getContext()).inflate(R.layout.unread_count_tab, null);
155       View iconView = layout.findViewById(R.id.icon);
156       iconView.setBackgroundResource(mTabIcons[position]);
157       iconView.setContentDescription(tabTitle);
158       TextView textView = (TextView) layout.findViewById(R.id.count);
159       if (mUnreadCounts != null && mUnreadCounts[position] > 0) {
160         textView.setText(Integer.toString(mUnreadCounts[position]));
161         textView.setVisibility(View.VISIBLE);
162         iconView.setContentDescription(
163             getResources()
164                 .getQuantityString(
165                     R.plurals.tab_title_with_unread_items,
166                     mUnreadCounts[position],
167                     tabTitle.toString(),
168                     mUnreadCounts[position]));
169       } else {
170         textView.setVisibility(View.INVISIBLE);
171         iconView.setContentDescription(getResources().getString(R.string.tab_title, tabTitle));
172       }
173       tabView = layout;
174     } else {
175       final TextView textView = new TextView(getContext());
176       textView.setText(tabTitle);
177       textView.setBackgroundResource(R.drawable.view_pager_tab_background);
178 
179       // Assign various text appearance related attributes to child views.
180       if (mTextStyle > 0) {
181         textView.setTypeface(textView.getTypeface(), mTextStyle);
182       }
183       if (mTextSize > 0) {
184         textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
185       }
186       if (mTextColor != null) {
187         textView.setTextColor(mTextColor);
188       }
189       textView.setAllCaps(mTextAllCaps);
190       textView.setGravity(Gravity.CENTER);
191 
192       tabView = textView;
193     }
194 
195     tabView.setOnClickListener(
196         new OnClickListener() {
197           @Override
198           public void onClick(View v) {
199             mPager.setCurrentItem(getRtlPosition(position));
200           }
201         });
202 
203     tabView.setOnLongClickListener(new OnTabLongClickListener(position));
204 
205     tabView.setPadding(mSidePadding, 0, mSidePadding, 0);
206 
207     mTabStrip.addView(
208         tabView,
209         position,
210         new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT, 1));
211 
212     // Default to the first child being selected
213     if (position == 0) {
214       mPrevSelected = 0;
215       tabView.setSelected(true);
216     }
217   }
218 
219   /**
220    * Remove a tab at a certain index.
221    *
222    * @param index The index of the tab view we wish to remove.
223    */
removeTab(int index)224   public void removeTab(int index) {
225     View view = mTabStrip.getChildAt(index);
226     if (view != null) {
227       mTabStrip.removeView(view);
228     }
229   }
230 
231   /**
232    * Refresh a tab at a certain index by removing it and reconstructing it.
233    *
234    * @param index The index of the tab view we wish to update.
235    */
updateTab(int index)236   public void updateTab(int index) {
237     removeTab(index);
238 
239     if (index < mPager.getAdapter().getCount()) {
240       addTab(mPager.getAdapter().getPageTitle(index), index);
241     }
242   }
243 
244   @Override
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)245   public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
246     position = getRtlPosition(position);
247     int tabStripChildCount = mTabStrip.getChildCount();
248     if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
249       return;
250     }
251 
252     mTabStrip.onPageScrolled(position, positionOffset, positionOffsetPixels);
253   }
254 
255   @Override
onPageSelected(int position)256   public void onPageSelected(int position) {
257     position = getRtlPosition(position);
258     int tabStripChildCount = mTabStrip.getChildCount();
259     if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
260       return;
261     }
262 
263     if (mPrevSelected >= 0 && mPrevSelected < tabStripChildCount) {
264       mTabStrip.getChildAt(mPrevSelected).setSelected(false);
265     }
266     final View selectedChild = mTabStrip.getChildAt(position);
267     selectedChild.setSelected(true);
268 
269     // Update scroll position
270     final int scrollPos = selectedChild.getLeft() - (getWidth() - selectedChild.getWidth()) / 2;
271     smoothScrollTo(scrollPos, 0);
272     mPrevSelected = position;
273   }
274 
275   @Override
onPageScrollStateChanged(int state)276   public void onPageScrollStateChanged(int state) {}
277 
getRtlPosition(int position)278   private int getRtlPosition(int position) {
279     if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
280       return mTabStrip.getChildCount() - 1 - position;
281     }
282     return position;
283   }
284 
285   /** Simulates actionbar tab behavior by showing a toast with the tab title when long clicked. */
286   private class OnTabLongClickListener implements OnLongClickListener {
287 
288     final int mPosition;
289 
OnTabLongClickListener(int position)290     public OnTabLongClickListener(int position) {
291       mPosition = position;
292     }
293 
294     @Override
onLongClick(View v)295     public boolean onLongClick(View v) {
296       final int[] screenPos = new int[2];
297       getLocationOnScreen(screenPos);
298 
299       final Context context = getContext();
300       final int width = getWidth();
301       final int height = getHeight();
302       final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
303 
304       Toast toast =
305           Toast.makeText(context, mPager.getAdapter().getPageTitle(mPosition), Toast.LENGTH_SHORT);
306 
307       // Show the toast under the tab
308       toast.setGravity(
309           Gravity.TOP | Gravity.CENTER_HORIZONTAL,
310           (screenPos[0] + width / 2) - screenWidth / 2,
311           screenPos[1] + height);
312 
313       toast.show();
314       return true;
315     }
316   }
317 }
318