package carbon.widget; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.widget.LinearLayout; import carbon.Carbon; import carbon.R; import carbon.animation.AnimatedColorStateList; import carbon.drawable.DefaultPrimaryColorStateList; public class PagerTabStrip extends android.widget.HorizontalScrollView implements TintedView, VisibleView { // TODO: extend carbon.widget.HorizontalScrollView ViewPager viewPager; private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); LinearLayout content; private float indicatorPos = 0; private int selectedPage = 0; private float indicatorPos2 = 0; float indicatorHeight; DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); boolean fixed = false; private ValueAnimator animator, animator2; private ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { position = Math.round(position + positionOffset); if (position != selectedPage) { View view = content.getChildAt(position); if (view == null) return; // TODO: what's really going on here? #130 if (animator != null) animator.cancel(); if (animator2 != null) animator2.cancel(); animator = ValueAnimator.ofFloat(indicatorPos, view.getLeft()); animator.setDuration(200); if (position > selectedPage) animator.setStartDelay(100); animator.setInterpolator(decelerateInterpolator); animator.addUpdateListener(animation -> { indicatorPos = (float) animation.getAnimatedValue(); postInvalidate(); }); animator.start(); animator2 = ValueAnimator.ofFloat(indicatorPos2, view.getRight()); animator2.setDuration(200); if (position < selectedPage) animator2.setStartDelay(100); animator2.setInterpolator(decelerateInterpolator); animator2.addUpdateListener(animation -> { indicatorPos2 = (float) animation.getAnimatedValue(); postInvalidate(); }); animator2.start(); setSelectedPage(position); if (content.getChildAt(selectedPage).getLeft() - getScrollX() < 0) { smoothScrollTo(content.getChildAt(selectedPage).getLeft(), 0); } else if (content.getChildAt(selectedPage).getRight() - getScrollX() > getWidth()) { smoothScrollTo(content.getChildAt(selectedPage).getRight() - getWidth(), 0); } } } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { } }; private TabBuilder tabBuilder; public PagerTabStrip(Context context) { super(context, null, R.attr.carbon_pagerTabStripStyle); initPagerTabStrip(null, R.attr.carbon_pagerTabStripStyle); } public PagerTabStrip(Context context, AttributeSet attrs) { super(Carbon.getThemedContext(context, attrs, R.styleable.PagerTabStrip, R.attr.carbon_pagerTabStripStyle, R.styleable.PagerTabStrip_carbon_theme), attrs, R.attr.carbon_pagerTabStripStyle); initPagerTabStrip(attrs, R.attr.carbon_pagerTabStripStyle); } public PagerTabStrip(Context context, AttributeSet attrs, int defStyleAttr) { super(Carbon.getThemedContext(context, attrs, R.styleable.PagerTabStrip, defStyleAttr, R.styleable.PagerTabStrip_carbon_theme), attrs, defStyleAttr); initPagerTabStrip(attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public PagerTabStrip(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(Carbon.getThemedContext(context, attrs, R.styleable.PagerTabStrip, defStyleAttr, R.styleable.PagerTabStrip_carbon_theme), attrs, defStyleAttr, defStyleRes); initPagerTabStrip(attrs, defStyleAttr); } private static int[] tintIds = new int[]{ R.styleable.PagerTabStrip_carbon_tint, R.styleable.PagerTabStrip_carbon_tintMode, R.styleable.PagerTabStrip_carbon_backgroundTint, R.styleable.PagerTabStrip_carbon_backgroundTintMode, R.styleable.PagerTabStrip_carbon_animateColorChanges }; private void initPagerTabStrip(AttributeSet attrs, int defStyleAttr) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PagerTabStrip, defStyleAttr, R.style.carbon_PagerTabStrip); setIndicatorHeight(a.getDimension(R.styleable.PagerTabStrip_carbon_indicatorWidth, 2)); setFixed(a.getBoolean(R.styleable.PagerTabStrip_carbon_fixedTabs, true)); Carbon.initTint(this, a, tintIds); a.recycle(); setHorizontalFadingEdgeEnabled(false); setHorizontalScrollBarEnabled(false); content = new LinearLayout(getContext()); addView(content, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); initTabs(); } public void setViewPager(final ViewPager viewPager) { if (viewPager != null) viewPager.removeOnPageChangeListener(pageChangeListener); this.viewPager = viewPager; if (viewPager != null) viewPager.addOnPageChangeListener(pageChangeListener); initTabs(); } private void initTabs() { content.removeAllViews(); if (viewPager == null) return; final PagerAdapter adapter = viewPager.getAdapter(); if (viewPager.getAdapter() == null) return; if (tabBuilder == null) { tabBuilder = position -> { View tab = inflate(getContext(), R.layout.carbon_tab, null); ((TextView) tab.findViewById(R.id.carbon_tabText)).setText(getViewPager().getAdapter().getPageTitle(position).toString().toUpperCase()); return tab; }; } for (int i = 0; i < adapter.getCount(); i++) { View tab = tabBuilder.getView(i); content.addView(tab, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)); tab.setSelected(i == 0); final int finalI = i; tab.setOnClickListener(__ -> viewPager.setCurrentItem(finalI)); } } @Override public void draw(@NonNull Canvas canvas) { super.draw(canvas); if (content.getChildCount() == 0) return; if (indicatorPos == indicatorPos2) indicatorPos2 = content.getChildAt(selectedPage).getWidth(); paint.setColor(getTint().getColorForState(getDrawableState(), getTint().getDefaultColor())); canvas.drawRect(indicatorPos, getHeight() - indicatorHeight, indicatorPos2, getHeight(), paint); } public ViewPager getViewPager() { return viewPager; } public boolean isFixed() { return fixed; } public void setFixed(boolean fixed) { this.fixed = fixed; setFillViewport(fixed); } public float getIndicatorHeight() { return indicatorHeight; } public void setIndicatorHeight(float indicatorHeight) { this.indicatorHeight = indicatorHeight; postInvalidate(); } public void setTabBuilder(TabBuilder tabBuilder) { this.tabBuilder = tabBuilder; initTabs(); } public void setSelectedPage(int position) { if (viewPager == null) return; if (content.getChildCount() > selectedPage) content.getChildAt(selectedPage).setSelected(false); selectedPage = position; if (content.getChildCount() > selectedPage) content.getChildAt(selectedPage).setSelected(true); } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); return new SavedState(superState, selectedPage, getScrollX(), indicatorPos, indicatorPos2); } @Override protected void onRestoreInstanceState(Parcelable state) { final SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); setSelectedPage(savedState.getSelectedPage()); indicatorPos = savedState.getIndicatorPos(); indicatorPos2 = savedState.getIndicatorPos2(); post(new Runnable() { public void run() { setScrollX(savedState.getScroll()); } }); } @Override protected void dispatchSaveInstanceState(@NonNull SparseArray<Parcelable> container) { super.dispatchFreezeSelfOnly(container); } @Override protected void dispatchRestoreInstanceState(@NonNull SparseArray<Parcelable> container) { super.dispatchThawSelfOnly(container); } protected static class SavedState extends BaseSavedState { private final int selectedPage; private final int scroll; private final float indicatorPos; private final float indicatorPos2; private SavedState(Parcelable superState, int selectedPage, int scrollX, float indicatorPos, float indicatorPos2) { super(superState); this.selectedPage = selectedPage; this.scroll = scrollX; this.indicatorPos = indicatorPos; this.indicatorPos2 = indicatorPos2; } private SavedState(Parcel in) { super(in); selectedPage = in.readInt(); scroll = in.readInt(); indicatorPos = in.readFloat(); indicatorPos2 = in.readFloat(); } public int getSelectedPage() { return selectedPage; } public int getScroll() { return scroll; } public float getIndicatorPos() { return indicatorPos; } public float getIndicatorPos2() { return indicatorPos2; } @Override public void writeToParcel(@NonNull Parcel destination, int flags) { super.writeToParcel(destination, flags); destination.writeInt(selectedPage); destination.writeInt(scroll); destination.writeFloat(indicatorPos); destination.writeFloat(indicatorPos2); } public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } // ------------------------------- // tint // ------------------------------- ColorStateList tint; PorterDuff.Mode tintMode; ColorStateList backgroundTint; PorterDuff.Mode backgroundTintMode; boolean animateColorChanges; ValueAnimator.AnimatorUpdateListener tintAnimatorListener = animation -> { updateTint(); ViewCompat.postInvalidateOnAnimation(PagerTabStrip.this); }; ValueAnimator.AnimatorUpdateListener backgroundTintAnimatorListener = animation -> { updateBackgroundTint(); ViewCompat.postInvalidateOnAnimation(PagerTabStrip.this); }; @Override public void setTint(ColorStateList list) { this.tint = animateColorChanges && !(list instanceof AnimatedColorStateList) ? AnimatedColorStateList.fromList(list, tintAnimatorListener) : list; updateTint(); } @Override public void setTint(int color) { if (color == 0) { setTint(new DefaultPrimaryColorStateList(getContext())); } else { setTint(ColorStateList.valueOf(color)); } } @Override public ColorStateList getTint() { return tint; } private void updateTint() { if (tint == null) return; int color = tint.getColorForState(getDrawableState(), tint.getDefaultColor()); /*if (leftGlow != null) leftGlow.setColor(color); if (rightGlow != null) rightGlow.setColor(color); if (topGlow != null) topGlow.setColor(color); if (bottomGlow != null) bottomGlow.setColor(color); scrollBarDrawable = null;*/ } @Override public void setTintMode(@NonNull PorterDuff.Mode mode) { this.tintMode = mode; updateTint(); } @Override public PorterDuff.Mode getTintMode() { return tintMode; } @Override public void setBackgroundTint(ColorStateList list) { this.backgroundTint = animateColorChanges && !(list instanceof AnimatedColorStateList) ? AnimatedColorStateList.fromList(list, backgroundTintAnimatorListener) : list; updateBackgroundTint(); } @Override public void setBackgroundTint(int color) { if (color == 0) { setBackgroundTint(new DefaultPrimaryColorStateList(getContext())); } else { setBackgroundTint(ColorStateList.valueOf(color)); } } @Override public ColorStateList getBackgroundTint() { return backgroundTint; } private void updateBackgroundTint() { if (getBackground() == null) return; if (backgroundTint != null && backgroundTintMode != null) { int color = backgroundTint.getColorForState(getDrawableState(), backgroundTint.getDefaultColor()); getBackground().setColorFilter(new PorterDuffColorFilter(color, tintMode)); } else { getBackground().setColorFilter(null); } } @Override public void setBackgroundTintMode(@Nullable PorterDuff.Mode mode) { this.backgroundTintMode = mode; updateBackgroundTint(); } @Override public PorterDuff.Mode getBackgroundTintMode() { return backgroundTintMode; } public boolean isAnimateColorChangesEnabled() { return animateColorChanges; } public void setAnimateColorChangesEnabled(boolean animateColorChanges) { this.animateColorChanges = animateColorChanges; if (tint != null && !(tint instanceof AnimatedColorStateList)) setTint(AnimatedColorStateList.fromList(tint, tintAnimatorListener)); if (backgroundTint != null && !(backgroundTint instanceof AnimatedColorStateList)) setBackgroundTint(AnimatedColorStateList.fromList(backgroundTint, backgroundTintAnimatorListener)); } }