com.github.shareme.gwsmaterialuikit.library.material.widget.TabPageIndicator.java Source code

Java tutorial

Introduction

Here is the source code for com.github.shareme.gwsmaterialuikit.library.material.widget.TabPageIndicator.java

Source

/*
 * Copyright 2015 Rey Pham.
 * Modifications Copyright(C) 2016 Fred Grott
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.github.shareme.gwsmaterialuikit.library.material.widget;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.github.shareme.gwsmaterialuikit.library.R;
import com.github.shareme.gwsmaterialuikit.library.material.app.ThemeManager;
import com.github.shareme.gwsmaterialuikit.library.material.drawable.RippleDrawable;
import com.github.shareme.gwsmaterialuikit.library.material.util.ThemeUtil;
import com.github.shareme.gwsmaterialuikit.library.material.util.ViewUtil;

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class TabPageIndicator extends HorizontalScrollView
        implements ViewPager.OnPageChangeListener, View.OnClickListener, ThemeManager.OnThemeChangedListener {

    protected int mStyleId;
    protected int mCurrentStyle = ThemeManager.THEME_UNDEFINED;

    private TabContainerLayout mTabContainer;
    private ViewPager mViewPager;

    private int mMode;
    private int mTabPadding;
    private int mTabRippleStyle;
    private int mTextAppearance;
    private boolean mTabSingleLine;

    private int mIndicatorOffset;
    private int mIndicatorWidth;
    private int mIndicatorHeight;
    private boolean mIndicatorAtTop;

    private Paint mPaint;

    public static final int MODE_SCROLL = 0;
    public static final int MODE_FIXED = 1;

    private int mSelectedPosition;
    private boolean mScrolling;
    private boolean mIsRtl;

    private Runnable mTabAnimSelector;

    private ViewPager.OnPageChangeListener mListener;

    private DataSetObserver mObserver = new DataSetObserver() {

        @Override
        public void onChanged() {
            notifyDataSetChanged();
        }

        @Override
        public void onInvalidated() {
            notifyDataSetInvalidated();
        }

    };

    public TabPageIndicator(Context context) {
        super(context);

        init(context, null, 0, 0);
    }

    public TabPageIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);

        init(context, attrs, 0, 0);
    }

    public TabPageIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init(context, attrs, defStyleAttr, 0);
    }

    protected void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        setHorizontalScrollBarEnabled(false);

        mTabPadding = -1;
        mTabSingleLine = true;
        mIndicatorHeight = -1;
        mIndicatorAtTop = false;
        mScrolling = false;
        mIsRtl = false;

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(ThemeUtil.colorAccent(context, 0xFFFFFFFF));

        mTabContainer = new TabContainerLayout(context);

        applyStyle(context, attrs, defStyleAttr, defStyleRes);

        if (isInEditMode())
            addTemporaryTab();

        if (!isInEditMode())
            mStyleId = ThemeManager.getStyleId(context, attrs, defStyleAttr, defStyleRes);
    }

    public void applyStyle(int resId) {
        ViewUtil.applyStyle(this, resId);
        applyStyle(getContext(), null, 0, resId);
    }

    protected void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabPageIndicator, defStyleAttr,
                defStyleRes);

        int textAppearance = 0;
        int mode = -1;
        int rippleStyle = 0;

        for (int i = 0, count = a.getIndexCount(); i < count; i++) {
            int attr = a.getIndex(i);
            if (attr == R.styleable.TabPageIndicator_tpi_tabPadding)
                mTabPadding = a.getDimensionPixelSize(attr, 0);
            else if (attr == R.styleable.TabPageIndicator_tpi_tabRipple)
                rippleStyle = a.getResourceId(attr, 0);
            else if (attr == R.styleable.TabPageIndicator_tpi_indicatorColor)
                mPaint.setColor(a.getColor(attr, 0));
            else if (attr == R.styleable.TabPageIndicator_tpi_indicatorHeight)
                mIndicatorHeight = a.getDimensionPixelSize(attr, 0);
            else if (attr == R.styleable.TabPageIndicator_tpi_indicatorAtTop)
                mIndicatorAtTop = a.getBoolean(attr, true);
            else if (attr == R.styleable.TabPageIndicator_tpi_tabSingleLine)
                mTabSingleLine = a.getBoolean(attr, true);
            else if (attr == R.styleable.TabPageIndicator_android_textAppearance)
                textAppearance = a.getResourceId(attr, 0);
            else if (attr == R.styleable.TabPageIndicator_tpi_mode)
                mode = a.getInteger(attr, 0);
        }

        a.recycle();

        if (mTabPadding < 0)
            mTabPadding = ThemeUtil.dpToPx(context, 12);

        if (mIndicatorHeight < 0)
            mIndicatorHeight = ThemeUtil.dpToPx(context, 2);

        if (mode >= 0) {
            if (mMode != mode || getChildCount() == 0) {
                mMode = mode;
                removeAllViews();
                if (mMode == MODE_SCROLL) {
                    addView(mTabContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                            ViewGroup.LayoutParams.MATCH_PARENT));
                    setFillViewport(false);
                } else if (mMode == MODE_FIXED) {
                    addView(mTabContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT));
                    setFillViewport(true);
                }
            }
        }

        if (textAppearance != 0 && mTextAppearance != textAppearance) {
            mTextAppearance = textAppearance;
            for (int i = 0, count = mTabContainer.getChildCount(); i < count; i++) {
                CheckedTextView tv = (CheckedTextView) mTabContainer.getChildAt(i);
                tv.setTextAppearance(context, mTextAppearance);
            }
        }

        if (rippleStyle != 0 && rippleStyle != mTabRippleStyle) {
            mTabRippleStyle = rippleStyle;
            for (int i = 0, count = mTabContainer.getChildCount(); i < count; i++)
                ViewUtil.setBackground(mTabContainer.getChildAt(i),
                        new RippleDrawable.Builder(getContext(), mTabRippleStyle).build());
        }

        if (mViewPager != null)
            notifyDataSetChanged();
        requestLayout();
    }

    @Override
    public void onThemeChanged(ThemeManager.OnThemeChangedEvent event) {
        int style = ThemeManager.getInstance().getCurrentStyle(mStyleId);
        if (mCurrentStyle != style) {
            mCurrentStyle = style;
            applyStyle(mCurrentStyle);
        }
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        // Re-post the selector we saved
        if (mTabAnimSelector != null)
            post(mTabAnimSelector);

        if (mStyleId != 0) {
            ThemeManager.getInstance().registerOnThemeChangedListener(this);
            onThemeChanged(null);
        }
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mTabAnimSelector != null)
            removeCallbacks(mTabAnimSelector);

        if (mStyleId != 0)
            ThemeManager.getInstance().unregisterOnThemeChangedListener(this);
    }

    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        boolean rtl = layoutDirection == LAYOUT_DIRECTION_RTL;
        if (mIsRtl != rtl) {
            mIsRtl = rtl;
            invalidate();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int ws = widthMeasureSpec;
        if (ws != MeasureSpec.UNSPECIFIED)
            ws = MeasureSpec.makeMeasureSpec(widthSize - getPaddingLeft() - getPaddingRight(), widthMode);

        int hs = heightMeasureSpec;
        if (heightMode != MeasureSpec.UNSPECIFIED)
            hs = MeasureSpec.makeMeasureSpec(heightSize - getPaddingTop() - getPaddingBottom(), heightMode);

        mTabContainer.measure(ws, hs);

        int width = 0;
        switch (widthMode) {
        case MeasureSpec.UNSPECIFIED:
            width = mTabContainer.getMeasuredWidth() + getPaddingLeft() + getPaddingRight();
            break;
        case MeasureSpec.AT_MOST:
            width = Math.min(mTabContainer.getMeasuredWidth() + getPaddingLeft() + getPaddingRight(), widthSize);
            break;
        case MeasureSpec.EXACTLY:
            width = widthSize;
            break;
        }

        int height = 0;
        switch (heightMode) {
        case MeasureSpec.UNSPECIFIED:
            height = mTabContainer.getMeasuredHeight() + getPaddingTop() + getPaddingBottom();
            break;
        case MeasureSpec.AT_MOST:
            height = Math.min(mTabContainer.getMeasuredHeight() + getPaddingTop() + getPaddingBottom(), heightSize);
            break;
        case MeasureSpec.EXACTLY:
            height = heightSize;
            break;
        }

        if (mTabContainer.getMeasuredWidth() != width - getPaddingLeft() - getPaddingRight()
                || mTabContainer.getMeasuredHeight() != height - getPaddingTop() - getPaddingBottom())
            mTabContainer.measure(
                    MeasureSpec.makeMeasureSpec(width - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(height - getPaddingTop() - getPaddingBottom(),
                            MeasureSpec.EXACTLY));

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        TextView tv = getTabView(mSelectedPosition);
        if (tv != null)
            updateIndicator(tv.getLeft(), tv.getMeasuredWidth());
    }

    private CheckedTextView getTabView(int position) {
        return (CheckedTextView) mTabContainer.getChildAt(position);
    }

    private void animateToTab(final int position) {
        if (getTabView(position) == null)
            return;

        if (mTabAnimSelector != null)
            removeCallbacks(mTabAnimSelector);

        mTabAnimSelector = new Runnable() {
            public void run() {
                CheckedTextView tv = getTabView(position);
                if (tv != null) {
                    if (!mScrolling)
                        updateIndicator(tv.getLeft(), tv.getMeasuredWidth());

                    smoothScrollTo(tv.getLeft() - (getWidth() - tv.getWidth()) / 2 + getPaddingLeft(), 0);
                }
                mTabAnimSelector = null;
            }
        };

        post(mTabAnimSelector);
    }

    /**
     * Set a listener will be called when the current page is changed.
     * @param listener The {@link ViewPager.OnPageChangeListener} will be called.
     */
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
        mListener = listener;
    }

    /**
     * Set the ViewPager associate with this indicator view.
     * @param view The ViewPager view.
     */
    public void setViewPager(@Nullable ViewPager view) {
        if (mViewPager == view)
            return;

        if (mViewPager != null) {
            mViewPager.removeOnPageChangeListener(this);
            PagerAdapter adapter = mViewPager.getAdapter();
            if (adapter != null)
                adapter.unregisterDataSetObserver(mObserver);
        }

        mViewPager = view;

        if (mViewPager != null) {
            PagerAdapter adapter = mViewPager.getAdapter();
            if (adapter == null)
                throw new IllegalStateException("ViewPager does not have adapter instance.");

            adapter.registerDataSetObserver(mObserver);

            mViewPager.addOnPageChangeListener(this);

            notifyDataSetChanged();
            onPageSelected(mViewPager.getCurrentItem());
        } else
            mTabContainer.removeAllViews();
    }

    /**
     * Set the ViewPager associate with this indicator view and the current position;
     * @param view The ViewPager view.
     * @param initialPosition The current position.
     */
    public void setViewPager(ViewPager view, int initialPosition) {
        setViewPager(view);
        setCurrentItem(initialPosition);
    }

    private void updateIndicator(int offset, int width) {
        mIndicatorOffset = offset;
        mIndicatorWidth = width;
        invalidate();
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        super.draw(canvas);

        int x = mIndicatorOffset + getPaddingLeft();
        int y = mIndicatorAtTop ? 0 : getHeight() - mIndicatorHeight;
        canvas.drawRect(x, y, x + mIndicatorWidth, y + mIndicatorHeight, mPaint);

        if (isInEditMode())
            canvas.drawRect(getPaddingLeft(), y, getPaddingLeft() + mTabContainer.getChildAt(0).getWidth(),
                    y + mIndicatorHeight, mPaint);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            mScrolling = false;
            TextView tv = getTabView(mSelectedPosition);
            if (tv != null) {
                updateIndicator(tv.getLeft(), tv.getMeasuredWidth());
            }
        } else
            mScrolling = true;

        if (mListener != null)
            mListener.onPageScrollStateChanged(state);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mListener != null)
            mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);

        CheckedTextView tv_scroll = getTabView(position);
        CheckedTextView tv_next = getTabView(position + 1);

        if (tv_scroll != null && tv_next != null) {
            int width_scroll = tv_scroll.getMeasuredWidth();
            int width_next = tv_next.getMeasuredWidth();
            float distance = (width_scroll + width_next) / 2f;

            int width = (int) (width_scroll + (width_next - width_scroll) * positionOffset + 0.5f);
            int offset = (int) (tv_scroll.getLeft() + width_scroll / 2f + distance * positionOffset - width / 2f
                    + 0.5f);
            updateIndicator(offset, width);
        }
    }

    @Override
    public void onPageSelected(int position) {
        setCurrentItem(position);
        if (mListener != null)
            mListener.onPageSelected(position);
    }

    @Override
    public void onClick(View v) {
        int position = (Integer) v.getTag();
        if (position == mSelectedPosition && mListener != null)
            mListener.onPageSelected(position);

        mViewPager.setCurrentItem(position, true);
    }

    /**
     * Set the current page of this TabPageIndicator.
     * @param position The position of current page.
     */
    public void setCurrentItem(int position) {
        if (mSelectedPosition != position) {
            CheckedTextView tv = getTabView(mSelectedPosition);
            if (tv != null)
                tv.setChecked(false);
        }

        mSelectedPosition = position;
        CheckedTextView tv = getTabView(mSelectedPosition);
        if (tv != null)
            tv.setChecked(true);

        animateToTab(position);
    }

    private void notifyDataSetChanged() {
        mTabContainer.removeAllViews();

        PagerAdapter adapter = mViewPager.getAdapter();
        final int count = adapter.getCount();

        if (mSelectedPosition > count)
            mSelectedPosition = count - 1;

        for (int i = 0; i < count; i++) {
            CharSequence title = adapter.getPageTitle(i);
            if (title == null)
                title = "NULL";

            CheckedTextView tv = new CheckedTextView(getContext());
            tv.setCheckMarkDrawable(null);
            tv.setText(title);
            tv.setGravity(Gravity.CENTER);
            tv.setTextAppearance(getContext(), mTextAppearance);
            if (mTabSingleLine)
                tv.setSingleLine(true);
            else {
                tv.setSingleLine(false);
                tv.setMaxLines(2);
            }
            tv.setEllipsize(TruncateAt.END);
            tv.setOnClickListener(this);
            tv.setTag(i);
            if (mTabRippleStyle > 0)
                ViewUtil.setBackground(tv, new RippleDrawable.Builder(getContext(), mTabRippleStyle).build());

            tv.setPadding(mTabPadding, 0, mTabPadding, 0);
            mTabContainer.addView(tv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.MATCH_PARENT));
        }

        setCurrentItem(mSelectedPosition);
        requestLayout();
    }

    private void notifyDataSetInvalidated() {
        PagerAdapter adapter = mViewPager.getAdapter();
        final int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            TextView tv = getTabView(i);

            if (tv != null) {
                CharSequence title = adapter.getPageTitle(i);
                if (title == null)
                    title = "NULL";

                tv.setText(title);
            }
        }

        requestLayout();
    }

    private void addTemporaryTab() {
        for (int i = 0; i < 3; i++) {
            CharSequence title = null;
            if (i == 0)
                title = "TAB ONE";
            else if (i == 1)
                title = "TAB TWO";
            else if (i == 2)
                title = "TAB THREE";

            CheckedTextView tv = new CheckedTextView(getContext());
            tv.setCheckMarkDrawable(null);
            tv.setText(title);
            tv.setGravity(Gravity.CENTER);
            tv.setTextAppearance(getContext(), mTextAppearance);
            tv.setSingleLine(true);
            tv.setEllipsize(TruncateAt.END);
            tv.setTag(i);
            tv.setChecked(i == 0);
            if (mMode == MODE_SCROLL) {
                tv.setPadding(mTabPadding, 0, mTabPadding, 0);
                mTabContainer.addView(tv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
            } else if (mMode == MODE_FIXED) {
                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,
                        LinearLayout.LayoutParams.MATCH_PARENT);
                params.weight = 1f;
                mTabContainer.addView(tv, params);
            }
        }
    }

    private class TabContainerLayout extends FrameLayout {

        public TabContainerLayout(Context context) {
            super(context);
        }

        @SuppressWarnings("Range")
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);

            int width = 0;
            int height = 0;

            if (mMode == MODE_SCROLL) {
                int ws = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    child.measure(ws, heightMeasureSpec);
                    width += child.getMeasuredWidth();
                    height = Math.max(height, child.getMeasuredHeight());
                }
                setMeasuredDimension(width, height);
            } else {
                if (widthMode != MeasureSpec.EXACTLY) {
                    int ws = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
                    for (int i = 0; i < getChildCount(); i++) {
                        View child = getChildAt(i);
                        child.measure(ws, heightMeasureSpec);
                        width += child.getMeasuredWidth();
                        height = Math.max(height, child.getMeasuredHeight());
                    }

                    if (widthMode == MeasureSpec.UNSPECIFIED || width < widthSize)
                        setMeasuredDimension(widthSize, height);
                    else {
                        int count = getChildCount();
                        int childWidth = count == 0 ? 0 : widthSize / count;
                        for (int i = 0; i < count; i++) {
                            View child = getChildAt(i);
                            if (i != count - 1)
                                child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                                        heightMeasureSpec);
                            else
                                child.measure(MeasureSpec.makeMeasureSpec(widthSize - childWidth * (count - 1),
                                        MeasureSpec.EXACTLY), heightMeasureSpec);
                        }
                        setMeasuredDimension(widthSize, height);
                    }
                } else {
                    int count = getChildCount();
                    int childWidth = count == 0 ? 0 : widthSize / count;
                    for (int i = 0; i < count; i++) {
                        View child = getChildAt(i);
                        if (i != count - 1)
                            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                                    heightMeasureSpec);
                        else
                            child.measure(MeasureSpec.makeMeasureSpec(widthSize - childWidth * (count - 1),
                                    MeasureSpec.EXACTLY), heightMeasureSpec);
                        height = Math.max(height, child.getMeasuredHeight());
                    }
                    setMeasuredDimension(widthSize, height);
                }
            }

            int hs = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (child.getMeasuredHeight() != height)
                    child.measure(MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY), hs);
            }
        }

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            int childLeft = 0;
            int childTop = 0;
            int childRight = right - left;
            int childBottom = bottom - top;

            if (mIsRtl)
                for (int i = 0, count = getChildCount(); i < count; i++) {
                    View child = getChildAt(i);
                    child.layout(childRight - child.getMeasuredWidth(), childTop, childRight, childBottom);
                    childRight -= child.getMeasuredWidth();
                }
            else
                for (int i = 0, count = getChildCount(); i < count; i++) {
                    View child = getChildAt(i);
                    child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childBottom);
                    childLeft += child.getMeasuredWidth();
                }
        }
    }
}