zoukankan      html  css  js  c++  java
  • Android XListView实现原理讲解及分析

    XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了。之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无聊,研究了下XListview的实现原理,学到了很多,今天分享给大家。

        提前声明,为了让代码更好的理解,我对代码进行了部分删减和重构,如果大家想看原版代码,请去github自行下载。

        Xlistview项目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分别是XListView主体、header、footer的实现。下面我们分开来介绍。

        下面是修改之后的XListViewHeader代码

    public class XListViewHeader extends LinearLayout {  
      
        private static final String HINT_NORMAL = "下拉刷新";  
        private static final String HINT_READY = "松开刷新数据";  
        private static final String HINT_LOADING = "正在加载...";  
      
        // 正常状态  
        public final static int STATE_NORMAL = 0;  
        // 准备刷新状态,也就是箭头方向发生改变之后的状态  
        public final static int STATE_READY = 1;  
        // 刷新状态,箭头变成了progressBar  
        public final static int STATE_REFRESHING = 2;  
        // 布局容器,也就是根布局  
        private LinearLayout container;  
        // 箭头图片  
        private ImageView mArrowImageView;  
        // 刷新状态显示  
        private ProgressBar mProgressBar;  
        // 说明文本  
        private TextView mHintTextView;  
        // 记录当前的状态  
        private int mState;  
        // 用于改变箭头的方向的动画  
        private Animation mRotateUpAnim;  
        private Animation mRotateDownAnim;  
        // 动画持续时间  
        private final int ROTATE_ANIM_DURATION = 180;  
      
        public XListViewHeader(Context context) {  
            super(context);  
            initView(context);  
        }  
      
        public XListViewHeader(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            initView(context);  
        }  
      
        private void initView(Context context) {  
            mState = STATE_NORMAL;  
            // 初始情况下,设置下拉刷新view高度为0  
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(  
                    LayoutParams.MATCH_PARENT, 0);  
            container = (LinearLayout) LayoutInflater.from(context).inflate(  
                    R.layout.xlistview_header, null);  
            addView(container, lp);  
            // 初始化控件  
            mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow);  
            mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview);  
            mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar);  
            // 初始化动画  
            mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,  
                    Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
                    0.5f);  
            mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);  
            mRotateUpAnim.setFillAfter(true);  
            mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,  
                    Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
                    0.5f);  
            mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);  
            mRotateDownAnim.setFillAfter(true);  
        }  
      
        // 设置header的状态  
        public void setState(int state) {  
            if (state == mState)  
                return;  
      
            // 显示进度  
            if (state == STATE_REFRESHING) {  
                mArrowImageView.clearAnimation();  
                mArrowImageView.setVisibility(View.INVISIBLE);  
                mProgressBar.setVisibility(View.VISIBLE);  
            } else {  
                // 显示箭头  
                mArrowImageView.setVisibility(View.VISIBLE);  
                mProgressBar.setVisibility(View.INVISIBLE);  
            }  
      
            switch (state) {  
            case STATE_NORMAL:  
                if (mState == STATE_READY) {  
                    mArrowImageView.startAnimation(mRotateDownAnim);  
                }  
                if (mState == STATE_REFRESHING) {  
                    mArrowImageView.clearAnimation();  
                }  
                mHintTextView.setText(HINT_NORMAL);  
                break;  
            case STATE_READY:  
                if (mState != STATE_READY) {  
                    mArrowImageView.clearAnimation();  
                    mArrowImageView.startAnimation(mRotateUpAnim);  
                    mHintTextView.setText(HINT_READY);  
                }  
                break;  
            case STATE_REFRESHING:  
                mHintTextView.setText(HINT_LOADING);  
                break;  
            }  
      
            mState = state;  
        }  
      
        public void setVisiableHeight(int height) {  
            if (height < 0)  
                height = 0;  
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) container  
                    .getLayoutParams();  
            lp.height = height;  
            container.setLayoutParams(lp);  
        }  
      
        public int getVisiableHeight() {  
            return container.getHeight();  
        }  
      
        public void show() {  
            container.setVisibility(View.VISIBLE);  
        }  
      
        public void hide() {  
            container.setVisibility(View.INVISIBLE);  
        }  
      
    }  

       XListViewHeader继承自linearLayout,用来实现下拉刷新时的界面展示,可以分为三种状态:正常、准备刷新、正在加载。

        在Linearlayout布局里面,主要有指示箭头、说明文本、圆形加载条三个控件。在构造函数中,调用了initView()进行控件的初始化操作。在添加布局文件的时候,指定高度为0,这是为了隐藏header,然后初始化动画,是为了完成箭头的旋转动作。

        setState()是设置header的状态,因为header需要根据不同的状态,完成控件隐藏、显示、改变文字等操作,这个方法主要是在XListView里面调用。除此之外,还有setVisiableHeight()和getVisiableHeight(),这两个方法是为了设置和获取Header中根布局文件的高度属性,从而完成拉伸和收缩的效果,而show()和hide()则显然就是完成显示和隐藏的效果。

        下面是Header的布局文件

    <?xml version="1.0" encoding="utf-8"?>  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:gravity="bottom" >  
      
        <RelativeLayout  
            android:id="@+id/xlistview_header_content"  
            android:layout_width="match_parent"  
            android:layout_height="60dp"  
            tools:ignore="UselessParent" >  
      
            <TextView  
                android:id="@+id/xlistview_header_hint_textview"  
                android:layout_width="100dp"  
                android:layout_height="wrap_content"  
                android:layout_centerInParent="true"  
                android:gravity="center"  
                android:text="正在加载"  
                android:textColor="@android:color/black"  
                android:textSize="14sp" />  
      
            <ImageView  
                android:id="@+id/xlistview_header_arrow"  
                android:layout_width="30dp"  
                android:layout_height="wrap_content"  
                android:layout_centerVertical="true"  
                android:layout_toLeftOf="@id/xlistview_header_hint_textview"  
                android:src="@drawable/xlistview_arrow" />  
      
            <ProgressBar  
                android:id="@+id/xlistview_header_progressbar"  
                style="@style/progressbar_style"  
                android:layout_width="30dp"  
                android:layout_height="30dp"  
                android:layout_centerVertical="true"  
                android:layout_toLeftOf="@id/xlistview_header_hint_textview"  
                android:visibility="invisible" />  
        </RelativeLayout>  
      
    </LinearLayout>  

        说完了Header,我们再看看Footer。Footer是为了完成加载更多功能时候的界面展示,基本思路和Header是一样的,下面是Footer的代码

    public class XListViewFooter extends LinearLayout {  
      
        // 正常状态  
        public final static int STATE_NORMAL = 0;  
        // 准备状态  
        public final static int STATE_READY = 1;  
        // 加载状态  
        public final static int STATE_LOADING = 2;  
      
        private View mContentView;  
        private View mProgressBar;  
        private TextView mHintView;  
      
        public XListViewFooter(Context context) {  
            super(context);  
            initView(context);  
        }  
      
        public XListViewFooter(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            initView(context);  
        }  
      
        private void initView(Context context) {  
      
            LinearLayout moreView = (LinearLayout) LayoutInflater.from(context)  
                    .inflate(R.layout.xlistview_footer, null);  
            addView(moreView);  
            moreView.setLayoutParams(new LinearLayout.LayoutParams(  
                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));  
      
            mContentView = moreView.findViewById(R.id.xlistview_footer_content);  
            mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar);  
            mHintView = (TextView) moreView  
                    .findViewById(R.id.xlistview_footer_hint_textview);  
        }  
      
        /** 
         * 设置当前的状态 
         *  
         * @param state 
         */  
        public void setState(int state) {  
      
            mProgressBar.setVisibility(View.INVISIBLE);  
            mHintView.setVisibility(View.INVISIBLE);  
      
            switch (state) {  
            case STATE_READY:  
                mHintView.setVisibility(View.VISIBLE);  
                mHintView.setText(R.string.xlistview_footer_hint_ready);  
                break;  
      
            case STATE_NORMAL:  
                mHintView.setVisibility(View.VISIBLE);  
                mHintView.setText(R.string.xlistview_footer_hint_normal);  
                break;  
      
            case STATE_LOADING:  
                mProgressBar.setVisibility(View.VISIBLE);  
                break;  
      
            }  
      
        }  
      
        public void setBottomMargin(int height) {  
            if (height > 0) {  
      
                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
                        .getLayoutParams();  
                lp.bottomMargin = height;  
                mContentView.setLayoutParams(lp);  
            }  
        }  
      
        public int getBottomMargin() {  
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
                    .getLayoutParams();  
            return lp.bottomMargin;  
        }  
      
        public void hide() {  
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
                    .getLayoutParams();  
            lp.height = 0;  
            mContentView.setLayoutParams(lp);  
        }  
      
        public void show() {  
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
                    .getLayoutParams();  
            lp.height = LayoutParams.WRAP_CONTENT;  
            mContentView.setLayoutParams(lp);  
        }  
      
    }  

        从上面的代码里面,我们可以看出,footer和header的思路是一样的,只不过,footer的拉伸和显示效果不是通过高度来模拟的,而是通过设置BottomMargin来完成的。

        下面是Footer的布局文件

    <?xml version="1.0" encoding="utf-8"?>  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content" >  
      
        <RelativeLayout  
            android:id="@+id/xlistview_footer_content"  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:padding="5dp"  
            tools:ignore="UselessParent" >  
      
            <ProgressBar  
                android:id="@+id/xlistview_footer_progressbar"  
                style="@style/progressbar_style"  
                android:layout_width="30dp"  
                android:layout_height="30dp"  
                android:layout_centerInParent="true"  
                android:visibility="invisible" />  
      
            <TextView  
                android:id="@+id/xlistview_footer_hint_textview"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:layout_centerInParent="true"  
                android:text="@string/xlistview_footer_hint_normal"  
                android:textColor="@android:color/black"  
                android:textSize="14sp" />  
        </RelativeLayout>  
      
    </LinearLayout>  

        在了解了Header和footer之后,我们就要介绍最核心的XListView的代码实现了。

        在介绍代码实现之前,我先介绍一下XListView的实现原理。

        首先,一旦使用XListView,Footer和Header就已经添加到我们的ListView上面了,XListView就是通过继承ListView,然后处理了屏幕点击事件和控制滑动实现效果的。所以,如果我们的Adapter中getCount()返回的值是20,那么其实XListView里面是有20+2个item的,这个数量即使我们关闭了XListView的刷新和加载功能,也是不会变化的。Header和Footer通过addHeaderView和addFooterView添加上去之后,如果想实现下拉刷新和上拉加载功能,那么就必须有拉伸效果,所以就像上面的那样,Header是通过设置height,Footer是通过设置BottomMargin来模拟拉伸效果。那么回弹效果呢?仅仅通过设置高度或者是间隔是达不到模拟回弹效果的,因此,就需要用Scroller来实现模拟回弹效果。在说明原理之后,我们开始介绍XListView的核心实现原理。

        再次提示,下面的代码经过我重构了,只是为了看起来更好的理解。

    public class XListView extends ListView {  
      
        private final static int SCROLLBACK_HEADER = 0;  
        private final static int SCROLLBACK_FOOTER = 1;  
        // 滑动时长  
        private final static int SCROLL_DURATION = 400;  
        // 加载更多的距离  
        private final static int PULL_LOAD_MORE_DELTA = 100;  
        // 滑动比例  
        private final static float OFFSET_RADIO = 2f;  
        // 记录按下点的y坐标  
        private float lastY;  
        // 用来回滚  
        private Scroller scroller;  
        private IXListViewListener mListViewListener;  
        private XListViewHeader headerView;  
        private RelativeLayout headerViewContent;  
        // header的高度  
        private int headerHeight;  
        // 是否能够刷新  
        private boolean enableRefresh = true;  
        // 是否正在刷新  
        private boolean isRefreashing = false;  
        // footer  
        private XListViewFooter footerView;  
        // 是否可以加载更多  
        private boolean enableLoadMore;  
        // 是否正在加载  
        private boolean isLoadingMore;  
        // 是否footer准备状态  
        private boolean isFooterAdd = false;  
        // total list items, used to detect is at the bottom of listview.  
        private int totalItemCount;  
        // 记录是从header还是footer返回  
        private int mScrollBack;  
      
        private static final String TAG = "XListView";  
      
        public XListView(Context context) {  
            super(context);  
            initView(context);  
        }  
      
        public XListView(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            initView(context);  
        }  
      
        public XListView(Context context, AttributeSet attrs, int defStyle) {  
            super(context, attrs, defStyle);  
            initView(context);  
        }  
      
        private void initView(Context context) {  
      
            scroller = new Scroller(context, new DecelerateInterpolator());  
      
            headerView = new XListViewHeader(context);  
            footerView = new XListViewFooter(context);  
      
            headerViewContent = (RelativeLayout) headerView  
                    .findViewById(R.id.xlistview_header_content);  
            headerView.getViewTreeObserver().addOnGlobalLayoutListener(  
                    new OnGlobalLayoutListener() {  
                        @SuppressWarnings("deprecation")  
                        @Override  
                        public void onGlobalLayout() {  
                            headerHeight = headerViewContent.getHeight();  
                            getViewTreeObserver()  
                                    .removeGlobalOnLayoutListener(this);  
                        }  
                    });  
            addHeaderView(headerView);  
      
        }  
      
        @Override  
        public void setAdapter(ListAdapter adapter) {  
            // 确保footer最后添加并且只添加一次  
            if (isFooterAdd == false) {  
                isFooterAdd = true;  
                addFooterView(footerView);  
            }  
            super.setAdapter(adapter);  
      
        }  
      
        @Override  
        public boolean onTouchEvent(MotionEvent ev) {  
      
            totalItemCount = getAdapter().getCount();  
            switch (ev.getAction()) {  
            case MotionEvent.ACTION_DOWN:  
                // 记录按下的坐标  
                lastY = ev.getRawY();  
                break;  
            case MotionEvent.ACTION_MOVE:  
                // 计算移动距离  
                float deltaY = ev.getRawY() - lastY;  
                lastY = ev.getRawY();  
                // 是第一项并且标题已经显示或者是在下拉  
                if (getFirstVisiblePosition() == 0  
                        && (headerView.getVisiableHeight() > 0 || deltaY > 0)) {  
                    updateHeaderHeight(deltaY / OFFSET_RADIO);  
                } else if (getLastVisiblePosition() == totalItemCount - 1  
                        && (footerView.getBottomMargin() > 0 || deltaY < 0)) {  
                    updateFooterHeight(-deltaY / OFFSET_RADIO);  
                }  
                break;  
      
            case MotionEvent.ACTION_UP:  
      
                if (getFirstVisiblePosition() == 0) {  
                    if (enableRefresh  
                            && headerView.getVisiableHeight() > headerHeight) {  
                        isRefreashing = true;  
                        headerView.setState(XListViewHeader.STATE_REFRESHING);  
                        if (mListViewListener != null) {  
                            mListViewListener.onRefresh();  
                        }  
                    }  
                    resetHeaderHeight();  
                } else if (getLastVisiblePosition() == totalItemCount - 1) {  
                    if (enableLoadMore  
                            && footerView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {  
                        startLoadMore();  
                    }  
                    resetFooterHeight();  
                }  
                break;  
            }  
            return super.onTouchEvent(ev);  
        }  
      
        @Override  
        public void computeScroll() {  
      
            // 松手之后调用  
            if (scroller.computeScrollOffset()) {  
      
                if (mScrollBack == SCROLLBACK_HEADER) {  
                    headerView.setVisiableHeight(scroller.getCurrY());  
                } else {  
                    footerView.setBottomMargin(scroller.getCurrY());  
                }  
                postInvalidate();  
            }  
            super.computeScroll();  
      
        }  
      
        public void setPullRefreshEnable(boolean enable) {  
            enableRefresh = enable;  
      
            if (!enableRefresh) {  
                headerView.hide();  
            } else {  
                headerView.show();  
            }  
        }  
      
        public void setPullLoadEnable(boolean enable) {  
            enableLoadMore = enable;  
            if (!enableLoadMore) {  
                footerView.hide();  
                footerView.setOnClickListener(null);  
            } else {  
                isLoadingMore = false;  
                footerView.show();  
                footerView.setState(XListViewFooter.STATE_NORMAL);  
                footerView.setOnClickListener(new OnClickListener() {  
                    @Override  
                    public void onClick(View v) {  
                        startLoadMore();  
                    }  
                });  
            }  
        }  
      
        public void stopRefresh() {  
            if (isRefreashing == true) {  
                isRefreashing = false;  
                resetHeaderHeight();  
            }  
        }  
      
        public void stopLoadMore() {  
            if (isLoadingMore == true) {  
                isLoadingMore = false;  
                footerView.setState(XListViewFooter.STATE_NORMAL);  
            }  
        }  
      
        private void updateHeaderHeight(float delta) {  
            headerView.setVisiableHeight((int) delta  
                    + headerView.getVisiableHeight());  
            // 未处于刷新状态,更新箭头  
            if (enableRefresh && !isRefreashing) {  
                if (headerView.getVisiableHeight() > headerHeight) {  
                    headerView.setState(XListViewHeader.STATE_READY);  
                } else {  
                    headerView.setState(XListViewHeader.STATE_NORMAL);  
                }  
            }  
      
        }  
      
        private void resetHeaderHeight() {  
            // 当前的可见高度  
            int height = headerView.getVisiableHeight();  
            // 如果正在刷新并且高度没有完全展示  
            if ((isRefreashing && height <= headerHeight) || (height == 0)) {  
                return;  
            }  
            // 默认会回滚到header的位置  
            int finalHeight = 0;  
            // 如果是正在刷新状态,则回滚到header的高度  
            if (isRefreashing && height > headerHeight) {  
                finalHeight = headerHeight;  
            }  
            mScrollBack = SCROLLBACK_HEADER;  
            // 回滚到指定位置  
            scroller.startScroll(0, height, 0, finalHeight - height,  
                    SCROLL_DURATION);  
            // 触发computeScroll  
            invalidate();  
        }  
      
        private void updateFooterHeight(float delta) {  
            int height = footerView.getBottomMargin() + (int) delta;  
            if (enableLoadMore && !isLoadingMore) {  
                if (height > PULL_LOAD_MORE_DELTA) {  
                    footerView.setState(XListViewFooter.STATE_READY);  
                } else {  
                    footerView.setState(XListViewFooter.STATE_NORMAL);  
                }  
            }  
            footerView.setBottomMargin(height);  
      
        }  
      
        private void resetFooterHeight() {  
            int bottomMargin = footerView.getBottomMargin();  
            if (bottomMargin > 0) {  
                mScrollBack = SCROLLBACK_FOOTER;  
                scroller.startScroll(0, bottomMargin, 0, -bottomMargin,  
                        SCROLL_DURATION);  
                invalidate();  
            }  
        }  
      
        private void startLoadMore() {  
            isLoadingMore = true;  
            footerView.setState(XListViewFooter.STATE_LOADING);  
            if (mListViewListener != null) {  
                mListViewListener.onLoadMore();  
            }  
        }  
      
        public void setXListViewListener(IXListViewListener l) {  
            mListViewListener = l;  
        }  
      
        public interface IXListViewListener {  
      
            public void onRefresh();  
      
            public void onLoadMore();  
        }  
    }  

      在三个构造函数中,都调用initView进行了header和footer的初始化,并且定义了一个Scroller,并传入了一个减速的插值器,为了模仿回弹效果。在initView方法里面,因为header可能还没初始化完毕,所以通过GlobalLayoutlistener来获取了header的高度,然后addHeaderView添加到了listview上面。

        通过重写setAdapter方法,保证Footer最后天假,并且只添加一次。

        最重要的,要属onTouchEvent了。在方法开始之前,通过getAdapter().getCount()获取到了item的总数,便于计算位置。这个操作在源代码中是通过scrollerListener完成的,因为ScrollerListener在这里没大有用,所以我直接去掉了,然后把位置改到了这里。如果在setAdapter里面获取的话,只能获取到没有header和footer的item数量。

        在ACTION_DOWN里面,进行了lastY的初始化,lastY是为了判断移动方向的,因为在ACTION_MOVE里面,通过ev.getRawY()-lastY可以计算出手指的移动趋势,如果>0,那么就是向下滑动,反之向上。getRowY()是获取元Y坐标,意思就是和Window和View坐标没有关系的坐标,代表在屏幕上的绝对位置。然后在下面的代码里面,如果第一项可见并且header的可见高度>0或者是向下滑动,就说明用户在向下拉动或者是向上拉动header,也就是指示箭头显示的时候的状态,这时候调用了updateHeaderHeight,来更新header的高度,实现header可以跟随手指动作上下移动。这里有个OFFSET_RADIO,这个值是一个移动比例,就是说,你手指在Y方向上移动400px,如果比例是2,那么屏幕上的控件移动就是400px/2=200px,可以通过这个值来控制用户的滑动体验。下面的关于footer的判断与此类似,不再赘述。

       当用户移开手指之后,ACTION_UP方法就会被调用。在这里面,只对可见位置是0和item总数-1的位置进行了处理,其实正好对应header和footer。如果位置是0,并且可以刷新,然后当前的header可见高度>原始高度的话,就说明用户确实是要进行刷新操作,所以通过setState改变header的状态,如果有监听器的话,就调用onRefresh方法,然后调用resetHeaderHeight初始化header的状态,因为footer的操作如出一辙,所以不再赘述。但是在footer中有一个PULL_LOAD_MORE_DELTA,这个值是加载更多触发条件的临界值,只有footer的间隔超过这个值之后,才能够触发加载更多的功能,因此我们可以修改这个值来改变用户体验。

        说到现在,大家应该明白基本的原理了,其实XListView就是通过对用户手势的方向和距离的判断,来动态的改变Header和Footer实现的功能,所以如果我们也有类似的需求,就可以参照这种思路进行自定义。

        下面再说几个比较重要的方法。

        前面我们说道,在ACTION_MOVE里面,会不断的调用下面的updateXXXX方法,来动态的改变header和fooer的状态,

    private void updateHeaderHeight(float delta) {  
            headerView.setVisiableHeight((int) delta  
                    + headerView.getVisiableHeight());  
            // 未处于刷新状态,更新箭头  
            if (enableRefresh && !isRefreashing) {  
                if (headerView.getVisiableHeight() > headerHeight) {  
                    headerView.setState(XListViewHeader.STATE_READY);  
                } else {  
                    headerView.setState(XListViewHeader.STATE_NORMAL);  
                }  
            }  
      
        }  
      
    private void updateFooterHeight(float delta) {  
            int height = footerView.getBottomMargin() + (int) delta;  
            if (enableLoadMore && !isLoadingMore) {  
                if (height > PULL_LOAD_MORE_DELTA) {  
                    footerView.setState(XListViewFooter.STATE_READY);  
                } else {  
                    footerView.setState(XListViewFooter.STATE_NORMAL);  
                }  
            }  
            footerView.setBottomMargin(height);  
      
        }  

     在移开手指之后,会调用下面的resetXXX来初始化header和footer的状态

    private void resetHeaderHeight() {  
            // 当前的可见高度  
            int height = headerView.getVisiableHeight();  
            // 如果正在刷新并且高度没有完全展示  
            if ((isRefreashing && height <= headerHeight) || (height == 0)) {  
                return;  
            }  
            // 默认会回滚到header的位置  
            int finalHeight = 0;  
            // 如果是正在刷新状态,则回滚到header的高度  
            if (isRefreashing && height > headerHeight) {  
                finalHeight = headerHeight;  
            }  
            mScrollBack = SCROLLBACK_HEADER;  
            // 回滚到指定位置  
            scroller.startScroll(0, height, 0, finalHeight - height,  
                    SCROLL_DURATION);  
            // 触发computeScroll  
            invalidate();  
        }  
      
    private void resetFooterHeight() {  
            int bottomMargin = footerView.getBottomMargin();  
            if (bottomMargin > 0) {  
                mScrollBack = SCROLLBACK_FOOTER;  
                scroller.startScroll(0, bottomMargin, 0, -bottomMargin,  
                        SCROLL_DURATION);  
                invalidate();  
            }  
        }  

        我们可以看到,滚动操作不是通过直接的设置高度来实现的,而是通过Scroller.startScroll()来实现的,通过调用此方法,computeScroll()就会被调用,然后在这个里面,根据mScrollBack区分是哪一个滚动,然后再通过设置高度和间隔,就可以完成收缩的效果了。
        至此,整个XListView的实现原理就完全的搞明白了,以后如果做滚动类的自定义控件,应该也有思路了。

    1. private void resetHeaderHeight() {  
    2.         // 当前的可见高度  
    3.         int height = headerView.getVisiableHeight();  
    4.         // 如果正在刷新并且高度没有完全展示  
    5.         if ((isRefreashing && height <= headerHeight) || (height == 0)) {  
    6.             return;  
    7.         }  
    8.         // 默认会回滚到header的位置  
    9.         int finalHeight = 0;  
    10.         // 如果是正在刷新状态,则回滚到header的高度  
    11.         if (isRefreashing && height > headerHeight) {  
    12.             finalHeight = headerHeight;  
    13.         }  
    14.         mScrollBack = SCROLLBACK_HEADER;  
    15.         // 回滚到指定位置  
    16.         scroller.startScroll(0, height, 0, finalHeight - height,  
    17.                 SCROLL_DURATION);  
    18.         // 触发computeScroll  
    19.         invalidate();  
    20.     }  
    21.   
    22. private void resetFooterHeight() {  
    23.         int bottomMargin = footerView.getBottomMargin();  
    24.         if (bottomMargin > 0) {  
    25.             mScrollBack = SCROLLBACK_FOOTER;  
    26.             scroller.startScroll(0, bottomMargin, 0, -bottomMargin,  
    27.                     SCROLL_DURATION);  
    28.             invalidate();  
    29.         }  
    30.     }  
  • 相关阅读:
    如何实现共享软件网络授权认证,包括注册新用户、登录、修改密码等操作
    Winform分页控件支持表头全选操作实现之最优方法
    Web开发框架之权限管理系统
    DevExpress控件开发常用要点(项目总结版)
    Winform开发框架之证件套打
    Winform开发框架之系统重新登录、自动登录实现
    我的WCF开发框架简化版及基于NET.TCP传输方式的实现
    Winform开发框架之数据曲线报表
    在GridControl控件中使用SearchLookUpEdit构建数据快速输入
    合理的布局,绚丽的样式,谈谈Winform程序的界面设计
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/5323373.html
Copyright © 2011-2022 走看看