package com.itcast.googleplayteach.ui.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.support.v4.widget.EdgeEffectCompat;
import android.support.v4.widget.ScrollerCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.widget.ImageButton;
import android.widget.TextView;
import com.itcast.googleplayteach.R;
import com.itcast.googleplayteach.ui.activity.BaseActivity;
import com.itcast.googleplayteach.utils.UIUtils;
public class PagerTab extends ViewGroup {
private ViewPager mViewPager;
private PageListener mPageListener = new PageListener();// 用於註冊給ViewPager監聽狀態和滾動
private OnPageChangeListener mDelegatePageListener;// 用於通知外界ViewPager的狀態和滾動
private BaseActivity mActivity;
private int mDividerPadding = 12;// 分割線上下的padding
private int mDividerWidth = 1;// 分割線的寬度
private int mDividerColor = 0x1A000000;// 分割線顏色
private Paint mDividerPaint;// 分割線的畫筆
private int mIndicatorHeight = 4;// 指示器的高度
private int mIndicatorWidth;// 指示器的寬度,是動態的隨着tab的寬度變化
private int mIndicatorLeft;// 指示器的距離左邊的距離
private int mIndicatorColor = 0xFF0084FF;// 指示器顏色
private Paint mIndicatorPaint; // 指示器的畫筆
private int mContentWidth;// 記錄自身內容的寬度
private int mContentHeight;// 記錄自身內容的高度
private int mTabPadding = 24;// tab左右的內邊距
private int mTabTextSize = 16; // tab文字大小
private int mTabBackgroundResId = R.drawable.bg_tab_text;// tab背景資源
private int mTabTextColorResId = R.color.tab_text_color; // tab文字顏色
private int mTabCount;// tab的個數
private int mCurrentPosition = 0;// 當前光標所處的tab,規則是以光標的最左端所在的item的position
private float mCurrentOffsetPixels;// 光標左邊距離當前光標所處的tab的左邊距離
private int mSelectedPosition = 0; // 當前被選中的tab,用於記錄手指點擊tab的position
private boolean mIsBeingDragged = false;// 是否處於拖動中
private float mLastMotionX;// 上一次手指觸摸的x座標
private VelocityTracker mVelocityTracker;// 用於記錄速度的幫助類
private int mMinimumVelocity;// 系統默認的最小滿足fling的速度
private int mMaximumVelocity;// 系統默認最大的fling速度
private int mTouchSlop;// 系統默認滿足滑動的最小位移
private ScrollerCompat mScroller;// 處理滾動的幫助者
private int mLastScrollX;// 記錄上一次滾動的x位置,這是用於處理overScroll,實際位置可能會受到限制
private int mMaxScrollX = 0;// 控件最大可滾動的距離
private int mSplitScrollX = 0;// 根據item的個數,計算出每移動一個item控件需要移動的距離
private EdgeEffectCompat mLeftEdge;// 處理overScroll的反饋效果
private EdgeEffectCompat mRightEdge;
public PagerTab(Context context) {
this(context, null);
}
public PagerTab(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PagerTab(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (context instanceof BaseActivity) {
mActivity = (BaseActivity) context;
}
init();
initPaint();
}
/** 初始化一些常量 */
private void init() {
// 把一個值從dip轉換成px
mIndicatorHeight = UIUtils.dip2px(mIndicatorHeight);
mDividerPadding = UIUtils.dip2px(mDividerPadding);
mTabPadding = UIUtils.dip2px(mTabPadding);
mDividerWidth = UIUtils.dip2px(mDividerWidth);
mTabTextSize = UIUtils.dip2px(mTabTextSize);
// 創建一個scroller
mScroller = ScrollerCompat.create(mActivity);
// 獲取一個系統關於View的常量配置類
final ViewConfiguration configuration = ViewConfiguration
.get(mActivity);
// 獲取滑動的最小距離
mTouchSlop = configuration.getScaledTouchSlop();
// 獲取fling的最小速度
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
// 獲取fling的最大速度
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mLeftEdge = new EdgeEffectCompat(mActivity);
mRightEdge = new EdgeEffectCompat(mActivity);
}
/** 初始化筆 */
private void initPaint() {
mIndicatorPaint = new Paint();
mIndicatorPaint.setAntiAlias(true);
mIndicatorPaint.setStyle(Paint.Style.FILL);
mIndicatorPaint.setColor(mIndicatorColor);
mDividerPaint = new Paint();
mDividerPaint.setAntiAlias(true);
mDividerPaint.setStrokeWidth(mDividerWidth);
mDividerPaint.setColor(mDividerColor);
}
/** 設置ViewPager */
public void setViewPager(ViewPager viewPager) {
if (viewPager == null || viewPager.getAdapter() == null) {
throw new IllegalStateException(
"ViewPager is null or ViewPager does not have adapter instance.");
}
mViewPager = viewPager;
onViewPagerChanged();
}
private void onViewPagerChanged() {
mViewPager.setOnPageChangeListener(mPageListener);// 給ViewPager設置監聽
mTabCount = mViewPager.getAdapter().getCount();// 有多少個tab需要看ViewPager有多少個頁面
for (int i = 0; i < mTabCount; i++) {
if (mViewPager.getAdapter() instanceof IconTabProvider) {// 如果想要使用icon作爲tab,則需要adapter實現IconTabProvider接口
addIconTab(i,
((IconTabProvider) mViewPager.getAdapter())
.getPageIconResId(i));
} else {
addTextTab(i, mViewPager.getAdapter().getPageTitle(i)
.toString());
}
}
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver != null) {// 監聽第一個的全局layout事件,來設置當前的mCurrentPosition,顯示對應的tab
viewTreeObserver
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getViewTreeObserver().removeGlobalOnLayoutListener(
this);// 只需要監聽一次,之後通過listener回調即可
mCurrentPosition = mViewPager.getCurrentItem();
if (mDelegatePageListener != null) {
mDelegatePageListener
.onPageSelected(mCurrentPosition);
}
}
});
}
}
/** 設置監聽,因爲Tab會監聽ViewPager的狀態,所以不要給ViewPager設置監聽了,設置給Tab,由Tab轉發 */
public void setOnPageChangeListener(OnPageChangeListener listener) {
mDelegatePageListener = listener;
}
/** 添加文字tab */
private void addTextTab(final int position, String title) {
TextView tab = new TextView(mActivity);
tab.setText(title);
tab.setGravity(Gravity.CENTER);
tab.setSingleLine();
tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTabTextSize);
tab.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
tab.setTextColor(UIUtils.getColorStateList(mTabTextColorResId));
tab.setBackgroundDrawable(UIUtils.getDrawable(mTabBackgroundResId));
tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
addTab(position, tab);
}
/** 添加圖片icon */
private void addIconTab(final int position, int resId) {
ImageButton tab = new ImageButton(mActivity);
tab.setImageResource(resId);
tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
addTab(position, tab);
}
private void addTab(final int position, View tab) {
tab.setFocusable(true);
// 設置tab的點擊事件,當tab被點擊時候切換pager的頁面
tab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setCurrentItem(position);
}
});
tab.setPadding(mTabPadding, 0, mTabPadding, 0);
addView(tab, position);
}
/** 測量時的回調 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 獲取控件自身的寬高,模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec)
- getPaddingLeft() - getPaddingRight();
int heightSize = MeasureSpec.getSize(heightMeasureSpec)
- getPaddingBottom() - getPaddingBottom();
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int totalWidth = 0;
int highest = 0;
int goneChildCount = 0;
for (int i = 0; i < mTabCount; i++) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
goneChildCount--;
continue;
}
int childWidthMeasureSpec;
int childHeightMeasureSpec;
LayoutParams childLayoutParams = child.getLayoutParams();
if (childLayoutParams == null) {
childLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
}
if (childLayoutParams.width == LayoutParams.MATCH_PARENT) {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize,
MeasureSpec.EXACTLY);
} else if (childLayoutParams.width == LayoutParams.WRAP_CONTENT) {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize,
MeasureSpec.AT_MOST);
} else {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
childLayoutParams.width, MeasureSpec.EXACTLY);
}
if (childLayoutParams.height == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize, MeasureSpec.EXACTLY);
} else if (childLayoutParams.height == LayoutParams.WRAP_CONTENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize, MeasureSpec.AT_MOST);
} else {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
childLayoutParams.height, MeasureSpec.EXACTLY);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
totalWidth += childWidth;
highest = highest < childHeight ? childHeight : highest;
}
if (totalWidth <= widthSize) {// 如果子Tab的總寬度小於PagerTab,則採用平分模式
int splitWidth = (int) (widthSize
/ (mTabCount - goneChildCount + 0.0f) + 0.5f);
for (int i = 0; i < mTabCount; i++) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
splitWidth, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
child.getMeasuredHeight(), MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
mMaxScrollX = 0;
mSplitScrollX = 0;
} else {// 如果所有子View大於控件的寬度
mMaxScrollX = totalWidth - widthSize;
mSplitScrollX = (int) (mMaxScrollX
/ (mTabCount - goneChildCount - 1.0f) + 0.5f);
}
if (widthMode == MeasureSpec.EXACTLY) {
mContentWidth = widthSize;
} else {
mContentWidth = totalWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
mContentHeight = heightSize;
} else {
mContentHeight = highest;
}
int measureWidth = mContentWidth + getPaddingLeft() + getPaddingRight();
int measureHeight = mContentHeight + getPaddingTop()
+ getPaddingBottom();
setMeasuredDimension(measureWidth, measureHeight);
}
/** 佈局時的回調 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {// 這裏簡化了,沒有考慮margin的情況
if (changed) {
int height = b - t;// 控件供子View顯示的高度
int left = l;
for (int i = 0; i < mTabCount; i++) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
int top = (int) ((height - child.getMeasuredHeight()) / 2.0f + 0.5f);// 如果控件比tab要高,則居中顯示
int right = left + child.getMeasuredWidth();
child.layout(left, top, right, top + child.getMeasuredHeight());// 擺放tab
left = right;// 因爲是水平擺放的,所以爲下一個準備left值
}
}
}
/** 繪製時的回調 */
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int height = getHeight();
// 畫指示器
canvas.drawRect(mIndicatorLeft, height - mIndicatorHeight,
mIndicatorLeft + mIndicatorWidth, height, mIndicatorPaint);
// 畫分割線
for (int i = 0; i < mTabCount - 1; i++) {// 分割線的個數比tab的個數少一個
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
if (child != null) {
canvas.drawLine(child.getRight(), mDividerPadding,
child.getRight(), mContentHeight - mDividerPadding,
mDividerPaint);
}
}
// 因爲overScroll效果是一個持續效果,所以需要持續畫
boolean needsInvalidate = false;
if (!mLeftEdge.isFinished()) {// 如果效果沒停止
final int restoreCount = canvas.save();// 先保存當前畫布
final int heightEdge = getHeight() - getPaddingTop()
- getPaddingBottom();
final int widthEdge = getWidth();
canvas.rotate(270);
canvas.translate(-heightEdge + getPaddingTop(), 0);
mLeftEdge.setSize(heightEdge, widthEdge);
needsInvalidate |= mLeftEdge.draw(canvas);
canvas.restoreToCount(restoreCount);
}
if (!mRightEdge.isFinished()) {
final int restoreCount = canvas.save();
final int widthEdge = getWidth();
final int heightEdge = getHeight() - getPaddingTop()
- getPaddingBottom();
canvas.rotate(90);
canvas.translate(-getPaddingTop(), -(widthEdge + mMaxScrollX));
mRightEdge.setSize(heightEdge, widthEdge);
needsInvalidate |= mRightEdge.draw(canvas);
canvas.restoreToCount(restoreCount);
}
if (needsInvalidate) {
postInvalidate();
}
}
/** 觸摸事件是否攔截的方法 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if (mIsBeingDragged && action == MotionEvent.ACTION_MOVE) {// 當已經處於拖動,並且當前事件是MOVE,直接消費掉
return true;
}
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
mLastMotionX = x; // 記錄住當前的x座標
mIsBeingDragged = !mScroller.isFinished();// 如果按下的時候還在滾動,則把狀態處於拖動狀態
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = ev.getX();
final int xDiff = (int) Math.abs(x - mLastMotionX);// 計算兩次的差值
if (xDiff > mTouchSlop) {// 如果大於最小移動的距離,則把狀態改變爲拖動狀態
mIsBeingDragged = true;
mLastMotionX = x;
ViewParent parent = getParent();// 並請求父View不要再攔截自己觸摸事件,交給自己處理
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
break;
}
case MotionEvent.ACTION_CANCEL:// 當手指離開或者觸摸事件取消的時候,把拖動狀態取消掉
case MotionEvent.ACTION_UP:
mIsBeingDragged = false;
break;
}
return mIsBeingDragged;// 如果是拖動狀態,則攔截事件,交給自己的onTouch處理
}
/** 觸摸事件的處理方法 */
public boolean onTouchEvent(MotionEvent ev) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {// 如果是down事件,記錄住當前的x座標
final float x = ev.getX();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
mLastMotionX = x;
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = ev.getX();
final float deltaX = x - mLastMotionX;
if (!mIsBeingDragged) {// 如果還沒有處於拖動,則判斷兩次的差值是否大於最小拖動的距離
if (Math.abs(deltaX) > mTouchSlop) {
mIsBeingDragged = true;
}
}
if (mIsBeingDragged) {// 如果處於拖動狀態,記錄住x座標
mLastMotionX = x;
onMove(deltaX);
}
break;
}
case MotionEvent.ACTION_UP: {
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
// 先對速度進行一個調整,第一個參數是時間單位,1000毫秒,第二個參數是最大速度。
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
float velocity = velocityTracker.getXVelocity();// 獲取水平方向上的速度
onUp(velocity);
}
}
case MotionEvent.ACTION_CANCEL: {
mIsBeingDragged = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
}
return true;
}
private void onMove(float x) {
if (mMaxScrollX <= 0) {
if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
mViewPager.fakeDragBy(x);
}
} else {
int scrollByX = -(int) (x + 0.5);
if (getScrollX() + scrollByX < 0) {
scrollByX = 0 - getScrollX();
mLeftEdge.onPull(Math.abs(x) / getWidth());
}
if (getScrollX() + scrollByX > mMaxScrollX) {
scrollByX = mMaxScrollX - getScrollX();
mRightEdge.onPull(Math.abs(x) / getWidth());
}
scrollBy(scrollByX, 0);
ViewCompat.postInvalidateOnAnimation(this);
}
}
private void onUp(float velocity) {
if (mMaxScrollX <= 0) {
if (mViewPager.isFakeDragging())
mViewPager.endFakeDrag();
} else {
if (Math.abs(velocity) <= mMinimumVelocity) {
return;
}
mScroller.fling(getScrollX(), 0, -(int) (velocity + 0.5), 0, 0,
mMaxScrollX, 0, 0, 270, 0);
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int oldX = mLastScrollX;
mLastScrollX = mScroller.getCurrX();
if (mLastScrollX < 0 && oldX >= 0) {
mLeftEdge.onAbsorb((int) mScroller.getCurrVelocity());
} else if (mLastScrollX > mMaxScrollX && oldX <= mMaxScrollX) {
mRightEdge.onAbsorb((int) mScroller.getCurrVelocity());
}
int x = mLastScrollX;
if (mLastScrollX < 0) {
x = 0;
} else if (mLastScrollX > mMaxScrollX) {
x = mMaxScrollX;
}
scrollTo(x, 0);
}
ViewCompat.postInvalidateOnAnimation(this);
}
/** 檢測mIndicatorOffset的合法性,並計算出其他有關tab的屬性值 */
private void checkAndcalculate() {
// 如果指示器起始位置比第一個tab的起始位置還要小,糾正爲第一個tab的起始位置,指示器寬度就是第一個tab的寬度
final View firstTab = getChildAt(0);
if (mIndicatorLeft < firstTab.getLeft()) {
mIndicatorLeft = firstTab.getLeft();
mIndicatorWidth = firstTab.getWidth();
}
// 如果指示器起始位置比最後一個tab的起始位置還要大,糾正爲最後一個tab的起始位置,指示器寬度就是最後一個tab的寬度
View lastTab = getChildAt(mTabCount - 1);
if (mIndicatorLeft > lastTab.getLeft()) {
mIndicatorLeft = lastTab.getLeft();
mIndicatorWidth = lastTab.getWidth();
}
// 通過指示器的起始位置計算出當前處於第幾個position,並且計算出已經偏移了多少,偏移量是以當前所處的tab的寬度的百分比
for (int i = 0; i < mTabCount; i++) {
View tab = getChildAt(i);
if (mIndicatorLeft < tab.getLeft()) {
mCurrentPosition = i - 1;
View currentTab = getChildAt(mCurrentPosition);
mCurrentOffsetPixels = (mIndicatorLeft - currentTab.getLeft())
/ (currentTab.getWidth() + 0.0f);
break;
}
}
}
/** 滾動到指定的child */
public void scrollSelf(int position, float offset) {
if (position >= mTabCount) {
return;
}
final View tab = getChildAt(position);
mIndicatorLeft = (int) (tab.getLeft() + tab.getWidth() * offset + 0.5);
int rightPosition = position + 1;
if (offset > 0 && rightPosition < mTabCount) {
View rightTab = getChildAt(rightPosition);
mIndicatorWidth = (int) (tab.getWidth() * (1 - offset)
+ rightTab.getWidth() * offset + 0.5);
} else {
mIndicatorWidth = tab.getWidth();
}
checkAndcalculate();
int newScrollX = position * mSplitScrollX
+ (int) (offset * mSplitScrollX + 0.5);
if (newScrollX < 0) {
newScrollX = 0;
}
if (newScrollX > mMaxScrollX) {
newScrollX = mMaxScrollX;
}
// scrollTo(newScrollX, 0);//滑動
int duration = 100;
if (mSelectedPosition != -1) {
duration = (Math.abs(mSelectedPosition - position)) * 100;
}
mScroller.startScroll(getScrollX(), 0, (newScrollX - getScrollX()), 0,
duration);
ViewCompat.postInvalidateOnAnimation(this);
}
/** 選中指定位置的Tab */
private void selectTab(int position) {
for (int i = 0; i < mTabCount; i++) {
View tab = getChildAt(i);
if (tab != null) {
tab.setSelected(position == i);
}
}
}
/**
* ViewPager的OnPageChangeListener實現類,因爲我們需要在PagerTab中獲取PagerView的監聽,
* 以便可以調整tab
*/
private class PageListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset,
final int positionOffsetPixels) {
// 根據VierPager的偏移值來滾動tab
scrollSelf(position, positionOffset);
if (mDelegatePageListener != null) {// 這個是提供給外部的
mDelegatePageListener.onPageScrolled(position, positionOffset,
positionOffsetPixels);
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
mSelectedPosition = -1;
}
if (mDelegatePageListener != null) {
mDelegatePageListener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageSelected(int position) {
System.out.println("onPageSelected:" + position);
mSelectedPosition = position;
selectTab(position);
if (mDelegatePageListener != null) {
mDelegatePageListener.onPageSelected(position);
}
}
}
/** 如果指示器希望是圖片,則繼承該接口 */
public interface IconTabProvider {
public int getPageIconResId(int position);
public int getPageSelectedIconResId();
}
}
pagertab 自定義控件碎片滑動
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.