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的效果



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

时间: 2024-12-07 10:14:25

ListView与Adapter笔记:ZrcListView的相关文章

超简便的ListView中Adapter的写法

对于 ListView 的使用,他有两个重点的部分,一个是下拉刷新和加载更多,这个今天我们不讲,另外一个是 BaseAdapter 的使用,这个是今天的主角,BaseAdapter 中又有 ViewHolder 模式来实现缓存视图 继承BaseAdapter类,实现以下几个方法 getCount() ->int 返回的是 List的个数 getView(int, View, ViewGroup)->View 返回显示的视图 getItemId(int position) ->long返回

ListView和Adapter信息显示

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" andr

ListView和Adapter数据适配器的简单介绍

ListView 显示大量相同格式数据 常用属性: listSelector            listView每项在选中.按下等不同状态时的Drawable divider                ListView每项间的间隔Drawable dividerHeight        ListView每项间间隔的间隔高度 常用方法: setAdapter()                设置数据适配器 setOnItemClickListener()        设置每项点击事件

ListView和Adapter的配合使用以及Adapter的重写

ListView和Adapter的使用 首先介绍一下ListView是Android开发过程中较为常见的组件之一,它将数据以列表的形式展现出来.一般而言,一个ListView由以下三个元素组成: 1.View,用于展示列表,通常是一个xml所指定的.大家都知道Android的界面基本上是由xml文件负责完成的,所以ListView的界面也理所应当的使用了xml定义.例如在ListView中经常用到的“android.R.layout.simple_list_item”等, 就是Android系统

listview及adapter

http://blog.csdn.net/shaojie519/article/details/6595720 http://blog.csdn.net/liuhe688/article/details/6532519 http://tech.cncms.com/shouji/android/78565.html http://blog.csdn.net/wangjinyu501/article/details/7716785 http://blog.csdn.net/wangkuifeng01

Android ListView 和 Adapter 从本地/网络获取歌曲列表

本文内容 环境 项目结构 演示1:SimpleAdapter 演示2:BaseAdapter 演示3:customlazylist 演示4:customcompletelazylist 本文只给出演示概要,代码太多,贴出来意义不大,自己下载调试一下,点击此处下载. 本文通过四个示例,循序渐进地演示,将歌曲列表加载到 ListView 控件,歌曲列表,包括缩略图.歌手名.歌曲名等信息,或存放在本地,或以 JSON 形式存放在网络. 环境 Windows 2008 R2 64 位 Eclipse A

ListView及Adapter的使用

一.使用ArrayAdapter 其中ArrayAdapter的构造函数有如下几个,其中resource是指每个列表项的布局文件,objects是指列表项的数据源,此处通常指一个数组 ArrayAdapter(Context context, int resource) ArrayAdapter(Context context, int resource, int textViewResourceId) ArrayAdapter(Context context, int resource, T[

第十九讲:ListView与Adapter(一)

天将降大任于是人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为.--<孟子·告子下> 本讲内容:ListView列表组件 与 Adapter适配器的用法 一.ListView列表组件: 作用:ListView通常有两个职责. (1)将数据填充到布局. (2)处理用户的选择点击等操作(通过绑定监听器). 创建一个ListView需要3个元素. (1)ListView展示每一列的View. (2)填入View的数据或者图片等. (3)连接数据与ListView的适配器. ListVi

第十九讲:ListView与Adapter(二)

会当凌绝顶,一览众山小. -- 杜  甫<望岳> 本讲内容:ListView列表组件 与 Adapter适配器的用法 一.ListView使用SimpleAdapter 很多时候需要在列表中展示一些除了文字以外的东西,比如图片等.这时候可以使用SimpleAdapter.可以通过它 使用simpleAdapter的数据一般都是用HashMap构成的列表,列表的每一节对应ListView的每一行.通过SimpleAdapter的构造函数,将HashMap的每个键的数据映射到布局文件中对应控件上.