Android Navigation TabBar控件实现多彩标签栏

先看看效果图:

源码下载:Android Navigation TabBar控件实现多彩标签栏

代码:

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>

以上就是本文的全部内容,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Android自定义控件ViewGroup实现标签云(四)

    前言: 前面几篇讲了自定义控件绘制原理Android自定义控件基本原理详解(一) ,Android自定义控件之自定义属性(二) ,Android自定义控件之自定义组合控件(三),常言道:"好记性不如烂笔头,光说不练假把式!!!",作为一名学渣就是因为没有遵循这句名言才沦落于此,所以要谨遵教诲,注重理论与实践相结合,今天通过自定义ViewGroup来实现一下项目中用到的标签云. 需求背景: 公司需要实现一个知识点的标签显示,每个标签的长度未知,如下图所示 基本绘制流程: 绘制原理这里不再

  • Android 仿淘宝商品属性标签页

    需求 1.动态加载属性,如尺码,颜色,款式等 由于每件商品的属性是不确定的,有的商品的属性是颜色和尺码,有的是口味,有的是大小,所以这些属性不能直接写死到页面上. 2.动态加载属性下的标签 每个属性下的标签个数也不是一定的,比如有的商品的尺码是是S,M,XL,有的是均码,也就是每种属性的具体的内容是不一定的. 技术点 自定义ViewGroup,使其中的TextView可以依据内容长短自动换行,如下图所示 实现 布局 通过ListView来显示商品所有属性,每种属性作为ListView的Item.

  • Android仿新闻顶部导航标签切换效果

    最近由于个人兴趣原因,写了个模仿新闻顶部导航标签的demo.具体看下图. 那么大致上我们会用到这些知识. 1.Fragment 2.FragmentPagerAdapter 3.HorizontalScrollView 4.PopupWindow ok,那么首先进入第一步. 为了实现顶部的标签,我们要用到HorizontalScrollView,因为原有的HorizontalScrollView控件已经不能满足我们的使用了.所以这里就自定义一个HorizontalScrollView impor

  • Android TextView显示Html类解析的网页和图片及自定义标签用法示例

    本文实例讲述了Android TextView显示Html类解析的网页和图片及自定义标签.分享给大家供大家参考,具体如下: Android系统显示HTML网页的最佳控件为WebView,有时候为了满足特定需求,需要在TextView中显示HTML网页.图片及解析自定义标签. 1.TextView显示Html类解析的网页 CharSequence richText = Html.fromHtml("<strong>萝卜白菜的博客</strong>--<a href='

  • Android模仿微信收藏文件的标签处理功能

    最近需要用到微信的标签功能(如下图所示).该功能可以添加已有标签,也可以自定义标签.也可以删除已编辑菜单.研究了一番.发现还是挺有意思的,模拟实现相关功能. 该功能使用类似FlowLayout的功能.Flowlayout为一个开源软件(https://github.com/ApmeM/android-flowlayout),功能为自动换行的布局类型 import android.content.Context; import android.util.AttributeSet; import a

  • Android中使用TagFlowLayout制作动态添加删除标签

    效果图 简单的效果图(使用开源库)[FlowLayout](" https://github.com/hongyangAndroid/FlowLayout ") 步骤 导包 compile 'com.zhy:flowlayout-lib:1.0.3' <com.zhy.view.flowlayout.TagFlowLayout android:id="@+id/id_flowlayout" zhy:max_select="-1" andro

  • Android Navigation TabBar控件实现多彩标签栏

    先看看效果图: 源码下载:Android Navigation TabBar控件实现多彩标签栏 代码: 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; im

  • Android中Spinner控件之键值对用法实例分析

    本文实例讲述了Android中Spinner控件之键值对用法.分享给大家供大家参考.具体如下: 一.字典表,用来存放键值对信息 package com.ljq.activity; import java.io.Serializable; @SuppressWarnings("serial") public class Dict implements Serializable { private Integer id; private String text; public Dict()

  • Android编程布局控件之AbsoluteLayout用法实例分析

    本文实例讲述了Android编程布局控件之AbsoluteLayout用法.分享给大家供大家参考,具体如下: AbsoluteLayout是绝对布局管理器,指的是指定组件的左上角绝对坐标来指定组件的布局 <?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"

  • 分享Android中ExpandableListView控件使用教程

    本文采用一个Demo来展示Android中ExpandableListView控件的使用,如如何在组/子ListView中绑定数据源.直接上代码如下: 程序结构图: layout目录下的 main.xml 文件源码如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android

  • Android 代码写控件代替XML简单实例

    Android 代码写控件代替XML简单实例 简单的一个Button控件的练习. 实现代码: Button btn = new Button(HandlerToActivity.this); LinearLayout.LayoutParams params=new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_HORIZONTAL; param

  • Android编程之控件ListView使用方法

    本文实例讲述了Android编程之控件ListView使用方法.分享给大家供大家参考.具体分析如下: 控件ListView是一个重要的控件,可以被用作用户列表等显示,下面进行它的操作测试. 下面代码实现了生成了一个ListView显示,并对每个条目的单击事件作出响应. 源代码: package com.list; import java.util.ArrayList; import java.util.HashMap; import android.app.Activity; import an

  • Android实现EditText控件禁止输入内容的方法(附测试demo)

    本文实例讲述了Android实现EditText控件禁止输入内容的方法.分享给大家供大家参考,具体如下: 问题: android如何实现EditText控件禁止往里面输入内容? 修改版解决方法: EditText editText = (EditText) findViewById(R.id.editText1); editText.setKeyListener(null); 看到这个问题大家可能有点奇怪了,EditText的功能不就是往上面写入内容吗? 再者,如果真要禁止输入文本,在布局文件中

  • 如何让安卓(Android)子控件超出父控件的范围显示

    先来看一张预览图: 废话不多说,直接上代码: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false"> <Imag

  • Android自定义组合控件之自定义下拉刷新和左滑删除实例代码

    绪论 最近项目里面用到了下拉刷新和左滑删除,网上找了找并没有可以用的,有比较好的左滑删除,但是并没有和下拉刷新上拉加载结合到一起,要不就是一些比较水的结合,并不能在项目里面使用,小编一着急自己组合了一个,做完了和QQ的对比了一下,并没有太大区别,今天分享给大家,其实并不难,但是不知道为什么网上没有比较好的Demo,当你的项目真的很急的时候,又没有比较好的Demo,那么"那条友谊的小船儿真是说翻就翻啊",好了,下面先来具体看一下实现后的效果吧: 代码已经上传到Github上了,小伙伴们记

  • Android ListView 子控件onClick正确获取position的方法

    在实际开发中,我们有时候不仅需要响应ListView的onItemClick,还需要响应其子控件的点击事件,这个时候我们就会 发现,由于复用等原因,如果直接在子控件的onClick事件中调用getView()中的position,会出现数据或显示错位的问 题,原因就是position的值出现了重复或不准确,解决这个问题的方法是,在adapter每次加载数据的时候,为需要点击 的控件设置一个tag值,这个tag值就设置为当前这个控件对应的position,然后在点击事件中gettag(),获取这个

随机推荐