Android Navigation TabBar控件实现多彩标签栏
作者:Gxs丶小宇 发布时间:2022-06-29 10:00:40
标签: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>


猜你喜欢
- 思路:先从集合中找出来顶级的菜单,然后遍历顶级菜单,找出每个顶级菜单的所有子菜单,然后判断当前需要排列的集合是否为空,如果不为空的话,就在遍
- 本文章使用C#编程,制作一个端口扫描器,能够扫描本机有哪些端口开放了,并显示出来,分别使用单线程和多线程进行了比较。编译软件:Visual
- 1. ObsoleteAttributeObsoleteAttribute 适用于除组件、模块、参数和返回值以外的所有程序元素。将元素标记为
- 1.概述注解可以定义到方法上,类上,一个注解相当与一个类,就相当于实例了一个对象,加上了注解,就相当于加了一个标志。常用的注解:@Overr
- 今天做了一个java对象转Map的例子,执行的时候报错了,如下:Exception in thread "main" j
- Spring boot 2.1.X整合Elasticsearch最新版的一处问题新版本的Spring boot 2的spring-boot-
- 本次和大家分享的是怎么来消费服务,上篇文章讲了使用Feign来消费,本篇来使用rest+ribbon消费服务,并且通过轮询方式来自定义了个简
- 使用线程池的好处1、降低资源消耗可以重复利用已创建的线程降低线程创建和销毁造成的消耗。2、提高响应速度当任务到达时,任务可以不需要等到线程创
- 实例描述现有某班学生的两份成绩,两份成绩中存在一些不一致的记录。需借助于编程方法找出这些不一致的记录。实例代码using System;us
- Android 中下拉菜单,即如html中的<select>,关键在于调用setDropDownViewResource方法,以
- 源码学习的好处不用多说,Mybatis源码量少、逻辑简单,将写个系列文章来学习。SqlSessionMybatis的使用入口位于org.ap
- 如下所示:<?xml version="1.0" encoding="utf-8" ?>
- 如果想实现一个在桌面显示的悬浮窗,用Dialog、PopupWindow、Toast等已经不能实现了,他们基本都是在Activity之上显示
- 上一篇简单介绍了SurfaceView的基本使用,这次就介绍SurfaceView与多线程的混搭。SurfaceView与多线程混搭,是为了
- 目录如何实现简化呢?当然,一些常见的方法已经封装在了 Norns.Urd.Extensions.Polly如何启用 Norns.Urd +
- 简介大家都知道,当我们用C#来开发客户端程序的时候,总会不可避免的需要调用外部程序或者访问网站,本篇博客介绍了三种调用外部应用的方法,供参考
- 最近在读刘增辉老师所著的《MyBatis从入门到精通》一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不
- 本文实例为大家分享了Java实现简易俄罗斯方块的具体代码,供大家参考,具体内容如下一、将对象抽象为类首先考虑俄罗斯方块游戏中含有哪些具体的对
- [DllImport("User32.dll", CharSet = CharSet.Auto)]  
- 目录前言一、小提二、将传感器改成事件分发机制三、使用四、源码总结前言在之前写了一篇关于3D效果的文章,借助传感器展示,有小伙伴问可不可以改成