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