編輯:關於Android編程
先貼一個自己畫的ZrcListView的UML類圖(學習ing。。。)
首先說下他的整個大體的布沮喎?/kf/yidong/wp/" target="_blank" class="keylink">WPGJyIC8+DQo8aW1nIGFsdD0="這裡寫圖片描述" src="/uploadfile/Collfiles/20160422/20160422091125749.png" title="\" />
SimpleHeader是根據狀態來draw自己的,一般是不畫空白,下拉時把setHeadable設置的SimpleHeader給畫出來;然後如果你把MainActivity中loadMZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcmXXosrNtfTE48Cttbm117K/xOO74beiz9a809TYuPy24LXEtq+7rdK71rHU2sXco6zLtcP3y/y008q81sHW1ba81NrEx7XEsqLDu9PQ0OjSqtf2yrLDtLSmwO2ho9XiwO+1xFNpbXBsZUhlYWRlcrrNU2ltcGxlRm9vdGVy09DH0ta709DSu7j2o7u2+MfSy/vSssq1z9bBy0xpc3RWaWV3tcRIZWFkZXJWaWV3us1Gb290ZXJWaWV3tcS5psTco6y1q8rH1NrL+9XiuPbP7sS/wO/Du9PQ08O1vaOoyc/Su8aqzsTVwsu1tcTT0NK7uPa+zcrHwPvTw0hlYWRlclZpZXfAtMq1z9bPwsCty6LQwravu62jqaOs1eK49rrzw+a74cu1tb3U9cO0yrXP1rXEoaM8L3A+DQo8aDIgaWQ9"滾動的實現">滾動的實現
想知道滾動方面的實現要看什麼呢?當然是觸摸事件的監聽啦。而onTouchEvent只有在ZrcAbsListView中才有實現,看來是在這裡實現的了。
@Override
public boolean onTouchEvent(MotionEvent ev) {
try {
if (!isEnabled()) {
return isClickable() || isLongClickable();
}
if (!mIsAttached) {
return false;
}
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
final int actionMasked = ev.getActionMasked();
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
onTouchDown(ev);
break;
}
case MotionEvent.ACTION_MOVE: {
onTouchMove(ev);
break;
}
case MotionEvent.ACTION_UP: {
onTouchUp(ev);
break;
}
case MotionEvent.ACTION_CANCEL: {
onTouchCancel();
break;
}
case MotionEvent.ACTION_POINTER_UP: {
onSecondaryPointerUp(ev);
final int x = mMotionX;
final int y = mMotionY;
final int motionPosition = pointToPosition(x, y);
if (motionPosition >= 0) {
mMotionPosition = motionPosition;
}
mLastY = y;
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
final int id = ev.getPointerId(index);
final int x = (int) ev.getX(index);
final int y = (int) ev.getY(index);
mMotionCorrection = 0;
mActivePointerId = id;
mMotionX = x;
mMotionY = y;
final int motionPosition = pointToPosition(x, y);
if (motionPosition >= 0) {
mMotionPosition = motionPosition;
}
mLastY = y;
break;
}
}
return true;
} catch (Throwable e) {
e.printStackTrace();
return false;
}
}
看起來似乎很復雜,但是其實只需要關心ACTION_MOVE即可;
private void onTouchMove(MotionEvent ev) {
if (mTouchMode == TOUCH_MODE_INVALID) {
mTouchMode = TOUCH_MODE_SCROLL;
}
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
pointerIndex = 0;
mActivePointerId = ev.getPointerId(pointerIndex);
}
if (mDataChanged) {
layoutChildren();
}
final int x = (int) ev.getX(pointerIndex);
final int y = (int) ev.getY(pointerIndex);
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
startScrollIfNeeded(x, y);
break;
case TOUCH_MODE_SCROLL:
scrollIfNeeded(x, y);
break;
}
}
startScrollIfNeeded中最後也還是調用scrollIfNeeded;直接看看scrollIfNeeded
private void scrollIfNeeded(int x, int y) {
final int rawDeltaY = y - mMotionY;
final int deltaY = rawDeltaY - mMotionCorrection;
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY
: deltaY;
if (mTouchMode == TOUCH_MODE_SCROLL) {
if (y != mLastY) {
if (Math.abs(rawDeltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
boolean atEdge = false;
if (incrementalDeltaY != 0) {
atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
}
if (atEdge) {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
mMotionX = x;
mMotionY = y;
mLastY = y;
}
}
}
trackMotionScroll跟進去;這裡就是重點
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
final int firstPosition = mFirstPosition;
final int firstTop = childCount == 0 ? mFirstTop : getChildAt(0)
.getTop();
int lastBottom = childCount == 0 ? firstTop
: getChildAt(childCount - 1).getBottom();
if (firstPosition + childCount >= mItemCount - 1) {
if (!isRefreshing && !isLoadingMore && isLoadMoreOn
&& onLoadMoreStart != null) {
isLoadingMore = true;
onLoadMoreStart.onStart();
}
}
if (isRefreshing || isLoadingMore) {
if (mZrcFooter != null) {
lastBottom += mZrcFooter.getHeight();
}
}
final int mPaddingBottom = getPaddingBottom();
final int mPaddingTop = getPaddingTop();
final Rect listPadding = mListPadding;
int effectivePaddingTop = 0;
int effectivePaddingBottom = 0;
final int spaceAbove = effectivePaddingTop - firstTop;
final int end = getHeight() - effectivePaddingBottom;
final int spaceBelow = lastBottom - end;
final int height = getHeight() - mPaddingBottom - mPaddingTop;
if (incrementalDeltaY < 0) {
incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
} else {
incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
}
final Headable zrcHeader = mZrcHeader;
final boolean isTooShort = childCount == mItemCount
&& lastBottom - firstTop < getHeight();
final int topOffset = firstTop
- (listPadding.top + mFirstTopOffset + (showHeader ? zrcHeader
.getHeight() : 0));
final int bottomOffset = isTooShort ? firstTop - listPadding.top
: lastBottom - getHeight() + listPadding.bottom
+ mLastBottomOffset;
final boolean isOutOfTop = firstPosition == 0 && topOffset > 0;
final boolean isOutOfBottom = firstPosition + childCount == mItemCount
&& bottomOffset < 0;
final boolean cannotScrollDown = (isOutOfTop && incrementalDeltaY > 0);
final boolean cannotScrollUp = (isOutOfBottom && incrementalDeltaY <= 0);
if (isTooShort && cannotScrollDown && mTouchMode == TOUCH_MODE_RESCROLL) {
mTouchMode = TOUCH_MODE_FLING;
return true;
}
if (isOutOfTop || isOutOfBottom) {
if (mTouchMode == TOUCH_MODE_SCROLL) {
incrementalDeltaY /= 1.7f;
if (zrcHeader != null && isOutOfTop) {
final int state = zrcHeader.getState();
if (topOffset >= zrcHeader.getHeight()) {
if (state == Headable.STATE_PULL
|| state == Headable.STATE_REST) {
zrcHeader.stateChange(Headable.STATE_RELEASE, null);
}
} else {
if (state == Headable.STATE_RELEASE
|| state == Headable.STATE_REST) {
zrcHeader.stateChange(Headable.STATE_PULL, null);
}
}
}
}
if (mTouchMode == TOUCH_MODE_RESCROLL && false) {
if (isOutOfTop && zrcHeader != null) {
final int state = zrcHeader.getState();
if (topOffset < 10
&& (state == Headable.STATE_SUCCESS || state == Headable.STATE_FAIL)) {
zrcHeader.stateChange(Headable.STATE_REST, null);
removeCallbacks(mResetRunnable);
}
}
}
if (mTouchMode == TOUCH_MODE_FLING) {
if (cannotScrollDown) {
incrementalDeltaY /= 1.7f;
int duration = firstTop - listPadding.top;
if (duration > getHeight() / 6) {
return true;
}
} else if (cannotScrollUp && !isOutOfTop) {
incrementalDeltaY /= 1.7f;
int duration = bottomOffset;
if (duration < -getHeight() / 6) {
return true;
}
}
} else {
if (incrementalDeltaY > 0) {
int duration = firstTop - listPadding.top;
if (duration > getHeight() / 2) {
return true;
}
} else if (incrementalDeltaY < 0 && !isOutOfTop) {
int duration = bottomOffset;
if (duration < -getHeight() / 2) {
return true;
}
}
}
if (onScrollStateListener != null) {
if (mScrollState != OnScrollStateListener.EDGE) {
mScrollState = OnScrollStateListener.EDGE;
onScrollStateListener.onChange(OnScrollStateListener.EDGE);
}
}
} else {
if (zrcHeader != null) {
if (zrcHeader.getState() == Headable.STATE_PULL) {
zrcHeader.stateChange(Headable.STATE_REST, null);
}
}
if (incrementalDeltaY > 5) {
if (onScrollStateListener != null) {
if (mScrollState != OnScrollStateListener.UP) {
mScrollState = OnScrollStateListener.UP;
onScrollStateListener
.onChange(OnScrollStateListener.UP);
}
}
} else if (incrementalDeltaY < -5) {
if (onScrollStateListener != null) {
if (mScrollState != OnScrollStateListener.DOWN) {
mScrollState = OnScrollStateListener.DOWN;
onScrollStateListener
.onChange(OnScrollStateListener.DOWN);
}
}
}
}
//---------------------漂亮的分割線-----------------
final boolean down = incrementalDeltaY < 0;
final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = 0;
int count = 0;
if (down) {
int top = -incrementalDeltaY;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top + Math.min(0, bottomOffset)) {
break;
} else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount
&& position < footerViewsStart) {
mRecycler.addScrapView(child, position);
}
}
}
} else {
int bottom = getHeight() - incrementalDeltaY;
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= bottom + Math.max(0, topOffset)) {
break;
} else {
start = i;
count++;
int position = firstPosition + i;
if (position >= headerViewsCount
&& position < footerViewsStart) {
mRecycler.addScrapView(child, position);
}
}
}
}
mBlockLayoutRequests = true;
if (count > 0) {
detachViewsFromParent(start, count);
mRecycler.removeSkippedScrap();
}
if (!awakenScrollBars()) {
invalidate();
}
offsetChildrenTopAndBottom(incrementalDeltaY);
if (down) {
mFirstPosition += count;
}
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY
|| spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
mFirstTop = getChildCount() == 0 ? mFirstTop + incrementalDeltaY
: getChildAt(0).getTop();
if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
mSelectorRect.setEmpty();
}
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
return false;
}
代碼雖多但是其實做了兩件事,第一件分割符上面的判斷並設置SimpleHeader的狀態,SimpleHeader會根據狀態做不同的變化;第二件根據需要調用fillGap(down)、offsetTopAndBottom使得ListView滾動起來。貼一下SimpleHeader的draw代碼
@Override
public boolean draw(Canvas canvas, int left, int top, int right, int bottom) {
boolean more = false;
final int width = right - left;
final int height = mHeight;
final int offset = bottom - top;
canvas.save();
if (isClipCanvas) {
canvas.clipRect(left + 5, 1, right + 5, bottom - 1);
}
switch (mState) {
case STATE_REST:
break;
case STATE_PULL:
case STATE_RELEASE:
if (offset < 10) {
break;
}
mPaint.setColor(mPointColor);
for (int i = 0; i < mPice; i++) {
int angleParam;
if (offset < height * 3 / 4) {
angleParam = offset * 16 / height - 3;// 每1%轉0.16度;
} else {
angleParam = offset * 300 / height - 217;// 每1%轉3度;
}
float angle = -(i * (360 / mPice) - angleParam) * PI / 180;
float radiusParam;
if (offset <= height) {
radiusParam = offset / (float) height;
radiusParam = 1 - radiusParam;
radiusParam *= radiusParam;
radiusParam = 1 - radiusParam;
} else {
radiusParam = 1;
}
float radius = width / 2 - radiusParam * (width / 2 - mCircleRadius);
float x = (float) (width / 2 + radius * Math.cos(angle));
float y = (float) (offset / 2 + radius * Math.sin(angle));
canvas.drawCircle(x, y + top, mPointRadius, mPaint);
}
break;
case STATE_LOADING:
more = true;
mPaint.setColor(mPointColor);
for (int i = 0; i < mPice; i++) {
int angleParam = mTime * 5;
float angle = -(i * (360 / mPice) - angleParam) * PI / 180;
float radius = mCircleRadius;
float x = (float) (width / 2 + radius * Math.cos(angle));
float y;
if (offset < height) {
y = (float) (offset - height / 2 + radius * Math.sin(angle));
} else {
y = (float) (offset / 2 + radius * Math.sin(angle));
}
canvas.drawCircle(x, y + top, mPointRadius, mPaint);
}
mTime++;
break;
case STATE_SUCCESS:
case STATE_FAIL:
more = true;
final int time = mTime;
if (time < 30) {
mPaint.setColor(mPointColor);
for (int i = 0; i < mPice; i++) {
int angleParam = mTime * 10;
float angle = -(i * (360 / mPice) - angleParam) * PI / 180;
float radius = mCircleRadius + time * mCircleRadius;
float x = (float) (width / 2 + radius * Math.cos(angle));
float y;
if (offset < height) {
y = (float) (offset - height / 2 + radius * Math.sin(angle));
} else {
y = (float) (offset / 2 + radius * Math.sin(angle));
}
canvas.drawCircle(x, y + top, mPointRadius, mPaint);
}
mPaint.setColor(mTextColor);
mPaint.setAlpha(time * 255 / 30);
String text = mMsg != null ? mMsg : mState == STATE_SUCCESS ? "加載成功" : "加載失敗";
float y;
if (offset < height) {
y = offset - height / 2;
} else {
y = offset / 2;
}
canvas.drawText(text, width / 2, y + top + mFontOffset, mPaint);
} else {
mPaint.setColor(mTextColor);
String text = mMsg != null ? mMsg : mState == STATE_SUCCESS ? "加載成功" : "加載失敗";
float y;
if (offset < height) {
y = offset - height / 2;
mPaint.setAlpha(offset * 255 / height);
} else {
y = offset / 2;
}
canvas.drawText(text, width / 2, y + top + mFontOffset, mPaint);
}
mTime++;
break;
}
canvas.restore();
return more;
}
這裡有個問題那麼draw這個函數什麼時候調用呢,其實是這樣的:ZrcListView再畫自己的時候會調用draw–>onDraw–>dispatchDraw而在ZrcAbsListView中重寫了,在這裡調用了header和footer的draw方法。這樣ZrcListView在畫自己的時候也會去畫header和footer了。
還有一個關鍵點是ListView內部是怎麼滾動起來的呢?我也是看了上面郭神的文章才了解的,具體大家可以細細去看,我這裡寫一個我理解的流程。fillGap主要是填充加載View到ListView中fillGap–>fillUp/fillDown–>makeAndAddView–>obtainView(真正把View從無到有的方法,如果緩存裡沒有就是從這裡獲得)–>setupChild(這個方法裡View已經加入了,這時候就是滾動了)–>offsetTopAndBottom(移動)
最後一點ListView整個控件又是怎麼移動的呢?我們知道下拉的時候它需要往下移動(一般移動量是手指移動的一半,這樣比較有下拉的感覺)讓出一定空間好讓SimpleHeader可以展示自己。
關於這個分為兩部分,第一部分:ListView跟隨手指移動而移動它的二分之一量;第二部分松開手指(可能會播放動畫也可能不會)後ListView上移值原始位置
首先看第一部分,找了N久沒找到他是怎麼移動的;後來我一步步跟蹤代碼最後我把offsetTopAndBottom這段移動ListView內部的代碼注釋掉發現子View動不了(肯定的)整個ListView也不會移動。於是有了如下猜想:
所以其實ListView內部子view的offsetTopAndBottom就是整個ListView的移動他並沒有在外面包一層View。心中一萬只某馬奔騰而過。。。待驗證?????
再然後說說第二部分:一句話概括就是在ACTION_UP裡利用Scroller讓ListView自然的飄逸的移動到原來的位置
ZrcListView的Adapter需要說下,看上面的類圖可以看到一個HeaderViewListAdapter,這個是干嘛的呢?我把getView代碼貼出來
public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an IndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).view;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
}
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).view;
}
還記得剛開始那個布局圖嗎,那個結構就是在這裡進行處理的。
private final ListAdapter mAdapter;
// These two ArrayList are assumed to NOT be null.
// They are indeed created when declared in ListView and then shared.
ArrayList mHeaderViewInfos;
ArrayList mFooterViewInfos;
沒錯,這個就是實現ListView的HeaderView和FooterView功能的地方。HeaderViewListAdapter這個類裡的這三個,mAdapter就是ListView的視圖只有一個,mHeaderViewInfos和mFooterViewInfos則可以有多個,你可以一直addHeaderView往裡面加各種View。效果就是ListView.addHeaderView的效果
小白一枚,學習記錄,輕噴
現在項目裡面有一個需求,本項目裡面下載的視頻和文檔都不允許通過其他的播放器播放,在培訓機構裡面這樣的需求很多。防止有人交一份錢,把所有的課件就拷給了別人。這樣的事情培訓機
1、 進程的地址空間在32位操作系統中,進程的地址空間為0到4GB,示意圖如下: 圖1 這裡主要說明一下Stack和Heap:Stack空
一 簡介 SQLite是一個輕量的、跨平台的、開源的數據庫引擎,它的讀寫效率、資源消耗總量、延遲時間和整體簡單性上具有的優越性,使其成為移動平台數
1.ServicesService 是一個可以在後台執行長時間運行操作而不使用用戶界面的應用組件。服務可由其他應用組件啟動,而且即使用戶切換到其他應用,服務仍將在後台繼續