Home | History | Annotate | Download | only in list
      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 
     88   public ViewPagerTabs(Context context) {
     89     this(context, null);
     90   }
     91 
     92   public ViewPagerTabs(Context context, AttributeSet attrs) {
     93     this(context, attrs, 0);
     94   }
     95 
     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 
    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    */
    130   public void configureTabIcons(int[] tabIcons) {
    131     mTabIcons = tabIcons;
    132     mUnreadCounts = new int[tabIcons.length];
    133   }
    134 
    135   public void setUnreadCount(int count, int position) {
    136     if (mUnreadCounts == null || position >= mUnreadCounts.length) {
    137       return;
    138     }
    139     mUnreadCounts[position] = count;
    140   }
    141 
    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 
    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    */
    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    */
    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
    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
    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
    276   public void onPageScrollStateChanged(int state) {}
    277 
    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 
    290     public OnTabLongClickListener(int position) {
    291       mPosition = position;
    292     }
    293 
    294     @Override
    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