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 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 */ 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 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 84 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 85 if (mTitleView.getChildCount() > 0) { 86 final int indicatorBottom = getMeasuredHeight(); 87 final int indicatorHeight = mIndicatorView.getMeasuredHeight(); 88 final int indicatorWidth = mIndicatorView.getMeasuredWidth(); 89 final int totalWidth = getMeasuredWidth(); 90 final int leftPadding = getPaddingLeft(); 91 final int rightPadding = getPaddingRight(); 92 93 mTitleView.layout(leftPadding, 0, mTitleView.getMeasuredWidth() + rightPadding, 94 mTitleView.getMeasuredHeight()); 95 // IndicatorView should start on the right when RTL mode is enabled 96 if (isRtlMode()) { 97 mIndicatorView.layout(totalWidth - indicatorWidth, 98 indicatorBottom - indicatorHeight, totalWidth, 99 indicatorBottom); 100 } else { 101 mIndicatorView.layout(0, indicatorBottom - indicatorHeight, 102 indicatorWidth, indicatorBottom); 103 } 104 } 105 } 106 107 @Override 108 public void onClick(View v) { 109 final int titleCount = mTitleView.getChildCount(); 110 for (int i = 0; i < titleCount; i++) { 111 if (v == mTitleView.getChildAt(i)) { 112 mViewPager.setCurrentItem(i); 113 return; 114 } 115 } 116 } 117 118 private void onViewPagerPageChanged(int position, float positionOffset) { 119 mSelectedPosition = position; 120 mSelectionOffset = positionOffset; 121 // Translation should be reversed in RTL mode 122 final int leftIndicator = isRtlMode() ? -getIndicatorLeft() : getIndicatorLeft(); 123 mIndicatorView.setTranslationX(leftIndicator); 124 } 125 126 private void populateTabStrip() { 127 final PagerAdapter adapter = mViewPager.getAdapter(); 128 129 for (int i = 0; i < adapter.getCount(); i++) { 130 final TextView tabTitleView = (TextView) mLayoutInflater.inflate( 131 R.layout.sliding_tab_title_view, mTitleView, false); 132 133 tabTitleView.setText(adapter.getPageTitle(i)); 134 tabTitleView.setOnClickListener(this); 135 136 mTitleView.addView(tabTitleView); 137 tabTitleView.setSelected(i == mViewPager.getCurrentItem()); 138 } 139 } 140 141 private int getIndicatorLeft() { 142 View selectedTitle = mTitleView.getChildAt(mSelectedPosition); 143 int left = selectedTitle.getLeft(); 144 if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { 145 View nextTitle = mTitleView.getChildAt(mSelectedPosition + 1); 146 left = (int) (mSelectionOffset * nextTitle.getLeft() 147 + (1.0f - mSelectionOffset) * left); 148 } 149 return left; 150 } 151 152 private boolean isRtlMode() { 153 return getLayoutDirection() == LAYOUT_DIRECTION_RTL; 154 } 155 156 private final class InternalViewPagerListener implements 157 RtlCompatibleViewPager.OnPageChangeListener { 158 private int mScrollState; 159 160 @Override 161 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 162 final int titleCount = mTitleView.getChildCount(); 163 if ((titleCount == 0) || (position < 0) || (position >= titleCount)) { 164 return; 165 } 166 onViewPagerPageChanged(position, positionOffset); 167 } 168 169 @Override 170 public void onPageScrollStateChanged(int state) { 171 mScrollState = state; 172 } 173 174 @Override 175 public void onPageSelected(int position) { 176 position = mViewPager.getRtlAwareIndex(position); 177 if (mScrollState == RtlCompatibleViewPager.SCROLL_STATE_IDLE) { 178 onViewPagerPageChanged(position, 0f); 179 } 180 final int titleCount = mTitleView.getChildCount(); 181 for (int i = 0; i < titleCount; i++) { 182 mTitleView.getChildAt(i).setSelected(position == i); 183 } 184 } 185 } 186 } 187