ListView与Adapter笔记:ZrcListView
怕自己说的不清不楚,先来一个郭神的文章镇楼: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中才有实现,看来是在这里实现的了。
@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自然的飘逸的移动到原来的位置
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;
}
// 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<ZrcListView.FixedViewInfo> mHeaderViewInfos;
ArrayList<ZrcListView.FixedViewInfo> mFooterViewInfos;
没错,这个就是实现ListView的HeaderView和FooterView功能的地方。HeaderViewListAdapter这个类里的这三个。mAdapter就是ListView的视图仅仅有一个,mHeaderViewInfos和mFooterViewInfos则能够有多个,你能够一直addHeaderView往里面加各种View。效果就是ListView.addHeaderView的效果
小白一枚,学习记录,轻喷
ListView与Adapter笔记:ZrcListView的更多相关文章
- 超简便的ListView中Adapter的写法
对于 ListView 的使用,他有两个重点的部分,一个是下拉刷新和加载更多,这个今天我们不讲,另外一个是 BaseAdapter 的使用,这个是今天的主角,BaseAdapter 中又有 ViewH ...
- ListView和Adapter数据适配器的简单介绍
ListView 显示大量相同格式数据 常用属性: listSelector listView每项在选中.按下等不同状态时的Drawable divider ...
- ListView和Adapter的配合使用以及Adapter的重写
ListView和Adapter的使用 首先介绍一下ListView是Android开发过程中较为常见的组件之一,它将数据以列表的形式展现出来.一般而言,一个ListView由以下三个元素组成: ...
- 在为ListView设置adapter时出错
为listView设置adapter,代码如下: SimpleAdapter simpleAdapter = new SimpleAdapter(this, listItems, R.layout.m ...
- Android listview与adapter用法
listview与adapter用法 博客分类: android 一个ListView通常有两个职责. (1)将数据填充到布局. (2)处理用户的选择点击等操作. 第一点很好理解,ListView ...
- ListView 和 Adapter用法
一个ListView通常有两个职责. (1)将数据填充到布局. (2)处理用户的选择点击等操作. 第一点很好理解,ListView就是实现这个功能的.第二点也不难做到,在后面的学习中读者会发现,这非常 ...
- ListView及Adapter的使用
一.使用ArrayAdapter 其中ArrayAdapter的构造函数有如下几个,其中resource是指每个列表项的布局文件,objects是指列表项的数据源,此处通常指一个数组 ArrayAda ...
- android 开发之 ListView 与Adapter 应用实践
在开发android中,ListView 的应用显得非常频繁,只要需要显示列表展示的应用,可以说是必不可少,下面是记录开发中应用到ListView与Adapter 使用的实例: ListView 所在 ...
- [Android] Android RecycleView和ListView 自定义Adapter封装类
在网上查看了很多对应 Android RecycleView和ListView 自定义Adapter封装类 的文章,主要存在几个问题: 一).网上代码一大抄,复制来复制去,大部分都运行不起来,或者 格 ...
随机推荐
- App网络管理
安卓开发一般都需要进行日志管理,常用操作老司机已为你封装完毕,你可以用这份工具进行管理,具体可以查看源码,现在为你开车,Demo传送门. 站点 系统日志输出工具类 → AppKeyBoardMgr g ...
- asp.net core webapi文件上传
最近开发一个新项目,使用了asp.net core 2.0,采用webapi开发后台,postgresql为数据库.最先来的问题就是上传文件的问题. POST文件的一些坑 使用默认模板创建webapi ...
- 编译安装PHP 时遇到问题解决方法.
编译安装PHP时出现下面的错误代码: error 2 checking for pkg-config... /usr/bin/pkg-config configure: error: Cannot f ...
- ML神器:sklearn的快速使用
传统的机器学习任务从开始到建模的一般流程是:获取数据 -> 数据预处理 -> 训练建模 -> 模型评估 -> 预测,分类.本文我们将依据传统机器学习的流程,看看在每一步流程中都 ...
- mapbox-gl象形文字字体glyph生成
简介 mapbox-gl可以对文字显示各种字体(依赖ttf文件),内部采用的是读取protobuf文件 环境条件 硬件:mac.网络 软件:nodejs.npm 创建mapbox-gl可用的字体pro ...
- [转载] ZooKeeper简介
转载自http://blog.csdn.net/kobejayandy/article/details/17738435 一. Paxos 基于消息传递通信模型的分布式系统,不可避免的会发生 ...
- python相关资料
http://blog.jobbole.com/59535/ http://www.nryoung.org/blog/2013/2/28/python-threading/ http://blog.j ...
- 如何使用webpack优化首屏渲染时间
其实说到性能优化,他的范围太广了,今天我们就只聊一聊通过webpack配置减少http请求数量这个点吧. 简单说下工作中遇到的问题吧,我们做的一个项目中首页用了十多张图片,每张图片都是一个静态资源,所 ...
- 【Java入门提高篇】Day2 接口
上一篇讲完了抽象类,这一篇主要讲解比抽象类更加抽象的内容--接口. 什么是接口呢?先来看个栗子: /** * @author Frank * @create 2017/11/22 * @descrip ...
- python实现图片批量剪裁的程序
from PIL import Image import os fin = 'D:/test' fout = 'D:/test2' for file in os.listdir(fin): file_ ...