怕自己说的不清不楚,先来一个郭神的文章镇楼:http://blog.csdn.net/guolin_blog/article/details/44996879

github:https://github.com/zarics/ZrcListView
先贴一个自己画的ZrcListView的UML类图(学习ing。。。



首先说下他的整个大体的布局

SimpleHeader是依据状态来draw自己的。通常是不画空白。下拉时把setHeadable设置的SimpleHeader给画出来;然后假设你把MainActivity中loadMore凝视掉你拉倒底部你会发现载入很多其它的动画一直在跑,说明它从始至终都在那的并没有须要做什么处理。

这里的SimpleHeader和SimpleFooter有且仅仅有一个;并且他也实现了ListView的HeaderView和FooterView的功能,可是在他这个项目里没实用到(上一篇文章说的有一个就是利用HeaderView来实现下拉刷新动画),这个后面会说到怎么实现的。

滚动的实现

想知道滚动方面的实现要看什么呢?当然是触摸事件的监听啦。而onTouchEvent仅仅有在ZrcAbsListView中才有实现,看来是在这里实现的了。

@Overridepublic 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代码

@Overridepublic 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自然的飘逸的移动到原来的位置

Adapter

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;}// Adapterfinal 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<ZrcListView.FixedViewInfo> mHeaderViewInfos;ArrayList<ZrcListView.FixedViewInfo> mFooterViewInfos;

没错,这个就是实现ListView的HeaderView和FooterView功能的地方。HeaderViewListAdapter这个类里的这三个。mAdapter就是ListView的视图仅仅有一个,mHeaderViewInfos和mFooterViewInfos则能够有多个,你能够一直addHeaderView往里面加各种View。效果就是ListView.addHeaderView的效果


小白一枚,学习记录,轻喷

转载于:https://www.cnblogs.com/zsychanpin/p/7327223.html

ListView与Adapter笔记:ZrcListView相关推荐

  1. Android listview与adapter用法

    2019独角兽企业重金招聘Python工程师标准>>> 一个ListView通常有两个职责. (1)将数据填充到布局. (2)处理用户的选择点击等操作. 第一点很好理解,ListVi ...

  2. Android控件——ListView之Adapter提供数据(其二)

    2019独角兽企业重金招聘Python工程师标准>>> 上一节中一些列表集合数据到手机屏幕时,通常采用ListView组件+ArrayAdapter. 虽然它能为我们提供展示数据列表 ...

  3. ListView.setAdapter(adapter);空指针异常的解决的总结

    ListView.setAdapter(adapter);空指针异常的解决的总结 参考文章: (1)ListView.setAdapter(adapter);空指针异常的解决的总结 (2)https: ...

  4. listview与adapter用法

    Android listview与adapter用法 listview与adapter用法 博客分类: android 一个ListView通常有两个职责. (1)将数据填充到布局. (2)处理用户的 ...

  5. Android记录15--关于ListView中adapter调用notifyDataSetChanged无效的原因

    Android记录15--关于ListView中adapter调用notifyDataSetChanged无效的原因 2014年1月16日 开发记录 话说这个问题已经困扰我很久了,一直找不到原因,我以 ...

  6. Kotlin ListView设置Adapter

    直接上代码. 1.附上Activity代码 package com.example.chehang168.kotlindemoimport android.view.View import andro ...

  7. android 中自定义安装,Android开发中ListView自定义adapter的封装

    [引入] 我们一般编写listView的时候顺序是这样的: •需要展示的数据集List •为这个数据集编写一个ListView •为这个ListView编写一个Adapter,一般继承自BaseAda ...

  8. ListView与Adapter之间的观察者模式

    ListView与Adapter之间的观察者模式 本文用文字叙述不太好描述,主要通过下面这张图来表示: 这张图体现出了主要的关系 首先创建一个MyAdapter继承BaseAdapter public ...

  9. Android listview和adapter

    ListView 和 Adapter的关系: 就是将ListView绑定的界面中的数据与adapter相适配 MainActivity.java package com.example.listvie ...

  10. android listview替代,Android笔记——RecyclerView替代ListView

    ListView是常用列表控件,但设置Adapter时自定义代码较为复杂,因此Android3.0后,增加RecyclerView替代ListView RecyclerView没有提供OnItemCl ...

最新文章

  1. GitHub 标星 20000+,国产 AI 开源从算法开始突破 | 专访商汤联合创始人林达华
  2. “河边一群鹅,嘘声赶落河。捉得鹅来填肚饿,吃完回家玩老婆!”
  3. python ctime源码_Python3基础 getatime getctime getmtime 文件的最近访问 + 属性修改 + 内容修改时间...
  4. 深入理解C语言的函数调用过程
  5. 【教程】Edraw Max使用教程:如何打印大流程图?
  6. oa服务器怎么修改域名,oa域名服务器配置
  7. Object类中hashCode()和equals()方法详解(附图)
  8. 关于PHP页面显示乱码问题的解决
  9. HTML(XHTML)基础知识(三)——【image】
  10. 451.根据字符出现频率排序
  11. SQL server 2008 r2 安装教程
  12. C语言实训 --- 仓库管理系统(原代码)
  13. 【微信支付】微信支付之 Native 支付
  14. 倾角传感器和陀螺仪传感器的区别
  15. Linux显示2015年日历表
  16. 【Python基础】第十六篇 | 面向对象之高级篇
  17. 硬件基础知识(电容)
  18. 迅为开发板-i.MX6Q开发板飞思卡尔imx6开发板专业推荐
  19. 计算机试题上网部分怎么做,考试经验之谈:计算机一级考试上网题怎么操作?...
  20. 一加账号app_一加应用商店安卓版下载-一加应用商店app下载v1.1.0.1632-西西软件下载...

热门文章

  1. jfreechart linux图片中文显示乱码解决方法
  2. 如何评估一个算法效果
  3. 【基础】深度学习最常用的10个激活函数!(数学原理+优缺点)
  4. 敢问多任务学习优化算法路在何方?|附代码
  5. 【初学者】10个例子带你了解机器学习中的线性代数
  6. 没有顶会的 CV/NLP 方向的博士生毕业出路在哪里?
  7. 数据预处理与特征工程—11.分层采样
  8. 7.1 API:GaussianMixture
  9. 向模块化进军,创建类
  10. “虚拟化 ”和“云计算”计算机技术新概念