1 /* 2 * Copyright (C) 2016 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 17 package com.android.settings.widget; 18 19 import android.content.Context; 20 import android.support.v4.view.PagerAdapter; 21 import android.util.AttributeSet; 22 import android.view.Gravity; 23 import android.view.LayoutInflater; 24 import android.view.View; 25 import android.widget.FrameLayout; 26 import android.widget.LinearLayout; 27 import android.widget.TextView; 28 29 import com.android.settings.R; 30 31 /** 32 * To be used with ViewPager to provide a tab indicator component which give constant feedback as 33 * to the user's scroll progress. 34 */ 35 public final class SlidingTabLayout extends FrameLayout implements View.OnClickListener { 36 37 private final LinearLayout mTitleView; 38 private final View mIndicatorView; 39 private final LayoutInflater mLayoutInflater; 40 41 private RtlCompatibleViewPager mViewPager; 42 private int mSelectedPosition; 43 private float mSelectionOffset; 44 SlidingTabLayout(Context context, AttributeSet attrs)45 public SlidingTabLayout(Context context, AttributeSet attrs) { 46 super(context, attrs); 47 mLayoutInflater = LayoutInflater.from(context); 48 mTitleView = new LinearLayout(context); 49 mTitleView.setGravity(Gravity.CENTER_HORIZONTAL); 50 mIndicatorView = mLayoutInflater.inflate(R.layout.sliding_tab_indicator_view, this, false); 51 52 addView(mTitleView, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 53 addView(mIndicatorView, mIndicatorView.getLayoutParams()); 54 } 55 56 /** 57 * Sets the associated view pager. Note that the assumption here is that the pager content 58 * (number of tabs and tab titles) does not change after this call has been made. 59 */ setViewPager(RtlCompatibleViewPager viewPager)60 public void setViewPager(RtlCompatibleViewPager viewPager) { 61 mTitleView.removeAllViews(); 62 63 mViewPager = viewPager; 64 if (viewPager != null) { 65 viewPager.addOnPageChangeListener(new InternalViewPagerListener()); 66 populateTabStrip(); 67 } 68 } 69 70 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)71 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 72 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 73 final int titleCount = mTitleView.getChildCount(); 74 if (titleCount > 0) { 75 final int width = MeasureSpec.makeMeasureSpec( 76 mTitleView.getMeasuredWidth() / titleCount, MeasureSpec.EXACTLY); 77 final int height = MeasureSpec.makeMeasureSpec( 78 mIndicatorView.getMeasuredHeight(), MeasureSpec.EXACTLY); 79 mIndicatorView.measure(width, height); 80 } 81 } 82 83 @Override onLayout(boolean changed, int left, int top, int right, int bottom)84 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 85 if (mTitleView.getChildCount() > 0) { 86 mTitleView.layout(0, 0, mTitleView.getMeasuredWidth(), mTitleView.getMeasuredHeight()); 87 final int indicatorBottom = getMeasuredHeight(); 88 final int indicatorHeight = mIndicatorView.getMeasuredHeight(); 89 final int indicatorWidth = mIndicatorView.getMeasuredWidth(); 90 final int totalWidth = getMeasuredWidth(); 91 92 // IndicatorView should start on the right when RTL mode is enabled 93 if (isRtlMode()) { 94 mIndicatorView.layout(totalWidth - indicatorWidth, 95 indicatorBottom - indicatorHeight, totalWidth, 96 indicatorBottom); 97 } else { 98 99 mIndicatorView.layout(0, indicatorBottom - indicatorHeight, 100 indicatorWidth, indicatorBottom); 101 } 102 } 103 } 104 105 @Override onClick(View v)106 public void onClick(View v) { 107 final int titleCount = mTitleView.getChildCount(); 108 for (int i = 0; i < titleCount; i++) { 109 if (v == mTitleView.getChildAt(i)) { 110 mViewPager.setCurrentItem(i); 111 return; 112 } 113 } 114 } 115 onViewPagerPageChanged(int position, float positionOffset)116 private void onViewPagerPageChanged(int position, float positionOffset) { 117 mSelectedPosition = position; 118 mSelectionOffset = positionOffset; 119 // Translation should be reversed in RTL mode 120 final int leftIndicator = isRtlMode() ? -getIndicatorLeft() : getIndicatorLeft(); 121 mIndicatorView.setTranslationX(leftIndicator); 122 } 123 populateTabStrip()124 private void populateTabStrip() { 125 final PagerAdapter adapter = mViewPager.getAdapter(); 126 127 for (int i = 0; i < adapter.getCount(); i++) { 128 final TextView tabTitleView = (TextView) mLayoutInflater.inflate( 129 R.layout.sliding_tab_title_view, mTitleView, false); 130 131 tabTitleView.setText(adapter.getPageTitle(i)); 132 tabTitleView.setOnClickListener(this); 133 134 mTitleView.addView(tabTitleView); 135 tabTitleView.setSelected(i == mViewPager.getCurrentItem()); 136 } 137 } 138 getIndicatorLeft()139 private int getIndicatorLeft() { 140 View selectedTitle = mTitleView.getChildAt(mSelectedPosition); 141 int left = selectedTitle.getLeft(); 142 if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { 143 View nextTitle = mTitleView.getChildAt(mSelectedPosition + 1); 144 left = (int) (mSelectionOffset * nextTitle.getLeft() 145 + (1.0f - mSelectionOffset) * left); 146 } 147 return left; 148 } 149 isRtlMode()150 private boolean isRtlMode() { 151 return getLayoutDirection() == LAYOUT_DIRECTION_RTL; 152 } 153 154 private final class InternalViewPagerListener implements 155 RtlCompatibleViewPager.OnPageChangeListener { 156 private int mScrollState; 157 158 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)159 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 160 final int titleCount = mTitleView.getChildCount(); 161 if ((titleCount == 0) || (position < 0) || (position >= titleCount)) { 162 return; 163 } 164 onViewPagerPageChanged(position, positionOffset); 165 } 166 167 @Override onPageScrollStateChanged(int state)168 public void onPageScrollStateChanged(int state) { 169 mScrollState = state; 170 } 171 172 @Override onPageSelected(int position)173 public void onPageSelected(int position) { 174 position = mViewPager.getRtlAwareIndex(position); 175 if (mScrollState == RtlCompatibleViewPager.SCROLL_STATE_IDLE) { 176 onViewPagerPageChanged(position, 0f); 177 } 178 final int titleCount = mTitleView.getChildCount(); 179 for (int i = 0; i < titleCount; i++) { 180 mTitleView.getChildAt(i).setSelected(position == i); 181 } 182 } 183 } 184 } 185