編輯:關於Android編程
ViewPagerIndicator,配合ViewPager使用的指示器,可以是標簽類型Tab指示器(如各種新聞app),也可以是小圓圈或小橫線類型的指示器(如引導頁),來自於github上大名鼎鼎的JakeWharton。
如圖所示。

類的設計圖:

(1).先來看繼承自View的四個類,其核心都是重寫onMeasure()、onDraw()、onTouchEvent()。主要區別在於onDraw()方法,根據需要繪制不同的形狀,而onTouchEvent()方法幾乎是一致的。
以CirclePageIndicator類為例,onMeasure()方法核心代碼如下。
onMeasure()方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
// 確定的寬度
result = specSize;
} else {
// 計算寬度
final int count = mViewPager.getAdapter().getCount();
result = (int) (getPaddingLeft() + getPaddingRight() + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
// 如果父視圖限定了寬度,則取兩者中的較小值
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// 確定的高度
result = specSize;
} else {
// 計算高度
result = (int) (2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
// 如果父視圖限定了高度,則取兩者中的較小值
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
MeasureSpec.EXACTLY:直接取子View的確定大小。
onDraw()方法核心代碼如下。在該方法中,繪制圓點指示器。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int count = mViewPager.getAdapter().getCount();
int longSize = getWidth();
int longPaddingBefore = getPaddingLeft();
int longPaddingAfter = getPaddingRight();
int shortPaddingBefore = getPaddingTop();
// threeRadius:兩個相鄰圓點的圓心之間的間距
final float threeRadius = mRadius * 3;
// shortOffset:圓點的垂直方向坐標
final float shortOffset = shortPaddingBefore + mRadius;
// longOffset:圓點的水平方向坐標
float longOffset = longPaddingBefore + mRadius;
if (mCentered) {
longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f);
}
float dX;
float dY;
float pageFillRadius = mRadius;
if (mPaintStroke.getStrokeWidth() > 0) {
pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
}
// 根據頁面的數量,循環繪制出空心圓
for (int iLoop = 0; iLoop < count; iLoop++) {
// drawLong:當前繪制的圓點的x坐標
float drawLong = longOffset + (iLoop * threeRadius);
dX = drawLong;
dY = shortOffset;
if (pageFillRadius != mRadius) {
canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
}
}
// 隨著頁面的滑動,繪制實心圓
// mSnap==true時,實心圓點不跟隨手勢移動
float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
if (!mSnap) {
cx += mPageOffset * threeRadius;
}
dX = longOffset + cx;
dY = shortOffset;
canvas.drawCircle(dX, dY, mRadius, mPaintFill);
}
@Override
public boolean onTouchEvent(android.view.MotionEvent ev) {
if (super.onTouchEvent(ev)) {
return true;
}
if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
return false;
}
// 獲得動作類型
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
// 按下時,記錄首次觸摸點的id和位置
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mLastMotionX = ev.getX();
break;
case MotionEventCompat.ACTION_POINTER_DOWN:
// 在已有觸摸點的情況下,又出現了新的觸摸點按下,獲取新觸摸點的id和位置
final int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
mLastMotionX = MotionEventCompat.getX(ev, index);
break;
case MotionEvent.ACTION_MOVE:
// 計算移動距離,拖動ViewPager
final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
final float deltaX = x - mLastMotionX;
if (!mIsDragging) {
if (Math.abs(deltaX) > mTouchSlop) {
mIsDragging = true;
}
}
if (mIsDragging) {
mLastMotionX = x;
if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
mViewPager.fakeDragBy(deltaX);
}
}
break;
case MotionEventCompat.ACTION_POINTER_UP:
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
}
mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (!mIsDragging) {
final int count = mViewPager.getAdapter().getCount();
final int width = getWidth();
final float halfWidth = width / 2f;
final float sixthWidth = width / 6f;
// ACTION_UP時,手指離開屏幕的點,小於指示器寬度的1/3,ViewPager滑動到上一頁
if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
if (action != MotionEvent.ACTION_CANCEL) {
mViewPager.setCurrentItem(mCurrentPage - 1);
}
return true;
// ACTION_UP時,手指離開屏幕的點,大於指示器寬度的2/3,ViewPager滑動到下一頁
} else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
if (action != MotionEvent.ACTION_CANCEL) {
mViewPager.setCurrentItem(mCurrentPage + 1);
}
return true;
}
}
// 重置狀態
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
break;
}
return true;
}
剩下的LinePageIndicator、TitlePageIndicator和UnderlinePageIndicator不再具體分析,基本就是onDraw()方法的實現不同。
(2).再來看繼承自HorizontalScrollView的兩個類,TabPageIndicator和IconPageIndicator。因為HorizontalScrollView已經幫我們實現了很多代碼,所以這兩個類比上面的四個類簡單很多。
以TabPageIndicator類為例,核心方法如下。
構造方法。
public TabPageIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
setHorizontalScrollBarEnabled(false);
mTabLayout = new IcsLinearLayout(context, R.attr.vpiTabPageIndicatorStyle);
addView(mTabLayout, new ViewGroup.LayoutParams(WRAP_CONTENT, MATCH_PARENT));
}
在構造方法中,創建一個IcsLinearLayout水平布局對象,調用addView()方法添加到當前視圖,之後會將每一個tab(TextView或ImageView)添加到IcsLinearLayout水平布局中。
notifyDataSetChanged()方法。
public void notifyDataSetChanged() {
mTabLayout.removeAllViews();
PagerAdapter adapter = mViewPager.getAdapter();
final int count = adapter.getCount();
for (int i = 0; i < count; i++) {
CharSequence title = adapter.getPageTitle(i);
addTab(i, title);
}
setCurrentItem(mSelectedTabIndex);
requestLayout();
}
private void addTab(int index, CharSequence text) {
final TabView tabView = new TabView(getContext());
tabView.mIndex = index;
tabView.setFocusable(true);
tabView.setOnClickListener(mTabClickListener);
tabView.setText(text);
mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0, MATCH_PARENT, 1));
}
在notifyDataSetChanged()方法中,遍歷Adapter中的標題,生成TabView並添加到IcsLinearLayout中。
setCurrentItem()方法。
@Override
public void setCurrentItem(int item) {
mViewPager.setCurrentItem(item);
final int tabCount = mTabLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {
final View child = mTabLayout.getChildAt(i);
final boolean isSelected = (i == item);
child.setSelected(isSelected);
if (isSelected) {
animateToTab(item);
}
}
}
private void animateToTab(final int position) {
final View tabView = mTabLayout.getChildAt(position);
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
}
mTabSelector = new Runnable() {
public void run() {
final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
smoothScrollTo(scrollPos, 0);
mTabSelector = null;
}
};
post(mTabSelector);
}
在setCurrentItem()方法中,先選中ViewPager中的頁面,然後將當前的Tab設置為選中狀態,當TabPageIndicator的寬度超出屏幕寬度時,通過調用smoothScrollTo()方法進行平移。
[Android測試] Android Studio+Appium+Java+Windows 自動化測試之二:Appium環境安裝搭建
一、需要下載安裝的東西1. 文件下載網上也有挺多安裝教程的,這裡我提供我的安裝方法。Win10 64位。一些文件我在後面打包。2016.9.12號本人安裝記錄。SDK:
Android項目——傳感器的使用
public classvc3Ryb25nPiBNYWluQWN0aXZpdHkgPHN0cm9uZz5leHRlbmRzPC9zdHJvbmc+IEFjdGl2
Android開發之Path類使用詳解,自繪各種各樣的圖形!
玩過自定義View的小伙伴都知道,在View的繪制過程中,有一個類叫做Path,Path可以幫助我們實現很多自定義形狀的View,特別是配合xfermode屬性來使用的時
Android基礎之Fragment與Activity交互詳解
今天繼續講解Fragment組件的特性,主要是跟Activity的交互和生命周期的關系,我們前面已經說過Fragment是依賴於Activity的,而且生命周期也跟Act