NavigationTabBar 多彩Tab页

演示:

源码下载

代码:

MainActivity.java

package com.bzu.gxs.meunguide;

import android.app.Activity;
import android.graphics.Color;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;

public class MainActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initUI();
    }

    private void initUI() {

        final ViewPager viewPager = (ViewPager) findViewById(R.id.vp_horizontal_ntb);
        viewPager.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return 5;
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view.equals(object);
            }

            @Override
            public void destroyItem(final View container,final int position,final Object object) {
                ((ViewPager)container).removeView((View)object);
            }

            @Override
            public Object instantiateItem(final ViewGroup container,final int position) {
                final View  view = LayoutInflater.from(getBaseContext()).inflate(R.layout.activity_item,null,false);

                final TextView textView = (TextView) view.findViewById(R.id.txt_vp_item_page);
                textView.setText(String.format("界面 %d",position));
                container.addView(view);
                return view;
            }
        });

        //
        final String[] colors = getResources().getStringArray(R.array.default_preview);
        final NavigationTabBar navigationTabBar = (NavigationTabBar) findViewById(R.id.ntb_horizontal);
        final ArrayList<NavigationTabBar.Model> models = new ArrayList<>();

        models.add(new NavigationTabBar.Model(
                getResources().getDrawable(R.drawable.ic_first), Color.parseColor(colors[0]),"One"));
        models.add(new NavigationTabBar.Model(
                getResources().getDrawable(R.drawable.ic_second),Color.parseColor(colors[1]),"Two"));
        models.add(new NavigationTabBar.Model(
                getResources().getDrawable(R.drawable.ic_third),Color.parseColor(colors[2]),"Thirt"));
        models.add(new NavigationTabBar.Model(
                getResources().getDrawable(R.drawable.ic_fourth),Color.parseColor(colors[3]),"Fourth"));
        models.add(new NavigationTabBar.Model(
                getResources().getDrawable(R.drawable.ic_fifth),Color.parseColor(colors[4]),"Fifth"));

        navigationTabBar.setModels(models);
        navigationTabBar.setViewPager(viewPager,2);
        navigationTabBar.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(final int position) {
                navigationTabBar.getModels().get(position).hideBadge();
            }

            @Override
            public void onPageScrollStateChanged(final int state) {

            }
        });

        navigationTabBar.post(new Runnable() {
            @Override
            public void run() {
                final View bgNavigationTaBar = findViewById(R.id.bg_ntb_horizontal);
                bgNavigationTaBar.getLayoutParams().height = (int) navigationTabBar.getHeight();

            }
        });

        navigationTabBar.postDelayed(new Runnable() {
            @Override
            public void run() {
                for (int i=0; i<navigationTabBar.getModels().size() ; i++){
                    final NavigationTabBar.Model model = navigationTabBar.getModels().get(i);
                    switch (i){
                        case 0:
                            model.setBadgeTitle("Gxs1");
                            break;
                        case 1:
                            model.setBadgeTitle("Gxs2");
                            break;
                        case 2:
                            model.setBadgeTitle("Gxs3");
                            break;
                        case 3:
                            model.setBadgeTitle("Gxs4");
                            break;
                        case 4:
                            model.setBadgeTitle("Gxs5");
                            break;
                        default:
                            break;
                    }
                    navigationTabBar.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            model.showBadge();
                        }
                    },i * 100);
                }
            }
        },500);

    }
}

NavigationTabBar.java

package com.bzu.gxs.meunguide;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewPager;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.Scroller;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by GXS on 2016/5/7.
 */

public class NavigationTabBar extends View implements ViewPager.OnPageChangeListener {

    // NTP constants
    private final static String PREVIEW_BADGE = "0";
    private final static String PREVIEW_TITLE = "Title";
    private final static int INVALID_INDEX = -1;

    private final static int DEFAULT_BADGE_ANIMATION_DURATION = 200;
    private final static int DEFAULT_BADGE_REFRESH_ANIMATION_DURATION = 100;
    private final static int DEFAULT_ANIMATION_DURATION = 300;
    private final static int DEFAULT_INACTIVE_COLOR = Color.parseColor("#9f90af");
    private final static int DEFAULT_ACTIVE_COLOR = Color.WHITE;

    private final static float MIN_FRACTION = 0.0f;
    private final static float NON_SCALED_FRACTION = 0.35f;
    private final static float MAX_FRACTION = 1.0f;

    private final static int MIN_ALPHA = 0;
    private final static int MAX_ALPHA = 255;

    private final static float ACTIVE_ICON_SCALE_BY = 0.3f;
    private final static float ICON_SIZE_FRACTION = 0.45f;

    private final static float TITLE_ACTIVE_ICON_SCALE_BY = 0.2f;
    private final static float TITLE_ICON_SIZE_FRACTION = 0.45f;
    private final static float TITLE_ACTIVE_SCALE_BY = 0.2f;
    private final static float TITLE_SIZE_FRACTION = 0.2f;
    private final static float TITLE_MARGIN_FRACTION = 0.15f;

    private final static float BADGE_HORIZONTAL_FRACTION = 0.5f;
    private final static float BADGE_VERTICAL_FRACTION = 0.75f;
    private final static float BADGE_TITLE_SIZE_FRACTION = 0.85f;

    private final static int ALL_INDEX = 0;
    private final static int ACTIVE_INDEX = 1;

    private final static int LEFT_INDEX = 0;
    private final static int CENTER_INDEX = 1;
    private final static int RIGHT_INDEX = 2;

    private final static int TOP_INDEX = 0;
    private final static int BOTTOM_INDEX = 1;

    private final static float LEFT_FRACTION = 0.25f;
    private final static float CENTER_FRACTION = 0.5f;
    private final static float RIGHT_FRACTION = 0.75f;

    private final static Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
    private final static Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();

    // NTP and pointer bounds
    private final RectF mBounds = new RectF();
    private final RectF mPointerBounds = new RectF();
    // Badge bounds and bg badge bounds
    private final Rect mBadgeBounds = new Rect();
    private final RectF mBgBadgeBounds = new RectF();

    // Canvas, where all of other canvas will be merged
    private Bitmap mBitmap;
    private Canvas mCanvas;

    // Canvas with icons
    private Bitmap mIconsBitmap;
    private Canvas mIconsCanvas;

    // Canvas for our rect pointer
    private Bitmap mPointerBitmap;
    private Canvas mPointerCanvas;

    // Main paint
    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG) {
        {
            setDither(true);
            setStyle(Style.FILL);
        }
    };

    // Pointer paint
    private final Paint mPointerPaint = new Paint(Paint.ANTI_ALIAS_FLAG) {
        {
            setDither(true);
            setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        }
    };

    // Icons paint
    private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG) {
        {
            setDither(true);
        }
    };

    // Paint for icon mask pointer
    private final Paint mIconPointerPaint = new Paint(Paint.ANTI_ALIAS_FLAG) {
        {
            setStyle(Style.FILL);
            setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        }
    };

    // Paint for model title
    private final Paint mModelTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG) {
        {
            setDither(true);
            setColor(Color.WHITE);
            setTextAlign(Align.CENTER);
        }
    };

    // Paint for badge
    private final Paint mBadgePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG) {
        {
            setDither(true);
            setTextAlign(Align.CENTER);
            setFakeBoldText(true);
        }
    };

    // Variables for animator
    private final ValueAnimator mAnimator = new ValueAnimator();
    private final ResizeInterpolator mResizeInterpolator = new ResizeInterpolator();
    private int mAnimationDuration;

    // NTP models
    private List<Model> mModels = new ArrayList<>();

    // Variables for ViewPager
    private ViewPager mViewPager;
    private ViewPager.OnPageChangeListener mOnPageChangeListener;
    private int mScrollState;

    // Tab listener
    private OnTabBarSelectedIndexListener mOnTabBarSelectedIndexListener;
    private ValueAnimator.AnimatorListener mAnimatorListener;

    // Variables for sizes
    private float mModelSize;
    private int mIconSize;
    // Corners radius for rect mode
    private float mCornersRadius;

    // Model title size and margin
    private float mModelTitleSize;
    private float mTitleMargin;

    // Model badge title size and margin
    private float mBadgeMargin;
    private float mBadgeTitleSize;

    // Model title mode: active ar all
    private TitleMode mTitleMode;
    // Model badge position: left, center or right
    private BadgePosition mBadgePosition;
    // Model badge gravity: top or bottom
    private BadgeGravity mBadgeGravity;

    // Indexes
    private int mLastIndex = INVALID_INDEX;
    private int mIndex = INVALID_INDEX;
    // General fraction value
    private float mFraction;

    // Coordinates of pointer
    private float mStartPointerX;
    private float mEndPointerX;
    private float mPointerLeftTop;
    private float mPointerRightBottom;

    // Detect if model has title
    private boolean mIsTitled;
    // Detect if model has badge
    private boolean mIsBadged;
    // Detect if model icon scaled
    private boolean mIsScaled;
    // Detect if model badge have custom typeface
    private boolean mIsBadgeUseTypeface;
    // Detect if is bar mode or indicator pager mode
    private boolean mIsViewPagerMode;
    // Detect whether the horizontal orientation
    private boolean mIsHorizontalOrientation;
    // Detect if we move from left to right
    private boolean mIsResizeIn;
    // Detect if we get action down event
    private boolean mIsActionDown;
    // Detect if we get action down event on pointer
    private boolean mIsPointerActionDown;
    // Detect when we set index from tab bar nor from ViewPager
    private boolean mIsSetIndexFromTabBar;

    // Color variables
    private int mInactiveColor;
    private int mActiveColor;

    // Custom typeface
    private Typeface mTypeface;

    public NavigationTabBar(final Context context) {
        this(context, null);
    }

    public NavigationTabBar(final Context context, final AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NavigationTabBar(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //Init NTB

        // Always draw
        setWillNotDraw(false);
        // More speed!
        setLayerType(LAYER_TYPE_HARDWARE, null);

        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NavigationTabBar);
        try {
            setIsTitled(
                    typedArray.getBoolean(R.styleable.NavigationTabBar_ntb_titled, false)
            );
            setIsBadged(
                    typedArray.getBoolean(R.styleable.NavigationTabBar_ntb_badged, false)
            );
            setIsScaled(
                    typedArray.getBoolean(R.styleable.NavigationTabBar_ntb_scaled, true)
            );
            setIsBadgeUseTypeface(
                    typedArray.getBoolean(R.styleable.NavigationTabBar_ntb_badge_use_typeface, false)
            );
            setTitleMode(
                    typedArray.getInt(R.styleable.NavigationTabBar_ntb_title_mode, ALL_INDEX)
            );
            setBadgePosition(
                    typedArray.getInt(R.styleable.NavigationTabBar_ntb_badge_position, RIGHT_INDEX)
            );
            setBadgeGravity(
                    typedArray.getInt(R.styleable.NavigationTabBar_ntb_badge_gravity, TOP_INDEX)
            );
            setTypeface(
                    typedArray.getString(R.styleable.NavigationTabBar_ntb_typeface)
            );
            setInactiveColor(
                    typedArray.getColor(
                            R.styleable.NavigationTabBar_ntb_inactive_color, DEFAULT_INACTIVE_COLOR
                    )
            );
            setActiveColor(
                    typedArray.getColor(
                            R.styleable.NavigationTabBar_ntb_active_color, DEFAULT_ACTIVE_COLOR
                    )
            );
            setAnimationDuration(
                    typedArray.getInteger(
                            R.styleable.NavigationTabBar_ntb_animation_duration, DEFAULT_ANIMATION_DURATION
                    )
            );
            setCornersRadius(
                    typedArray.getDimension(R.styleable.NavigationTabBar_ntb_corners_radius, 0.0f)
            );

            // Init animator
            mAnimator.setFloatValues(MIN_FRACTION, MAX_FRACTION);
            mAnimator.setInterpolator(new LinearInterpolator());
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(final ValueAnimator animation) {
                    updateIndicatorPosition((Float) animation.getAnimatedValue());
                }
            });

            // Set preview models
            if (isInEditMode()) {
                // Get preview colors
                String[] previewColors = null;
                try {
                    final int previewColorsId = typedArray.getResourceId(
                            R.styleable.NavigationTabBar_ntb_preview_colors, 0
                    );
                    previewColors = previewColorsId == 0 ? null :
                            typedArray.getResources().getStringArray(previewColorsId);
                } catch (Exception exception) {
                    previewColors = null;
                    exception.printStackTrace();
                } finally {
                    if (previewColors == null)
                        previewColors = typedArray.getResources().getStringArray(R.array.default_preview);

                    for (String previewColor : previewColors)
                        mModels.add(new Model(null, Color.parseColor(previewColor)));
                    requestLayout();
                }
            }
        } finally {
            typedArray.recycle();
        }
    }

    public int getAnimationDuration() {
        return mAnimationDuration;
    }

    public void setAnimationDuration(final int animationDuration) {
        mAnimationDuration = animationDuration;
        mAnimator.setDuration(mAnimationDuration);
        resetScroller();
    }

    public List<Model> getModels() {
        return mModels;
    }

    public void setModels(final List<Model> models) {
        //Set update listeners to badge model animation
        for (final Model model : models) {
            model.mBadgeAnimator.removeAllUpdateListeners();
            model.mBadgeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(final ValueAnimator animation) {
                    model.mBadgeFraction = (float) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
        }

        mModels.clear();
        mModels = models;
        requestLayout();
    }

    public boolean isTitled() {
        return mIsTitled;
    }

    public void setIsTitled(final boolean isTitled) {
        mIsTitled = isTitled;
        requestLayout();
    }

    public boolean isBadged() {
        return mIsBadged;
    }

    public void setIsBadged(final boolean isBadged) {
        mIsBadged = isBadged;
        requestLayout();
    }

    public boolean isScaled() {
        return mIsScaled;
    }

    public void setIsScaled(final boolean isScaled) {
        mIsScaled = isScaled;
        requestLayout();
    }

    public boolean isBadgeUseTypeface() {
        return mIsBadgeUseTypeface;
    }

    public void setIsBadgeUseTypeface(final boolean isBadgeUseTypeface) {
        mIsBadgeUseTypeface = isBadgeUseTypeface;
        setBadgeTypeface();
        postInvalidate();
    }

    public TitleMode getTitleMode() {
        return mTitleMode;
    }

    private void setTitleMode(final int index) {
        switch (index) {
            case ACTIVE_INDEX:
                setTitleMode(TitleMode.ACTIVE);
                break;
            case ALL_INDEX:
            default:
                setTitleMode(TitleMode.ALL);
        }
    }

    public void setTitleMode(final TitleMode titleMode) {
        mTitleMode = titleMode;
        postInvalidate();
    }

    public BadgePosition getBadgePosition() {
        return mBadgePosition;
    }

    private void setBadgePosition(final int index) {
        switch (index) {
            case LEFT_INDEX:
                setBadgePosition(BadgePosition.LEFT);
                break;
            case CENTER_INDEX:
                setBadgePosition(BadgePosition.CENTER);
                break;
            case RIGHT_INDEX:
            default:
                setBadgePosition(BadgePosition.RIGHT);
        }
    }

    public void setBadgePosition(final BadgePosition badgePosition) {
        mBadgePosition = badgePosition;
        postInvalidate();
    }

    public BadgeGravity getBadgeGravity() {
        return mBadgeGravity;
    }

    private void setBadgeGravity(final int index) {
        switch (index) {
            case BOTTOM_INDEX:
                setBadgeGravity(BadgeGravity.BOTTOM);
                break;
            case TOP_INDEX:
            default:
                setBadgeGravity(BadgeGravity.TOP);
        }
    }

    public void setBadgeGravity(final BadgeGravity badgeGravity) {
        mBadgeGravity = badgeGravity;
        requestLayout();
    }

    public Typeface getTypeface() {
        return mTypeface;
    }

    public void setTypeface(final String typeface) {
        Typeface tempTypeface;
        try {
            tempTypeface = Typeface.createFromAsset(getContext().getAssets(), typeface);
        } catch (Exception e) {
            tempTypeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
            e.printStackTrace();
        }

        setTypeface(tempTypeface);
    }

    public void setTypeface(final Typeface typeface) {
        mTypeface = typeface;
        mModelTitlePaint.setTypeface(typeface);
        setBadgeTypeface();
        postInvalidate();
    }

    private void setBadgeTypeface() {
        mBadgePaint.setTypeface(
                mIsBadgeUseTypeface ? mTypeface : Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
        );
    }

    public int getActiveColor() {
        return mActiveColor;
    }

    public void setActiveColor(final int activeColor) {
        mActiveColor = activeColor;
        mIconPointerPaint.setColor(activeColor);
        postInvalidate();
    }

    public int getInactiveColor() {
        return mInactiveColor;
    }

    public void setInactiveColor(final int inactiveColor) {
        mInactiveColor = inactiveColor;

        // Set color filter to wrap icons with inactive color
        mIconPaint.setColorFilter(new PorterDuffColorFilter(inactiveColor, PorterDuff.Mode.SRC_IN));
        mModelTitlePaint.setColor(mInactiveColor);
        postInvalidate();
    }

    public float getCornersRadius() {
        return mCornersRadius;
    }

    public void setCornersRadius(final float cornersRadius) {
        mCornersRadius = cornersRadius;
        postInvalidate();
    }

    public float getBadgeMargin() {
        return mBadgeMargin;
    }

    public float getBarHeight() {
        return mBounds.height();
    }

    public OnTabBarSelectedIndexListener getOnTabBarSelectedIndexListener() {
        return mOnTabBarSelectedIndexListener;
    }

    // Set on tab bar selected index listener where you can trigger action onStart or onEnd
    public void setOnTabBarSelectedIndexListener(final OnTabBarSelectedIndexListener onTabBarSelectedIndexListener) {
        mOnTabBarSelectedIndexListener = onTabBarSelectedIndexListener;

        if (mAnimatorListener == null)
            mAnimatorListener = new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(final Animator animation) {
                    if (mOnTabBarSelectedIndexListener != null)
                        mOnTabBarSelectedIndexListener.onStartTabSelected(mModels.get(mIndex), mIndex);
                }

                @Override
                public void onAnimationEnd(final Animator animation) {
                    if (mOnTabBarSelectedIndexListener != null)
                        mOnTabBarSelectedIndexListener.onEndTabSelected(mModels.get(mIndex), mIndex);
                }

                @Override
                public void onAnimationCancel(final Animator animation) {

                }

                @Override
                public void onAnimationRepeat(final Animator animation) {

                }
            };
        mAnimator.removeListener(mAnimatorListener);
        mAnimator.addListener(mAnimatorListener);
    }

    public void setViewPager(final ViewPager viewPager) {
        // Detect whether ViewPager mode
        if (viewPager == null) {
            mIsViewPagerMode = false;
            return;
        }

        if (mViewPager == viewPager) return;
        if (mViewPager != null) mViewPager.setOnPageChangeListener(null);
        if (viewPager.getAdapter() == null)
            throw new IllegalStateException("ViewPager does not provide adapter instance.");

        mIsViewPagerMode = true;
        mViewPager = viewPager;
        mViewPager.addOnPageChangeListener(this);

        resetScroller();
        postInvalidate();
    }

    public void setViewPager(final ViewPager viewPager, int index) {
        setViewPager(viewPager);

        mIndex = index;
        if (mIsViewPagerMode) mViewPager.setCurrentItem(index, true);
        postInvalidate();
    }

    // Reset scroller and reset scroll duration equals to animation duration
    private void resetScroller() {
        if (mViewPager == null) return;
        try {
            final Field scrollerField = ViewPager.class.getDeclaredField("mScroller");
            scrollerField.setAccessible(true);
            final ResizeViewPagerScroller scroller = new ResizeViewPagerScroller(getContext());
            scrollerField.set(mViewPager, scroller);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setOnPageChangeListener(final ViewPager.OnPageChangeListener listener) {
        mOnPageChangeListener = listener;
    }

    public int getModelIndex() {
        return mIndex;
    }

    public void setModelIndex(int index) {
        setModelIndex(index, false);
    }

    // Set model index from touch or programmatically
    public void setModelIndex(int index, boolean force) {
        if (mAnimator.isRunning()) return;
        if (mModels.isEmpty()) return;

        // This check gives us opportunity to have an non selected model
        if (mIndex == INVALID_INDEX) force = true;

        // Detect if last is the same
        if (index == mIndex) return;

        // Snap index to models size
        index = Math.max(0, Math.min(index, mModels.size() - 1));

        mIsResizeIn = index < mIndex;
        mLastIndex = mIndex;
        mIndex = index;

        mIsSetIndexFromTabBar = true;
        if (mIsViewPagerMode) {
            if (mViewPager == null) throw new IllegalStateException("ViewPager is null.");
            mViewPager.setCurrentItem(index, true);
        }

        // Set startX and endX for animation, where we animate two sides of rect with different interpolation
        mStartPointerX = mPointerLeftTop;
        mEndPointerX = mIndex * mModelSize;

        // If it force, so update immediately, else animate
        // This happens if we set index onCreate or something like this
        // You can use force param or call this method in some post()
        if (force) updateIndicatorPosition(MAX_FRACTION);
        else mAnimator.start();
    }

    private void updateIndicatorPosition(final float fraction) {
        // Update general fraction
        mFraction = fraction;

        // Set the pointer left top side coordinate
        mPointerLeftTop =
                mStartPointerX + (mResizeInterpolator.getResizeInterpolation(fraction, mIsResizeIn) *
                        (mEndPointerX - mStartPointerX));
        // Set the pointer right bottom side coordinate
        mPointerRightBottom =
                (mStartPointerX + mModelSize) +
                        (mResizeInterpolator.getResizeInterpolation(fraction, !mIsResizeIn) *
                                (mEndPointerX - mStartPointerX));

        // Update pointer
        postInvalidate();
    }

    // Update NTP
    private void notifyDataSetChanged() {
        postInvalidate();
    }

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        // Return if animation is running
        if (mAnimator.isRunning()) return true;
        // If is not idle state, return
        if (mScrollState != ViewPager.SCROLL_STATE_IDLE) return true;

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // Action down touch
                mIsActionDown = true;
                if (!mIsViewPagerMode) break;
                // Detect if we touch down on pointer, later to move
                if (mIsHorizontalOrientation)
                    mIsPointerActionDown = (int) (event.getX() / mModelSize) == mIndex;
                else
                    mIsPointerActionDown = (int) (event.getY() / mModelSize) == mIndex;
                break;
            case MotionEvent.ACTION_MOVE:
                // If pointer touched, so move
                if (mIsPointerActionDown) {
                    if (mIsHorizontalOrientation)
                        mViewPager.setCurrentItem((int) (event.getX() / mModelSize), true);
                    else
                        mViewPager.setCurrentItem((int) (event.getY() / mModelSize), true);
                    break;
                }
                if (mIsActionDown) break;
            case MotionEvent.ACTION_UP:
                // Press up and set model index relative to current coordinate
                if (mIsActionDown) {
                    if (mIsHorizontalOrientation) setModelIndex((int) (event.getX() / mModelSize));
                    else setModelIndex((int) (event.getY() / mModelSize));
                }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE:
            default:
                // Reset action touch variables
                mIsPointerActionDown = false;
                mIsActionDown = false;
                break;
        }

        return true;
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // Get measure size
        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int height = MeasureSpec.getSize(heightMeasureSpec);

        if (mModels.isEmpty() || width == 0 || height == 0) return;

        // Detect orientation and calculate icon size
        if (width > height) {
            mIsHorizontalOrientation = true;

            // Get smaller side
            float side = mModelSize > height ? height : mModelSize;
            if (mIsBadged) side -= side * TITLE_SIZE_FRACTION;

            mModelSize = (float) width / (float) mModels.size();
            mIconSize = (int) (side * (mIsTitled ? TITLE_ICON_SIZE_FRACTION : ICON_SIZE_FRACTION));

            mModelTitleSize = side * TITLE_SIZE_FRACTION;
            mTitleMargin = side * TITLE_MARGIN_FRACTION;

            // If is badged mode, so get vars and set paint with default bounds
            if (mIsBadged) {
                mBadgeTitleSize = mModelTitleSize * BADGE_TITLE_SIZE_FRACTION;

                final Rect badgeBounds = new Rect();
                mBadgePaint.setTextSize(mBadgeTitleSize);
                mBadgePaint.getTextBounds(PREVIEW_BADGE, 0, 1, badgeBounds);
                mBadgeMargin = (badgeBounds.height() * 0.5f) +
                        (mBadgeTitleSize * BADGE_HORIZONTAL_FRACTION * BADGE_VERTICAL_FRACTION);
            }
        } else {
            mIsHorizontalOrientation = false;
            mIsTitled = false;
            mIsBadged = false;

            mModelSize = (float) height / (float) mModels.size();
            mIconSize = (int) ((mModelSize > width ? width : mModelSize) * ICON_SIZE_FRACTION);
        }

        // Set bounds for NTB
        mBounds.set(0.0f, 0.0f, width, height - mBadgeMargin);

        // Set main bitmap
        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);

        // Set pointer canvas
        mPointerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mPointerCanvas = new Canvas(mPointerBitmap);

        // Set icons canvas
        mIconsBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mIconsCanvas = new Canvas(mIconsBitmap);

        // Set scale fraction for icons
        for (Model model : mModels) {
            final float originalIconSize = model.mIcon.getWidth() > model.mIcon.getHeight() ?
                    model.mIcon.getWidth() : model.mIcon.getHeight();
            model.mInactiveIconScale = (float) mIconSize / originalIconSize;
            model.mActiveIconScaleBy = model.mInactiveIconScale *
                    (mIsTitled ? TITLE_ACTIVE_ICON_SCALE_BY : ACTIVE_ICON_SCALE_BY);
        }

        // Set start position of pointer for preview or on start
        if (isInEditMode() || !mIsViewPagerMode) {
            mIsSetIndexFromTabBar = true;

            // Set random in preview mode
            if (isInEditMode()) {
                mIndex = new Random().nextInt(mModels.size());

                if (mIsBadged)
                    for (int i = 0; i < mModels.size(); i++) {
                        final Model model = mModels.get(i);

                        if (i == mIndex) {
                            model.mBadgeFraction = MAX_FRACTION;
                            model.showBadge();
                        } else {
                            model.mBadgeFraction = MIN_FRACTION;
                            model.hideBadge();
                        }
                    }
            }

            mStartPointerX = mIndex * mModelSize;
            mEndPointerX = mStartPointerX;
            updateIndicatorPosition(MAX_FRACTION);
        }
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        if (mCanvas == null || mPointerCanvas == null || mIconsCanvas == null) return;

        // Reset and clear canvases
        mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        mPointerCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        mIconsCanvas.drawColor(0, PorterDuff.Mode.CLEAR);

        // Get pointer badge margin for gravity
        final float pointerBadgeMargin = mBadgeGravity == BadgeGravity.TOP ? mBadgeMargin : 0.0f;

        // Draw our model colors
        for (int i = 0; i < mModels.size(); i++) {
            mPaint.setColor(mModels.get(i).getColor());

            if (mIsHorizontalOrientation) {
                final float left = mModelSize * i;
                final float right = left + mModelSize;
                mCanvas.drawRect(
                        left, pointerBadgeMargin, right, mBounds.height() + pointerBadgeMargin, mPaint
                );
            } else {
                final float top = mModelSize * i;
                final float bottom = top + mModelSize;
                mCanvas.drawRect(0.0f, top, mBounds.width(), bottom, mPaint);
            }
        }

        // Set bound of pointer
        if (mIsHorizontalOrientation)
            mPointerBounds.set(
                    mPointerLeftTop, pointerBadgeMargin,
                    mPointerRightBottom, mBounds.height() + pointerBadgeMargin
            );
        else mPointerBounds.set(0.0f, mPointerLeftTop, mBounds.width(), mPointerRightBottom);

        // Draw pointer for model colors
        if (mCornersRadius == 0) mPointerCanvas.drawRect(mPointerBounds, mPaint);
        else mPointerCanvas.drawRoundRect(mPointerBounds, mCornersRadius, mCornersRadius, mPaint);

        // Draw pointer into main canvas
        mCanvas.drawBitmap(mPointerBitmap, 0.0f, 0.0f, mPointerPaint);

        // Draw model icons
        for (int i = 0; i < mModels.size(); i++) {
            final Model model = mModels.get(i);

            // Variables to center our icons
            final float leftOffset;
            final float topOffset;
            final float matrixCenterX;
            final float matrixCenterY;

            // Set vars for icon when model with title or without
            final float iconMarginTitleHeight = mIconSize + mTitleMargin + mModelTitleSize;
            final float leftTitleOffset = (mModelSize * i) + (mModelSize * 0.5f);
            final float topTitleOffset =
                    mBounds.height() - (mBounds.height() - iconMarginTitleHeight) * 0.5f;

            if (mIsHorizontalOrientation) {
                leftOffset = (mModelSize * i) + (mModelSize - model.mIcon.getWidth()) * 0.5f;
                topOffset = (mBounds.height() - model.mIcon.getHeight()) * 0.5f;

                matrixCenterX = leftOffset + model.mIcon.getWidth() * 0.5f;
                matrixCenterY = topOffset + model.mIcon.getHeight() * 0.5f +
                        (mIsTitled && mTitleMode == TitleMode.ALL ? mTitleMargin * 0.5f : 0.0f);
            } else {
                leftOffset = (mBounds.width() - model.mIcon.getWidth()) * 0.5f;
                topOffset = (mModelSize * i) + (mModelSize - model.mIcon.getHeight()) * 0.5f;

                matrixCenterX = leftOffset + model.mIcon.getWidth() * 0.5f;
                matrixCenterY = topOffset + model.mIcon.getHeight() * 0.5f;
            }

            // Title translate position
            final float titleTranslate = -model.mIcon.getHeight() + topTitleOffset - mTitleMargin * 0.5f;

            // Translate icon to model center
            model.mIconMatrix.setTranslate(
                    leftOffset,
                    (mIsTitled && mTitleMode == TitleMode.ALL) ? titleTranslate : topOffset
            );

            // Get interpolated fraction for left last and current models
            final float interpolation = mResizeInterpolator.getResizeInterpolation(mFraction, true);
            final float lastInterpolation = mResizeInterpolator.getResizeInterpolation(mFraction, false);
//            final float interpolation =
//                    mIsScaled ? mResizeInterpolator.getResizeInterpolation(mFraction, true);
//            final float lastInterpolation =
//                    mIsScaled ? mResizeInterpolator.getResizeInterpolation(mFraction, false) :
//                            (MAX_FRACTION - NON_SCALED_FRACTION);

            // Scale value relative to interpolation
            final float matrixScale = model.mActiveIconScaleBy *
                    (mIsScaled ? interpolation : NON_SCALED_FRACTION);
            final float matrixLastScale = model.mActiveIconScaleBy *
                    (mIsScaled ? lastInterpolation : (MAX_FRACTION - NON_SCALED_FRACTION));

            // Get title alpha relative to interpolation
            final int titleAlpha = (int) (MAX_ALPHA * interpolation);
            final int titleLastAlpha = MAX_ALPHA - (int) (MAX_ALPHA * lastInterpolation);
            // Get title scale relative to interpolation
            final float titleScale = MAX_FRACTION +
                    ((mIsScaled ? interpolation : NON_SCALED_FRACTION) * TITLE_ACTIVE_SCALE_BY);
            final float titleLastScale = mIsScaled ? (MAX_FRACTION + TITLE_ACTIVE_SCALE_BY) -
                    (lastInterpolation * TITLE_ACTIVE_SCALE_BY) : titleScale;

            // Check if we handle models from touch on NTP or from ViewPager
            // There is a strange logic of ViewPager onPageScrolled method, so it is
            if (mIsSetIndexFromTabBar) {
                if (mIndex == i)
                    updateCurrentModel(
                            model, leftOffset, topOffset, titleTranslate, interpolation,
                            matrixCenterX, matrixCenterY, matrixScale, titleScale, titleAlpha
                    );
                else if (mLastIndex == i)
                    updateLastModel(
                            model, leftOffset, topOffset, titleTranslate, lastInterpolation,
                            matrixCenterX, matrixCenterY, matrixLastScale, titleLastScale, titleLastAlpha
                    );
                else
                    updateInactiveModel(
                            model, leftOffset, topOffset, titleScale,
                            matrixScale, matrixCenterX, matrixCenterY
                    );
            } else {
                if (i != mIndex && i != mIndex + 1)
                    updateInactiveModel(
                            model, leftOffset, topOffset, titleScale,
                            matrixScale, matrixCenterX, matrixCenterY
                    );
                else if (i == mIndex + 1)
                    updateCurrentModel(
                            model, leftOffset, topOffset, titleTranslate, interpolation,
                            matrixCenterX, matrixCenterY, matrixScale, titleScale, titleAlpha
                    );
                else if (i == mIndex)
                    updateLastModel(
                            model, leftOffset, topOffset, titleTranslate, lastInterpolation,
                            matrixCenterX, matrixCenterY, matrixLastScale, titleLastScale, titleLastAlpha
                    );
            }

            // Draw model icon
            mIconsCanvas.drawBitmap(model.mIcon, model.mIconMatrix, mIconPaint);
            if (mIsTitled)
                mIconsCanvas.drawText(
                        isInEditMode() ? PREVIEW_TITLE : model.getTitle(),
                        leftTitleOffset, topTitleOffset, mModelTitlePaint
                );
        }

        // Draw pointer with active color to wrap out active icon
        if (mCornersRadius == 0) mIconsCanvas.drawRect(mPointerBounds, mIconPointerPaint);
        else
            mIconsCanvas.drawRoundRect(mPointerBounds, mCornersRadius, mCornersRadius, mIconPointerPaint);

        // Draw general bitmap
        canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null);
        // Draw icons bitmap on top
        canvas.drawBitmap(mIconsBitmap, 0.0f, pointerBadgeMargin, null);

        // If is not badged, exit
        if (!mIsBadged) return;

        // Model badge margin and offset relative to gravity mode
        final float modelBadgeMargin =
                mBadgeGravity == BadgeGravity.TOP ? mBadgeMargin : mBounds.height();
        final float modelBadgeOffset =
                mBadgeGravity == BadgeGravity.TOP ? 0.0f : mBounds.height() - mBadgeMargin;

        for (int i = 0; i < mModels.size(); i++) {
            final Model model = mModels.get(i);

            // Set preview badge title
            if (isInEditMode() || TextUtils.isEmpty(model.getBadgeTitle()))
                model.setBadgeTitle(PREVIEW_BADGE);

            // Set badge title bounds
            mBadgePaint.setTextSize(mBadgeTitleSize * model.mBadgeFraction);
            mBadgePaint.getTextBounds(
                    model.getBadgeTitle(), 0, model.getBadgeTitle().length(), mBadgeBounds
            );

            // Get horizontal and vertical padding for bg
            final float horizontalPadding = mBadgeTitleSize * BADGE_HORIZONTAL_FRACTION;
            final float verticalPadding = horizontalPadding * BADGE_VERTICAL_FRACTION;

            // Set horizontal badge offset
            final float badgeBoundsHorizontalOffset =
                    (mModelSize * i) + (mModelSize * mBadgePosition.mPositionFraction);

            // If is badge title only one char, so create circle else round rect
            if (model.getBadgeTitle().length() == 1) {
                final float badgeMargin = mBadgeMargin * model.mBadgeFraction;
                mBgBadgeBounds.set(
                        badgeBoundsHorizontalOffset - badgeMargin, modelBadgeMargin - badgeMargin,
                        badgeBoundsHorizontalOffset + badgeMargin, modelBadgeMargin + badgeMargin
                );
            } else
                mBgBadgeBounds.set(
                        badgeBoundsHorizontalOffset - mBadgeBounds.centerX() - horizontalPadding,
                        modelBadgeMargin - (mBadgeMargin * model.mBadgeFraction),
                        badgeBoundsHorizontalOffset + mBadgeBounds.centerX() + horizontalPadding,
                        modelBadgeOffset + (verticalPadding * 2.0f) + mBadgeBounds.height()
                );

            // Set color and alpha for badge bg
            if (model.mBadgeFraction == MIN_FRACTION) mBadgePaint.setColor(Color.TRANSPARENT);
            else mBadgePaint.setColor(mActiveColor);
            mBadgePaint.setAlpha((int) (MAX_ALPHA * model.mBadgeFraction));

            // Set corners to round rect for badge bg and draw
            final float cornerRadius = mBgBadgeBounds.height() * 0.5f;
            canvas.drawRoundRect(mBgBadgeBounds, cornerRadius, cornerRadius, mBadgePaint);

            // Set color and alpha for badge title
            if (model.mBadgeFraction == MIN_FRACTION) mBadgePaint.setColor(Color.TRANSPARENT);
            else mBadgePaint.setColor(model.getColor());
            mBadgePaint.setAlpha((int) (MAX_ALPHA * model.mBadgeFraction));

            // Set badge title center position and draw title
            final float badgeHalfHeight = mBadgeBounds.height() * 0.5f;
            float badgeVerticalOffset = (mBgBadgeBounds.height() * 0.5f) + badgeHalfHeight -
                    mBadgeBounds.bottom + modelBadgeOffset;
            canvas.drawText(
                    model.getBadgeTitle(), badgeBoundsHorizontalOffset, badgeVerticalOffset +
                            mBadgeBounds.height() - (mBadgeBounds.height() * model.mBadgeFraction),
                    mBadgePaint
            );
        }
    }

    // Method to transform current fraction of NTB and position
    private void updateCurrentModel(
            final Model model,
            final float leftOffset,
            final float topOffset,
            final float titleTranslate,
            final float interpolation,
            final float matrixCenterX,
            final float matrixCenterY,
            final float matrixScale,
            final float textScale,
            final int textAlpha
    ) {
        if (mIsTitled && mTitleMode == TitleMode.ACTIVE)
            model.mIconMatrix.setTranslate(
                    leftOffset, topOffset - (interpolation * (topOffset - titleTranslate))
            );

        model.mIconMatrix.postScale(
                model.mInactiveIconScale + matrixScale, model.mInactiveIconScale + matrixScale,
                matrixCenterX, matrixCenterY + (mIsTitled && mTitleMode == TitleMode.ACTIVE ?
                        mTitleMargin * 0.5f * interpolation : 0.0f)
        );

        mModelTitlePaint.setTextSize(mModelTitleSize * textScale);
        if (mTitleMode == TitleMode.ACTIVE) mModelTitlePaint.setAlpha(textAlpha);
    }

    // Method to transform last fraction of NTB and position
    private void updateLastModel(
            final Model model,
            final float leftOffset,
            final float topOffset,
            final float titleTranslate,
            final float lastInterpolation,
            final float matrixCenterX,
            final float matrixCenterY,
            final float matrixLastScale,
            final float textLastScale,
            final int textLastAlpha
    ) {
        if (mIsTitled && mTitleMode == TitleMode.ACTIVE)
            model.mIconMatrix.setTranslate(
                    leftOffset, titleTranslate + (lastInterpolation * (topOffset - titleTranslate))
            );

        model.mIconMatrix.postScale(
                model.mInactiveIconScale + model.mActiveIconScaleBy - matrixLastScale,
                model.mInactiveIconScale + model.mActiveIconScaleBy - matrixLastScale,
                matrixCenterX, matrixCenterY + (mIsTitled && mTitleMode == TitleMode.ACTIVE ?
                        mTitleMargin * 0.5f - (mTitleMargin * 0.5f * lastInterpolation) : 0.0f)
        );

        mModelTitlePaint.setTextSize(mModelTitleSize * textLastScale);
        if (mTitleMode == TitleMode.ACTIVE) mModelTitlePaint.setAlpha(textLastAlpha);
    }

    // Method to transform others fraction of NTB and position
    private void updateInactiveModel(
            final Model model,
            final float leftOffset,
            final float topOffset,
            final float textScale,
            final float matrixScale,
            final float matrixCenterX,
            final float matrixCenterY
    ) {
        if (mIsTitled && mTitleMode == TitleMode.ACTIVE)
            model.mIconMatrix.setTranslate(leftOffset, topOffset);

        if (mIsScaled)
            model.mIconMatrix.postScale(
                    model.mInactiveIconScale, model.mInactiveIconScale, matrixCenterX, matrixCenterY
            );
        else
            model.mIconMatrix.postScale(
                    model.mInactiveIconScale + matrixScale, model.mInactiveIconScale + matrixScale,
                    matrixCenterX, matrixCenterY
            );

        mModelTitlePaint.setTextSize(mModelTitleSize * (mIsScaled ? 1.0f : textScale));
        if (mTitleMode == TitleMode.ACTIVE) mModelTitlePaint.setAlpha(MIN_ALPHA);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, final int positionOffsetPixels) {
        // If we animate, don`t call this
        if (!mIsSetIndexFromTabBar) {
            mIsResizeIn = position < mIndex;
            mLastIndex = mIndex;
            mIndex = position;

            mStartPointerX = position * mModelSize;
            mEndPointerX = mStartPointerX + mModelSize;
            updateIndicatorPosition(positionOffset);
        }

        if (mOnPageChangeListener != null)
            mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
    }

    @Override
    public void onPageSelected(final int position) {
        // If VP idle, so update
        if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
            mIsResizeIn = position < mIndex;
            mLastIndex = mIndex;
            mIndex = position;
            postInvalidate();
        }
    }

    @Override
    public void onPageScrollStateChanged(final int state) {
        // If VP idle, reset to MIN_FRACTION
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            mFraction = MIN_FRACTION;
            mIsSetIndexFromTabBar = false;

            if (mOnPageChangeListener != null) mOnPageChangeListener.onPageSelected(mIndex);
            else {
                if (mOnTabBarSelectedIndexListener != null)
                    mOnTabBarSelectedIndexListener.onEndTabSelected(mModels.get(mIndex), mIndex);
            }
        }
        mScrollState = state;

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

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        final SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        mIndex = savedState.index;
        requestLayout();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        final Parcelable superState = super.onSaveInstanceState();
        final SavedState savedState = new SavedState(superState);
        savedState.index = mIndex;
        return savedState;
    }

    private static class SavedState extends BaseSavedState {
        int index;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            index = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(index);
        }

        @SuppressWarnings("UnusedDeclaration")
        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

    @Override
    protected void onConfigurationChanged(final Configuration newConfig) {
        // Config view on rotate etc.
        super.onConfigurationChanged(newConfig);
        requestLayout();

        // Refresh pointer and state after config changed to current
        final int tempIndex = mIndex;
        setModelIndex(INVALID_INDEX, true);
        post(new Runnable() {
            @Override
            public void run() {
                setModelIndex(tempIndex, true);
            }
        });
    }

    // Model class
    public static class Model {

        private String mTitle = "";
        private int mColor;

        private Bitmap mIcon;
        private final Matrix mIconMatrix = new Matrix();

        private String mBadgeTitle = "";
        private String mTempBadgeTitle = "";
        private float mBadgeFraction;

        private boolean mIsBadgeShowed;
        private boolean mIsBadgeUpdated;

        private final ValueAnimator mBadgeAnimator = new ValueAnimator();

        private float mInactiveIconScale;
        private float mActiveIconScaleBy;

        public Model(final Drawable icon, final int color) {
            mColor = color;
            if (icon != null) {
                if (icon instanceof BitmapDrawable) mIcon = ((BitmapDrawable) icon).getBitmap();
                else {
                    mIcon = Bitmap.createBitmap(
                            icon.getIntrinsicWidth(),
                            icon.getIntrinsicHeight(),
                            Bitmap.Config.ARGB_8888
                    );
                    final Canvas canvas = new Canvas(mIcon);
                    icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
                    icon.draw(canvas);
                }
            } else {
                mIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
            }

            mBadgeAnimator.addListener(new Animator.AnimatorListener() {

                @Override
                public void onAnimationStart(final Animator animation) {
                }

                @Override
                public void onAnimationEnd(final Animator animation) {
                    // Detect whether we just update text and don`t reset show state
                    if (!mIsBadgeUpdated) mIsBadgeShowed = !mIsBadgeShowed;
                    else mIsBadgeUpdated = false;
                }

                @Override
                public void onAnimationCancel(final Animator animation) {

                }

                @Override
                public void onAnimationRepeat(final Animator animation) {
                    // Change title when we update and don`t see the title
                    if (mIsBadgeUpdated) mBadgeTitle = mTempBadgeTitle;
                }
            });
        }

        public Model(final Drawable icon, final int color, final String title) {
            this(icon, color);
            mTitle = title;
        }

        public Model(final Drawable icon, final int color, final String title, final String badgeTitle) {
            this(icon, color, title);
            mBadgeTitle = badgeTitle;
        }

        public String getTitle() {
            return mTitle;
        }

        public void setTitle(final String title) {
            mTitle = title;
        }

        public int getColor() {
            return mColor;
        }

        public void setColor(final int color) {
            mColor = color;
        }

        public boolean isBadgeShowed() {
            return mIsBadgeShowed;
        }

        public String getBadgeTitle() {
            return mBadgeTitle;
        }

        public void setBadgeTitle(final String badgeTitle) {
            mBadgeTitle = badgeTitle;
        }

        // If your badge is visible on screen, so you can update title with animation
        public void updateBadgeTitle(final String badgeTitle) {
            if (!mIsBadgeShowed) return;
            if (mBadgeAnimator.isRunning()) mBadgeAnimator.end();

            mTempBadgeTitle = badgeTitle;
            mIsBadgeUpdated = true;

            mBadgeAnimator.setFloatValues(MAX_FRACTION, MIN_FRACTION);
            mBadgeAnimator.setDuration(DEFAULT_BADGE_REFRESH_ANIMATION_DURATION);
            mBadgeAnimator.setRepeatMode(ValueAnimator.REVERSE);
            mBadgeAnimator.setRepeatCount(1);
            mBadgeAnimator.start();
        }

        public void toggleBadge() {
            if (mBadgeAnimator.isRunning()) mBadgeAnimator.end();
            if (mIsBadgeShowed) hideBadge();
            else showBadge();
        }

        public void showBadge() {
            mIsBadgeUpdated = false;

            if (mBadgeAnimator.isRunning()) mBadgeAnimator.end();
            if (mIsBadgeShowed) return;

            mBadgeAnimator.setFloatValues(MIN_FRACTION, MAX_FRACTION);
            mBadgeAnimator.setInterpolator(DECELERATE_INTERPOLATOR);
            mBadgeAnimator.setDuration(DEFAULT_BADGE_ANIMATION_DURATION);
            mBadgeAnimator.setRepeatMode(ValueAnimator.RESTART);
            mBadgeAnimator.setRepeatCount(0);
            mBadgeAnimator.start();
        }

        public void hideBadge() {
            mIsBadgeUpdated = false;

            if (mBadgeAnimator.isRunning()) mBadgeAnimator.end();
            if (!mIsBadgeShowed) return;

            mBadgeAnimator.setFloatValues(MAX_FRACTION, MIN_FRACTION);
            mBadgeAnimator.setInterpolator(ACCELERATE_INTERPOLATOR);
            mBadgeAnimator.setDuration(DEFAULT_BADGE_ANIMATION_DURATION);
            mBadgeAnimator.setRepeatMode(ValueAnimator.RESTART);
            mBadgeAnimator.setRepeatCount(0);
            mBadgeAnimator.start();
        }
    }

    // Custom scroller with custom scroll duration
    private class ResizeViewPagerScroller extends Scroller {

        public ResizeViewPagerScroller(Context context) {
            super(context, new AccelerateDecelerateInterpolator());
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, mAnimationDuration);
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy) {
            super.startScroll(startX, startY, dx, dy, mAnimationDuration);
        }
    }

    // Resize interpolator to create smooth effect on pointer according to inspiration design
    // This is like improved accelerated and decelerated interpolator
    private class ResizeInterpolator implements Interpolator {

        // Spring factor
        private final float mFactor = 1.0f;
        // Check whether side we move
        private boolean mResizeIn;

        @Override
        public float getInterpolation(final float input) {
            if (mResizeIn) return (float) (1.0f - Math.pow((1.0f - input), 2.0f * mFactor));
            else return (float) (Math.pow(input, 2.0f * mFactor));
        }

        public float getResizeInterpolation(final float input, final boolean resizeIn) {
            mResizeIn = resizeIn;
            return getInterpolation(input);
        }
    }

    // Model title mode
    public enum TitleMode {
        ALL, ACTIVE
    }

    // Model badge position
    public enum BadgePosition {

        LEFT(LEFT_FRACTION), CENTER(CENTER_FRACTION), RIGHT(RIGHT_FRACTION);

        private float mPositionFraction;

        BadgePosition() {
            mPositionFraction = RIGHT_FRACTION;
        }

        BadgePosition(final float positionFraction) {
            mPositionFraction = positionFraction;
        }
    }

    // Model badge gravity
    public enum BadgeGravity {
        TOP, BOTTOM
    }

    // Out listener for selected index
    public interface OnTabBarSelectedIndexListener {
        void onStartTabSelected(final Model model, final int index);

        void onEndTabSelected(final Model model, final int index);
    }
}

布局:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_horizontal_ntb"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/wrapper_ntb_horizontal"/>

    <FrameLayout
        android:id="@+id/wrapper_ntb_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">

        <View
            android:id="@+id/bg_ntb_horizontal"
            android:layout_width="match_parent"
            android:layout_height="52dp"
            android:layout_gravity="bottom"
            android:background="#605271"/>

        <com.bzu.gxs.meunguide.NavigationTabBar
            android:id="@+id/ntb_horizontal"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_gravity="center"
            android:background="@drawable/bg_round_circle"
            app:ntb_animation_duration="400"
            app:ntb_preview_colors="@array/red_wine"
            app:ntb_corners_radius="50dp"
            app:ntb_scaled="false"
            app:ntb_active_color="#8d88e4"
            app:ntb_inactive_color="#dddfec"/>

    </FrameLayout>

</RelativeLayout>

activity_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="4">

    <TextView
        android:id="@+id/txt_vp_item_page"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:background="@drawable/bg_round_rect"
        android:gravity="center"
        android:text="Page"
        android:textColor="#9b92b3"
        android:textStyle="bold"/>

</LinearLayout>
时间: 2024-10-17 11:05:32

NavigationTabBar 多彩Tab页的相关文章

微信Tab页切换

参考开源项目PagerSlidingTabStrip 做了一些小修改,比如设置Tab页平均铺满效果.字体变色等 微调的代码请 源码 下载 关于我 private void addTab(final int position, View tab) { tab.setFocusable(true); tab.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { pager.setCurre

Android 高仿华为手机Tab页滑动导航效果

首先带大家看一下实现效果,用了两种实现方式: 1.基于LinearLayout实现,导航栏不可响应手指滑动 2.基于HorizontalScrollView实现,导航栏可响应手指滑动 实现方式虽然不一样,但是使用的是一样的,因为我接口封装的一模一样,下面看实现效果. 基于LinearLayout的实现: 基于HorizontalScrollView的实现: 两者效果一样,区别就在于导航条可否随用户操作滑动. 下面只说明LinearLayout实现,HorizontalScrollView仅仅是套

使用原生js与jQuery分别实现一个简单的tab页签

tab页签通常适用于空间有限而内容较多同时兼顾页面美观度不给用户一种信息过量视觉疲劳的情形.使用面非常广,下面我们用两种方法简单实现之. 首先,构建页面元素.页签的可点击部分我们通常用列表来承载,包括ul和ol,我们这里让页签呈横向分布,所以需要使之向左浮动.而页签内容部分使用div承载即可.另外,我们需要对具有共性的元素统一控制样式和行为,所以就有了下面的dom结构: <div id="main"> <ul id="tabbar" class=&

chrome浏览器tab页内存占用变大,网站变慢为哪般?

问题概述: 公司做的是BS应用. 之前我们的后台服务器程序是带状态的,用ehcache存储登录状态:这两天被我改成了redis存储,应用本身不再存储登录状态. 然后自测,我在测试某个很耗时间的网页操作的时候,发现第一次请求的时候还比较快(这个请求会开200个iframe出来,每个iframe内部还有2个ajax请求)(期间浏览器会向服务器发送了大概600个请求),耗时1分钟内: 然后第二次请求的时候,发现很多请求一直处于pending状态(chrome的开发者工具可以看),等待很久也出不来页面,

动态tab页

1.前台代码 <%-- builed by manage.aspx.cmt  [ver:2015.25.26] at 2015-06-26 15:25:42 --%> <%@ Page Language="C#" AutoEventWireup="True" CodeBehind="CcrCompanyManage.aspx.cs" Inherits="HraWeb.CcrCompanyManage" %&g

JS组件系列——基于Bootstrap Ace模板的菜单和Tab页效果

Ace模板地址:http://code.google.com/p/ace-engine/wiki/AceTemplate(有时会打不开) Ace英文官网:http://wrapbootstrap.com/preview/WB0B30DGR Ace模板功能介绍地址:http://www.cnblogs.com/txw1958/p/Ace-Responsive-Admin-Template.html 一.效果展示 1.初始加载出来的效果 2.展开菜单(支持多级展开,后面代码介绍) 3.点击子菜单,以

1、应用设置之TAB页

转载请注明出处:http://blog.csdn.net/droyon/article/details/39891257                       应用设置的TAB页,共分6页.如图所看到的,依次为"已下载"."USB存储设备"."正在执行"."所有","已停用"6项. Android原生逻辑, "USB存储设备"页,假设外部存储设备为模拟存储区,则不显示. "

jquery实现tab页切换显示div

1.jQuery实现tab切换显示代码实现 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=

防止tab页反复的去请求服务端

直接看图吧. 左边是企业树,右边是根据企业变化的一个tab页 实现功能:1.我们希望如果选中的企业不变,我们在切换旁边五个tab页的时候,只是第一次进去的时候请求服务器端.下面来回切换tab页都不请求服务器端(前提企业树不切换). 2. 如果选中的企业变化,相应的 右边的表结构也要跟着相应的变化. 思路.1.企业树不切换的时候,每点击一次tab 页我们给他一个标识,该标识,去判断如果企业树不变来回切换我们不请求服务端.如下 $("#tab-director").click(functi