這篇文章將為大家詳細(xì)講解有關(guān)使用Android怎么構(gòu)造一個(gè)滾輪控件,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

自定義控件無(wú)非是 measure,draw,layout 三個(gè)過(guò)程,如果要支持手勢(shì)動(dòng)作,那么就再加上 touch 。
measure
測(cè)量過(guò)程比較簡(jiǎn)單,以文本大小所需要的尺寸,再加上 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();
//可見(jiàn) 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
繪制過(guò)程是通過(guò) canvas 的位移去繪制不同位置的部件,包括文本內(nèi)容和選擇框之類(lèi)的,這里可能需要注意下的地方是,不要一次性把所有文本繪制出來(lái),只需要繪制可見(jiàn)文本即可。
@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;
// 省略
// 通過(guò) 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ā)流程熟悉的話(huà),那么很多處理可以說(shuō)是模版代碼,可以參考 NestedScrollView、ScrollView。
在 onInterceptTouchEvent 中,判斷是否開(kāi)始進(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) {
// 開(kāi)始拖動(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ì)開(kāi)始的判斷
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)的自定義控件來(lái),對(duì) touch event 的處理代碼可以說(shuō)是和系統(tǒng)控件的處理沒(méi)有什么兩樣,在獲取到拖動(dòng)的距離后,根據(jù)這個(gè)值繪制不同位置的可見(jiàn)區(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)的問(wèn)題,如果剩余可滾動(dòng)的距離過(guò)短,拖動(dòng)的手勢(shì)速度又很快,就會(huì)導(dǎo)致 fling 處理沒(méi)結(jié)束,視覺(jué)上又沒(méi)有改變,同時(shí)是在滾動(dòng)結(jié)束后才進(jìn)行選擇的回調(diào),所以體檢上不好,但是 Scroller 并沒(méi)有提供 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();
}關(guān)于使用Android怎么構(gòu)造一個(gè)滾輪控件就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
文章題目:使用Android怎么構(gòu)造一個(gè)滾輪控件-創(chuàng)新互聯(lián)
瀏覽路徑:http://chinadenli.net/article46/dgpjhg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)、動(dòng)態(tài)網(wǎng)站、網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)公司、做網(wǎng)站、Google
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容