效果图
偶然看到在微博的最新版本看到发现界面和个人界面的几个Tab滑动的指示器添加了动画,看起来比以前的线条滑动看起来生动些。
实现
实现滑动是我们Android开发经常运用到的ViewPage 线条的滑动则是根据ViewPage 的Select Item 标识决定的。而相应的滑动动画效果则是根据ViewPage的监听事件OnPageChangeListener 中对应的回调方法
public void onPageScrolled(int position,float positionOffset,int positionOffsetPixels)
positionOffsetPixels是滑动距离,和屏幕有关,*手指往左滑动,该值递增,反之递减 对应的可以判断左右滑动然后根据值对线条做相应的处理
滑动距离计算公式
mCurrentScroll=控件的宽度*当前的position+positionOffsetPixels*(控件的宽度/ViewPage宽度)
scroll_x = (mCurrentScroll- ((选择的ITEM) * (Indicator的宽度))) /总的ITEM数量
实现代码
@Override
public voidonPageScrolled(intposition, floatpositionOffset, intpositionOffsetPixels) {
floata = (float) getWidth() / (float)mViewPager.getWidth();
onScrolled((int) ((getWidth() +mViewPager.getPageMargin()) * position + positionOffsetPixels * a));
}
计算出的的scroll_x则是线条的左右滑动距离 线条往左边移动是负数往右边是正数
所以线条长短变化实现代码如下:
//获取单个item线条的宽度 不能超过View的宽度平分的宽度
this.mItemLineWidth = mItemLineWidth > cursorWidth ? cursorWidth : mItemLineWidth;
int mItemLeft;
int mItemRight;
if (mItemLineWidth < cursorWidth) {
mItemLeft = (cursorWidth - mItemLineWidth) / 2;
mItemRight = cursorWidth - mItemLeft;
} else {
mItemLeft = 0;
mItemRight = cursorWidth;
}
boolean isHalf = Math.abs(scroll_x) < (cursorWidth / 2);
if (scroll_x < 0) {
if (isHalf) {
leftX = (mSelectedTab) * cursorWidth + scroll_x * 2 + mItemLeft;
rightX = (mSelectedTab) * cursorWidth + mItemRight;
} else {
leftX = (mSelectedTab - 1) * cursorWidth + mItemLeft;
rightX = (mSelectedTab) * cursorWidth + mItemRight + (scroll_x + (cursorWidth / 2)) * 2;
}
} else if (scroll_x > 0) {
if (isHalf) {
leftX = mSelectedTab * cursorWidth + mItemLeft;
rightX = (mSelectedTab) * cursorWidth + mItemRight + scroll_x * 2;
} else {
leftX = mSelectedTab * cursorWidth + mItemLeft + (scroll_x - (cursorWidth / 2)) * 2;
rightX = (mSelectedTab + 1) * cursorWidth + mItemRight;
}
} else {
leftX = mSelectedTab * cursorWidth + mItemLeft;
rightX = mSelectedTab * cursorWidth + mItemRight;
}
}
接下来便就是根据线条的最左边和最右边距离的点便可以在View上面绘制出线条
float bottomY = getHeight();
float topY = bottomY - mFooterLineHeight;
drawLineRect.left = leftX;
drawLineRect.right = rightX;
drawLineRect.bottom = bottomY;
drawLineRect.top = topY;
int roundXY = isRoundRectangleLine ? (mFooterLineHeight / 2) : 0;
canvas.drawRoundRect(drawLineRect, roundXY, roundXY, mPaintFooterLine);
完整代码
public class CaterpillarIndicator extends LinearLayout implements View.OnClickListener, ViewPager.OnPageChangeListener {
private static final String TAG = "CaterpillarIndicator";
private static final int BASE_ID = 0xffff00;
private static final int FOOTER_COLOR = 0xFFFFC445;
private static final int ITEM_TEXT_COLOR_NORMAL = 0xFF999999;
private static final int ITEM_TEXT_COLOR_SELECT = 0xFFFFC445;
private boolean isRoundRectangleLine = true;
private boolean isCaterpillar = true;
private int mFootLineColor;
private int mTextSizeNormal;
private int mTextSizeSelected;
private int mTextColorNormal;
private int mTextColorSelected;
private int mFooterLineHeight;
private int mItemLineWidth;
/**
* item count
*/
private int mItemCount = 0;
private int mCurrentScroll = 0;
private int mSelectedTab = 0;
private boolean isNewClick;
/**
* item view id index
*/
private int mCurrID = 0;
private List<TitleInfo> mTitles;
private ViewPager mViewPager;
/**
* indicator line is Rounded Rectangle
*/
/**
* line paint
*/
private Paint mPaintFooterLine;
/**
* line RectF
*/
private RectF drawLineRect;
public CaterpillarIndicator(Context context) {
this(context, null);
}
public CaterpillarIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CaterpillarIndicator);
mFootLineColor = a.getColor(R.styleable.CaterpillarIndicator_slide_footer_color, FOOTER_COLOR);
mTextSizeNormal = a.getDimensionPixelSize(R.styleable.CaterpillarIndicator_slide_text_size_normal, dip2px(mTextSizeNormal));
mTextSizeSelected = a.getDimensionPixelSize(R.styleable.CaterpillarIndicator_slide_text_size_selected, dip2px(mTextSizeNormal));
mFooterLineHeight = a.getDimensionPixelOffset(R.styleable.CaterpillarIndicator_slide_footer_line_height, dip2px(3));
mTextColorSelected = a.getColor(R.styleable.CaterpillarIndicator_slide_text_color_selected, ITEM_TEXT_COLOR_SELECT);
mTextColorNormal = a.getColor(R.styleable.CaterpillarIndicator_slide_text_color_normal, ITEM_TEXT_COLOR_NORMAL);
isCaterpillar = a.getBoolean(R.styleable.CaterpillarIndicator_slide_caterpillar, true);
isRoundRectangleLine = a.getBoolean(R.styleable.CaterpillarIndicator_slide_round, true);
mItemLineWidth = (int) a.getDimension(R.styleable.CaterpillarIndicator_slide_item_width, dip2px(24));
setWillNotDraw(false);
initDraw();
a.recycle();
}
/**
* set foot line height
*
* @param mFooterLineHeight foot line height (int)
*/
public void setFooterLineHeight(int mFooterLineHeight) {
this.mFooterLineHeight = dip2px(mFooterLineHeight);
invalidate();
}
/**
* item width
*
* @param mItemLineWidth item width(dp)
*/
public void setItemLineWidth(int mItemLineWidth) {
this.mItemLineWidth = dip2px(mItemLineWidth);
invalidate();
}
public void setCaterpillar(boolean caterpillar) {
isCaterpillar = caterpillar;
invalidate();
}
/**
* is round line
*
* @param roundRectangleLine true (yes) false (no )
*/
public void setRoundRectangleLine(boolean roundRectangleLine) {
isRoundRectangleLine = roundRectangleLine;
}
private void initDraw() {
mPaintFooterLine = new Paint();
mPaintFooterLine.setAntiAlias(true);
mPaintFooterLine.setStyle(Paint.Style.FILL);
drawLineRect = new RectF(0, 0, 0, 0);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
float a = (float) getWidth() / (float) mViewPager.getWidth();
onScrolled((int) ((getWidth() + mViewPager.getPageMargin()) * position + positionOffsetPixels * a));
}
@Override
public void onPageSelected(int position) {
onSwitched(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
public void setFootLineColor(int mFootLineColor) {
this.mFootLineColor = mFootLineColor;
invalidate();
}
/**
* set text normal size(dp)
*
* @param mTextSizeNormal normal text size
*/
public void setTextSizeNormal(int mTextSizeNormal) {
this.mTextSizeNormal = dip2px(mTextSizeNormal);
updateItemText();
}
public void setTextSizeSelected(int mTextSizeSelected) {
this.mTextSizeSelected = dip2px(mTextSizeSelected);
updateItemText();
}
public void setTextColorNormal(int mTextColorNormal) {
this.mTextColorNormal = mTextColorNormal;
updateItemText();
}
public void setTextColorSelected(int mTextColorSelected) {
this.mTextColorSelected = mTextColorSelected;
updateItemText();
}
private void updateItemText() {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (v instanceof TextView) {
TextView tv = (TextView) v;
if (tv.isSelected()) {
tv.setTextColor(mTextColorSelected);
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSizeSelected);
} else {
tv.setTextColor(mTextColorNormal);
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSizeNormal);
}
}
}
}
private int dip2px(float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isNewClick) {
isNewClick = false;
return;
}
float scroll_x;
int cursorWidth;
if (mItemCount != 0) {
cursorWidth = getWidth() / mItemCount;
scroll_x = (mCurrentScroll - ((mSelectedTab) * (getWidth()))) / mItemCount;
} else {
cursorWidth = getWidth();
scroll_x = mCurrentScroll;
}
this.mItemLineWidth = mItemLineWidth > cursorWidth ? cursorWidth : mItemLineWidth;
int mItemLeft;
int mItemRight;
if (mItemLineWidth < cursorWidth) {
mItemLeft = (cursorWidth - mItemLineWidth) / 2;
mItemRight = cursorWidth - mItemLeft;
} else {
mItemLeft = 0;
mItemRight = cursorWidth;
}
float leftX;
float rightX;
boolean isHalf = Math.abs(scroll_x) < (cursorWidth / 2);
mPaintFooterLine.setColor(mFootLineColor);
if (isCaterpillar) {
if (scroll_x < 0) {
if (isHalf) {
leftX = (mSelectedTab) * cursorWidth + scroll_x * 2 + mItemLeft;
rightX = (mSelectedTab) * cursorWidth + mItemRight;
} else {
leftX = (mSelectedTab - 1) * cursorWidth + mItemLeft;
rightX = (mSelectedTab) * cursorWidth + mItemRight + (scroll_x + (cursorWidth / 2)) * 2;
}
} else if (scroll_x > 0) {
if (isHalf) {
leftX = mSelectedTab * cursorWidth + mItemLeft;
rightX = (mSelectedTab) * cursorWidth + mItemRight + scroll_x * 2;
} else {
leftX = mSelectedTab * cursorWidth + mItemLeft + (scroll_x - (cursorWidth / 2)) * 2;
rightX = (mSelectedTab + 1) * cursorWidth + mItemRight;
}
} else {
leftX = mSelectedTab * cursorWidth + mItemLeft;
rightX = mSelectedTab * cursorWidth + mItemRight;
}
} else {
leftX = mSelectedTab * cursorWidth + scroll_x + mItemLeft;
rightX = (mSelectedTab) * cursorWidth + scroll_x + mItemRight;
}
float bottomY = getHeight();
//set foot line height
float topY = bottomY - mFooterLineHeight;
drawLineRect.left = leftX;
drawLineRect.right = rightX;
drawLineRect.bottom = bottomY;
drawLineRect.top = topY;
int roundXY = isRoundRectangleLine ? (mFooterLineHeight / 2) : 0;
canvas.drawRoundRect(drawLineRect, roundXY, roundXY, mPaintFooterLine);
}
private void onScrolled(int h) {
mCurrentScroll = h;
Log.e(TAG, "onScrolled: onScrolled"+mCurrentScroll);
invalidate();
}
public synchronized void onSwitched(int position) {
if (mSelectedTab == position) {
return;
}
setCurrentTab(position);
}
/**
* init indication
*
* @param startPos init select pos
* @param tabs title list
* @param mViewPager ViewPage
*/
public void init(int startPos, List<TitleInfo> tabs, ViewPager mViewPager) {
removeAllViews();
this.mViewPager = mViewPager;
this.mViewPager.addOnPageChangeListener(this);
this.mTitles = tabs;
this.mItemCount = tabs.size();
setWeightSum(mItemCount);
for (int i = 0; i < mItemCount; i++) {
add(tabs.get(i).getName());
}
mViewPager.setCurrentItem(startPos);
invalidate();
requestLayout();
}
protected void add(String label) {
TextView view = new TextView(getContext());
LinearLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1);
view.setGravity(Gravity.CENTER);
view.setLayoutParams(params);
view.setText(label);
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSizeNormal);
view.setTextColor(mTextColorNormal);
view.setId(BASE_ID + (mCurrID++));
view.setOnClickListener(this);
addView(view);
}
@Override
public void onClick(View v) {
int position = v.getId() - BASE_ID;
isNewClick = true;
mViewPager.setCurrentItem(position, true);
}
/**
* get title list size
*
* @return list size
*/
public int getTitleCount() {
return mTitles != null ? mTitles.size() : 0;
}
public synchronized void setCurrentTab(int index) {
if (index < 0 || index >= getTitleCount()) {
return;
}
View oldTab = getChildAt(mSelectedTab);
if (oldTab != null) {
oldTab.setSelected(false);
setTabTextSize(oldTab, false);
}
mSelectedTab = index;
View newTab = getChildAt(mSelectedTab);
if (newTab != null) {
setTabTextSize(newTab, true);
newTab.setSelected(true);
}
}
/**
* set select textView textSize&textColor state
*
* @param tab TextView
* @param selected is Select
*/
private void setTabTextSize(View tab, boolean selected) {
if (tab instanceof TextView) {
TextView tv = (TextView) tab;
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, selected ? mTextSizeSelected : mTextSizeNormal);
tv.setTextColor(selected ? mTextColorSelected : mTextColorNormal);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mCurrentScroll == 0 && mSelectedTab != 0) {
mCurrentScroll = (getWidth() + mViewPager.getPageMargin()) * mSelectedTab;
}
}
/**
* title
*/
public static class TitleInfo {
String name;
public TitleInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}