編輯:關於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()方法進行平移。
一、需要下載安裝的東西1. 文件下載網上也有挺多安裝教程的,這裡我提供我的安裝方法。Win10 64位。一些文件我在後面打包。2016.9.12號本人安裝記錄。SDK:
public classvc3Ryb25nPiBNYWluQWN0aXZpdHkgPHN0cm9uZz5leHRlbmRzPC9zdHJvbmc+IEFjdGl2
玩過自定義View的小伙伴都知道,在View的繪制過程中,有一個類叫做Path,Path可以幫助我們實現很多自定義形狀的View,特別是配合xfermode屬性來使用的時
今天繼續講解Fragment組件的特性,主要是跟Activity的交互和生命周期的關系,我們前面已經說過Fragment是依賴於Activity的,而且生命周期也跟Act