国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

使用Android造了個(gè)滾輪控件輪子示例

2019-12-12 01:09:08
字體:
供稿:網(wǎng)友

關(guān)于 Android 實(shí)現(xiàn) iOS 上的滾輪選擇效果的控件,到 github 上一搜一大堆,之所以還要造這個(gè)輪子,目的是為了更好的學(xué)習(xí)自定義控件,這個(gè)控件是幾個(gè)月前寫的了,經(jīng)過一段時(shí)間的完善,現(xiàn)在開源,順便寫這一篇簡單的介紹文章。

效果如下,錄屏軟件看起來可能有點(diǎn)卡頓,具體可以下載源碼運(yùn)行:

自定義控件無非是 measure,draw,layout 三個(gè)過程,如果要支持手勢(shì)動(dòng)作,那么就再加上 touch 。

measure

測量過程比較簡單,以文本大小所需要的尺寸,再加上 padding。

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   super.onMeasure(widthMeasureSpec, heightMeasureSpec);   int wantWith = getPaddingLeft() + getPaddingRight();   int wantHeight = getPaddingTop() + getPaddingBottom();   calculateTextSize();   wantWith += mTextRect.width();   //可見 item 數(shù)量計(jì)算文本尺寸   if (mVisibilityCount > 0) {     wantHeight += mTextRect.height() * mVisibilityCount;   } else {     wantHeight += mTextRect.height() * DEFALUT_VISIBILITY_COUNT;   }   setMeasuredDimension(       resolveSize(wantWith, widthMeasureSpec),       resolveSize(wantHeight, heightMeasureSpec)   );   mNeedCalculate = true; }

draw

繪制過程是通過 canvas 的位移去繪制不同位置的部件,包括文本內(nèi)容和選擇框之類的,這里可能需要注意下的地方是,不要一次性把所有文本繪制出來,只需要繪制可見文本即可。

@Override protected void onDraw(Canvas canvas) {   super.onDraw(canvas);   if (hasDataSource()) {     // 省略     // 這里計(jì)算下需要繪制的數(shù)量,+2 只是確保不會(huì)出現(xiàn)空白     final int drawCount = mContentRect.height() / mTextRect.height() + 2;     int invisibleCount = 0;     int dy = -mDistanceY;     // 省略     // 通過 translate 繪制文本     for (int i = 0; (i < drawCount && mDataSources.size() > (invisibleCount + i));        i++) {       final int position = invisibleCount + i;       String text = mDataSources.get(position);       if (i > 0) {         canvas.translate(0, mTextRect.height());       }       final PointF pointF = calculateTextGravity(text);       mTextPaint.setTextSize(mTextSize);       if (position == selctPosition) {         mTextPaint.setColor(mSelectedTextColor);       } else {         mTextPaint.setColor(mNormalTextColor);       }       canvas.drawText(text, pointF.x, pointF.y, mTextPaint);     }     canvas.restoreToCount(saveCount);   }      // 繪制選擇框   int saveCount = canvas.save();   mDrawPaint.setColor(mSelectedLineColor);   canvas.translate(mContentRect.left, mContentRect.top);   canvas.drawLine(       mSelctedRect.left,       mSelctedRect.top,       mSelctedRect.right,       mSelctedRect.top,       mDrawPaint   );   canvas.drawLine(       mSelctedRect.left,       mSelctedRect.bottom,       mSelctedRect.right,       mSelctedRect.bottom,       mDrawPaint   );   canvas.restoreToCount(saveCount); }

layout

因?yàn)檫@個(gè)控件是繼承于 View,所以不需要處理 onLayout。

touch

如果對(duì) touch event 分發(fā)流程熟悉的話,那么很多處理可以說是模版代碼,可以參考 NestedScrollView、ScrollView。

在 onInterceptTouchEvent 中,判斷是否開始進(jìn)行拖動(dòng)手勢(shì),保存到變量(mIsBeingDragged)中:

// 多指處理final int pointerIndex = ev.findPointerIndex(activePointerId);       if (pointerIndex == -1) {         Log.e(TAG, "Invalid pointerId=" + activePointerId             + " in onInterceptTouchEvent");         break;       }       final int y = (int) ev.getY(pointerIndex);       final int yDiff = Math.abs(y - mLastMotionY);       if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {         // 開始拖動(dòng)         mIsBeingDragged = true;         mLastMotionY = y;         initVelocityTrackerIfNotExists();         mVelocityTracker.addMovement(ev);         mNestedYOffset = 0;         if (mScrollStrictSpan == null) {           mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");         }         final ViewParent parent = getParent();         if (parent != null) {           // 禁止父控件攔截事件分發(fā)           parent.requestDisallowInterceptTouchEvent(true);         }       }

在 onTouchEvent 中對(duì) ACTION_MOVR 進(jìn)行拖動(dòng)的處理,如果支持嵌套滾動(dòng),那么會(huì)預(yù)先進(jìn)行嵌套滾動(dòng)的分發(fā)。如果支持陰影效果,那么使用 EdgeEffect。

// 和 onInterceptTouchEvent 一樣進(jìn)行拖動(dòng)手勢(shì)開始的判斷if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {         final ViewParent parent = getParent();         if (parent != null) {           parent.requestDisallowInterceptTouchEvent(true);         }         mIsBeingDragged = true;         if (deltaY > 0) {           deltaY -= mTouchSlop;         } else {           deltaY += mTouchSlop;         }       }       if (mIsBeingDragged) {         // 拖動(dòng)處理         // Scroll to follow the motion event         mLastMotionY = y - mScrollOffset[1];         final int oldY = mScrollY;         final int range = getScrollRange();         final int overscrollMode = getOverScrollMode();         boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);         // Calling overScrollBy will call onOverScrolled, which         // calls onScrollChanged if applicable.          // 滾動(dòng)處理,overScrollBy 中會(huì)處理嵌套滾動(dòng)預(yù)先分發(fā)         if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)             && !hasNestedScrollingParent()) {           // Break our velocity if we hit a scroll barrier.           mVelocityTracker.clear();         }         final int scrolledDeltaY = mScrollY - oldY;         final int unconsumedY = deltaY - scrolledDeltaY;         // 嵌套滾動(dòng)         if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {           mLastMotionY -= mScrollOffset[1];           vtev.offsetLocation(0, mScrollOffset[1]);           mNestedYOffset += mScrollOffset[1];         } else if (canOverscroll) {           final int pulledToY = oldY + deltaY;           // 拖動(dòng)陰影效果           if (pulledToY < 0) {             mEdgeGlowTop.onPull((float) deltaY / getHeight(),                 ev.getX(activePointerIndex) / getWidth());             if (!mEdgeGlowBottom.isFinished()) {               mEdgeGlowBottom.onRelease();             }           } else if (pulledToY > range) {             mEdgeGlowBottom.onPull((float) deltaY / getHeight(),                 1.f - ev.getX(activePointerIndex) / getWidth());             if (!mEdgeGlowTop.isFinished()) {               mEdgeGlowTop.onRelease();             }           }           if (mEdgeGlowTop != null               && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {             postInvalidateOnAnimation();           }         }       }

支持滾動(dòng)手勢(shì)的控件,一般都會(huì)支持 fling 手勢(shì),可以理解為慣性滾動(dòng)。這也是模版代碼,在 onTouchEvent 中對(duì) ACTION_UP 中對(duì)拖動(dòng)速度進(jìn)行分析。

case MotionEvent.ACTION_UP:       if (mIsBeingDragged) {         final VelocityTracker velocityTracker = mVelocityTracker;         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);         // 獲取拖動(dòng)速度         int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);         if ((Math.abs(initialVelocity) > mMinimumVelocity)) {           // 可以進(jìn)行 fling 操作           flingWithNestedDispatch(-initialVelocity);         } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,             getScrollRange())) {           postInvalidateOnAnimation();         }         mActivePointerId = INVALID_POINTER;         endDrag();       }       break;

具體的代碼可以在 ScrollView 中閱讀。

回到我實(shí)現(xiàn)的自定義控件來,對(duì) touch event 的處理代碼可以說是和系統(tǒng)控件的處理沒有什么兩樣,在獲取到拖動(dòng)的距離后,根據(jù)這個(gè)值繪制不同位置的可見區(qū)域。這里多了兩個(gè)處理是:

第一拖動(dòng)結(jié)束后,進(jìn)行復(fù)位處理。拖動(dòng)結(jié)束后,選擇框如果停留在兩個(gè) item 之間,那么根據(jù)和兩個(gè) item 的距離進(jìn)行比較,選擇更近的 item。

private void correctionDistanceY() {   if (mDistanceY % mTextRect.height() != 0) {     int position = mDistanceY / mTextRect.height();     int remainder = mDistanceY % mTextRect.height();     if (remainder >= mTextRect.height() / 2f) {       position++;     }     int newDistanceY = position * mTextRect.height();     animChangeDistanceY(newDistanceY);   } }

第二個(gè)是在使用上發(fā)現(xiàn)的問題,如果剩余可滾動(dòng)的距離過短,拖動(dòng)的手勢(shì)速度又很快,就會(huì)導(dǎo)致 fling 處理沒結(jié)束,視覺上又沒有改變,同時(shí)是在滾動(dòng)結(jié)束后才進(jìn)行選擇的回調(diào),所以體檢上不好,但是 Scroller 并沒有提供 setDuration,所以拷貝 Scroller 中計(jì)算 duration 的方法,根據(jù)剩余的滾動(dòng)計(jì)算合適的 duration,手動(dòng)中斷 Scroller 的 fling 處理。

if ((SystemClock.elapsedRealtime() - mStartFlingTime) >= mFlingDuration || currY == mScroller.getFinalY()) {       //duration or current == final       if (DEBUG) {         Logger.d("abortAnimation");       }       mScroller.abortAnimation();     }

具體的代碼可以閱讀源碼

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 哈巴河县| 永善县| 泾阳县| 贵州省| 甘南县| 界首市| 和平区| 和静县| 南雄市| 桂林市| 大同市| 金山区| 平和县| 始兴县| 许昌县| 留坝县| 沙湾县| 图片| 忻州市| 马公市| 沙雅县| 扶风县| 綦江县| 大关县| 定远县| 佛教| 孟州市| 个旧市| 塘沽区| 曲周县| 洪湖市| 沈丘县| 乐平市| 浦城县| 宁波市| 宁都县| 岫岩| 宜阳县| 仙游县| 昭通市| 康平县|