android ListView的上部下拉刷新下部点击加载更多具体实现及拓展


这次就不上图了,例子太多太多了,想必大家都见过.这个功能的实现,简直是开发者必备的.

我也不过多介绍了,网上详细介绍的博客太多太多了,若想深入了解,请参考网上其他博文.

在这里,我只是按照自己的理解,模拟实现了一个,顺便代码贡献出来.

我对之详细标明的注释,想必如果不懂的同学们,看注释也应该明白,前提是,你要耐心看,因为代码有点多,但是我整理过了,还算清晰.

详细代码:

[java] view plaincopy

    package com.jj.drag;  
      
    import android.content.Context;  
    import android.util.AttributeSet;  
    import android.util.Log;  
    import android.view.LayoutInflater;  
    import android.view.MotionEvent;  
    import android.view.View;  
    import android.view.View.OnClickListener;  
    import android.view.ViewGroup;  
    import android.view.animation.Animation;  
    import android.view.animation.LinearInterpolator;  
    import android.view.animation.RotateAnimation;  
    import android.widget.AbsListView;  
    import android.widget.AbsListView.OnScrollListener;  
    import android.widget.ImageView;  
    import android.widget.LinearLayout;  
    import android.widget.ListView;  
    import android.widget.ProgressBar;  
    import android.widget.RelativeLayout;  
    import android.widget.TextView;  
      
    /***
     * 自定义拖拉ListView
     *  
     * @author zhangjia
     *  
     */  
    public class DragListView extends ListView implements OnScrollListener,  
            OnClickListener {  
        // 拖拉ListView枚举所有状态  
        private enum DListViewState {  
            LV_NORMAL, // 普通状态  
            LV_PULL_REFRESH, // 下拉状态(为超过mHeadViewHeight)  
            LV_RELEASE_REFRESH, // 松开可刷新状态(超过mHeadViewHeight)  
            LV_LOADING;// 加载状态  
        }  
      
        // 点击加载更多枚举所有状态  
        private enum DListViewLoadingMore {  
            LV_NORMAL, // 普通状态  
            LV_LOADING, // 加载状态  
            LV_OVER; // 结束状态  
        }  
      
        private View mHeadView;// 头部headView  
        private TextView mRefreshTextview; // 刷新msg(mHeadView)  
        private TextView mLastUpdateTextView;// 更新事件(mHeadView)  
        private ImageView mArrowImageView;// 下拉图标(mHeadView)  
        private ProgressBar mHeadProgressBar;// 刷新进度体(mHeadView)  
      
        private int mHeadViewWidth; // headView的宽(mHeadView)  
        private int mHeadViewHeight;// headView的高(mHeadView)  
      
        private View mFootView;// 尾部mFootView  
        private View mLoadMoreView;// mFootView 的view(mFootView)  
        private TextView mLoadMoreTextView;// 加载更多.(mFootView)  
        private View mLoadingView;// 加载中...View(mFootView)  
      
        private Animation animation, reverseAnimation;// 旋转动画,旋转动画之后旋转动画.  
      
        private int mFirstItemIndex = -1;// 当前视图能看到的第一个项的索引  
      
        // 用于保证startY的值在一个完整的touch事件中只被记录一次  
        private boolean mIsRecord = false;  
      
        private int mStartY, mMoveY;// 按下是的y坐标,move时的y坐标  
      
        private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉状态.(自定义枚举)  
      
        private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 加载更多默认状态.  
      
        private final static int RATIO = 2;// 手势下拉距离比.  
      
        private boolean mBack = false;// headView是否返回.  
      
        private OnRefreshLoadingMoreListener onRefreshLoadingMoreListener;// 下拉刷新接口(自定义)  
      
        private boolean isScroller = true;// 是否屏蔽ListView滑动。  
      
        public DragListView(Context context) {  
            super(context, null);  
            initDragListView(context);  
        }  
      
        public DragListView(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            initDragListView(context);  
        }  
      
        // 注入下拉刷新接口  
        public void setOnRefreshListener(  
                OnRefreshLoadingMoreListener onRefreshLoadingMoreListener) {  
            this.onRefreshLoadingMoreListener = onRefreshLoadingMoreListener;  
        }  
      
        /***
         * 初始化ListView
         */  
        public void initDragListView(Context context) {  
      
            String time = "1994.12.05";// 更新时间  
      
            initHeadView(context, time);// 初始化该head.  
      
            initLoadMoreView(context);// 初始化footer  
      
            setOnScrollListener(this);// ListView滚动监听  
        }  
      
        /***
         * 初始话头部HeadView
         *  
         * @param context
         *            上下文
         * @param time
         *            上次更新时间
         */  
        public void initHeadView(Context context, String time) {  
            mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);  
            mArrowImageView = (ImageView) mHeadView  
                    .findViewById(R.id.head_arrowImageView);  
            mArrowImageView.setMinimumWidth(60);  
      
            mHeadProgressBar = (ProgressBar) mHeadView  
                    .findViewById(R.id.head_progressBar);  
      
            mRefreshTextview = (TextView) mHeadView  
                    .findViewById(R.id.head_tipsTextView);  
      
            mLastUpdateTextView = (TextView) mHeadView  
                    .findViewById(R.id.head_lastUpdatedTextView);  
            // 显示更新事件  
            mLastUpdateTextView.setText("最近更新:" + time);  
      
            measureView(mHeadView);  
            // 获取宽和高  
            mHeadViewWidth = mHeadView.getMeasuredWidth();  
            mHeadViewHeight = mHeadView.getMeasuredHeight();  
      
            addHeaderView(mHeadView, null, false);// 将初始好的ListView add进拖拽ListView  
            // 在这里我们要将此headView设置到顶部不显示位置.  
            mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
      
            initAnimation();// 初始化动画  
        }  
      
        /***
         * 初始化底部加载更多控件
         */  
        private void initLoadMoreView(Context context) {  
            mFootView = LayoutInflater.from(context).inflate(R.layout.footer, null);  
      
            mLoadMoreView = mFootView.findViewById(R.id.load_more_view);  
      
            mLoadMoreTextView = (TextView) mFootView  
                    .findViewById(R.id.load_more_tv);  
      
            mLoadingView = (LinearLayout) mFootView  
                    .findViewById(R.id.loading_layout);  
      
            mLoadMoreView.setOnClickListener(this);  
      
            addFooterView(mFootView);  
        }  
      
        /***
         * 初始化动画
         */  
        private void initAnimation() {  
            // 旋转动画  
            animation = new RotateAnimation(0, -180,  
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
            animation.setInterpolator(new LinearInterpolator());// 匀速  
            animation.setDuration(250);  
            animation.setFillAfter(true);// 停留在最后状态.  
            // 反向旋转动画  
            reverseAnimation = new RotateAnimation(-180, 0,  
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
            reverseAnimation.setInterpolator(new LinearInterpolator());  
            reverseAnimation.setDuration(250);  
            reverseAnimation.setFillAfter(true);  
        }  
      
        /***
         * 作用:测量 headView的宽和高.
         *  
         * @param child
         */  
        private void measureView(View child) {  
            ViewGroup.LayoutParams p = child.getLayoutParams();  
            if (p == null) {  
                p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,  
                        ViewGroup.LayoutParams.WRAP_CONTENT);  
            }  
            int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);  
            int lpHeight = p.height;  
            int childHeightSpec;  
            if (lpHeight > 0) {  
                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,  
                        MeasureSpec.EXACTLY);  
            } else {  
                childHeightSpec = MeasureSpec.makeMeasureSpec(0,  
                        MeasureSpec.UNSPECIFIED);  
            }  
            child.measure(childWidthSpec, childHeightSpec);  
        }  
      
        /***
         * touch 事件监听
         */  
        @Override  
        public boolean onTouchEvent(MotionEvent ev) {  
            switch (ev.getAction()) {  
            // 按下  
            case MotionEvent.ACTION_DOWN:  
                doActionDown(ev);  
                break;  
            // 移动  
            case MotionEvent.ACTION_MOVE:  
                doActionMove(ev);  
                break;  
            // 抬起  
            case MotionEvent.ACTION_UP:  
                doActionUp(ev);  
                break;  
            default:  
                break;  
            }  
            /***
             * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动.
             * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行.
             */  
            if (isScroller) {  
                return super.onTouchEvent(ev);  
            } else {  
                return true;  
            }  
      
        }  
      
        /***
         * 摁下操作
         *  
         * 作用:获取摁下是的y坐标
         *  
         * @param event
         */  
        void doActionDown(MotionEvent event) {  
            if (mIsRecord == false && mFirstItemIndex == 0) {  
                mStartY = (int) event.getY();  
                mIsRecord = true;  
            }  
        }  
      
        /***
         * 拖拽移动操作
         *  
         * @param event
         */  
        void doActionMove(MotionEvent event) {  
            mMoveY = (int) event.getY();// 获取实时滑动y坐标  
            // 检测是否是一次touch事件.  
            if (mIsRecord == false && mFirstItemIndex == 0) {  
                mStartY = (int) event.getY();  
                mIsRecord = true;  
            }  
            /***
             * 如果touch关闭或者正处于Loading状态的话 return.
             */  
            if (mIsRecord == false || mlistViewState == DListViewState.LV_LOADING) {  
                return;  
            }  
            // 向下啦headview移动距离为y移动的一半.(比较友好)  
            int offset = (mMoveY - mStartY) / RATIO;  
      
            switch (mlistViewState) {  
            // 普通状态  
            case LV_NORMAL: {  
                // 如果<0,则意味着上滑动.  
                if (offset > 0) {  
                    // 设置headView的padding属性.  
                    mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
                    switchViewState(DListViewState.LV_PULL_REFRESH);// 下拉状态  
                }  
      
            }  
                break;  
            // 下拉状态  
            case LV_PULL_REFRESH: {  
                setSelection(0);// 时时保持在顶部.  
                // 设置headView的padding属性.  
                mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
                if (offset < 0) {  
                    /***
                     * 要明白为什么isScroller = false;
                     */  
                    isScroller = false;  
                    switchViewState(DListViewState.LV_NORMAL);// 普通状态  
                    Log.e("jj", "isScroller=" + isScroller);  
                } else if (offset > mHeadViewHeight) {// 如果下拉的offset超过headView的高度则要执行刷新.  
                    switchViewState(DListViewState.LV_RELEASE_REFRESH);// 更新为可刷新的下拉状态.  
                }  
            }  
                break;  
            // 可刷新状态  
            case LV_RELEASE_REFRESH: {  
                setSelection(0);时时保持在顶部  
                // 设置headView的padding属性.  
                mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
                // 下拉offset>0,但是没有超过headView的高度.那么要goback 原装.  
                if (offset >= 0 && offset <= mHeadViewHeight) {  
                    mBack = true;  
                    switchViewState(DListViewState.LV_PULL_REFRESH);  
                } else if (offset < 0) {  
                    switchViewState(DListViewState.LV_NORMAL);  
                } else {  
      
                }  
            }  
                break;  
            default:  
                return;  
            }  
            ;  
        }  
      
        /***
         * 手势抬起操作
         *  
         * @param event
         */  
        public void doActionUp(MotionEvent event) {  
            mIsRecord = false;// 此时的touch事件完毕,要关闭。  
            isScroller = true;// ListView可以Scrooler滑动.  
            mBack = false;  
            // 如果下拉状态处于loading状态.  
            if (mlistViewState == DListViewState.LV_LOADING) {  
                return;  
            }  
            // 处理相应状态.  
            switch (mlistViewState) {  
            // 普通状态  
            case LV_NORMAL:  
      
                break;  
            // 下拉状态  
            case LV_PULL_REFRESH:  
                mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
                switchViewState(mlistViewState.LV_NORMAL);  
                break;  
            // 刷新状态  
            case LV_RELEASE_REFRESH:  
                mHeadView.setPadding(0, 0, 0, 0);  
                switchViewState(mlistViewState.LV_LOADING);  
                onRefresh();// 下拉刷新  
                break;  
            }  
      
        }  
      
        // 切换headview视图  
        private void switchViewState(DListViewState state) {  
      
            switch (state) {  
            // 普通状态  
            case LV_NORMAL: {  
                mArrowImageView.clearAnimation();// 清除动画  
                mArrowImageView.setImageResource(R.drawable.arrow);  
            }  
                break;  
            // 下拉状态  
            case LV_PULL_REFRESH: {  
                mHeadProgressBar.setVisibility(View.GONE);// 隐藏进度条  
                mArrowImageView.setVisibility(View.VISIBLE);// 下拉图标  
                mRefreshTextview.setText("下拉可以刷新");  
                mArrowImageView.clearAnimation();// 清除动画  
      
                // 是有可刷新状态(LV_RELEASE_REFRESH)转为这个状态才执行,其实就是你下拉后在上拉会执行.  
                if (mBack) {  
                    mBack = false;  
                    mArrowImageView.clearAnimation();// 清除动画  
                    mArrowImageView.startAnimation(reverseAnimation);// 启动反转动画  
                }  
            }  
                break;  
            // 松开刷新状态  
            case LV_RELEASE_REFRESH: {  
                mHeadProgressBar.setVisibility(View.GONE);// 隐藏进度条  
                mArrowImageView.setVisibility(View.VISIBLE);// 显示下拉图标  
                mRefreshTextview.setText("松开获取更多");  
                mArrowImageView.clearAnimation();// 清除动画  
                mArrowImageView.startAnimation(animation);// 启动动画  
            }  
                break;  
            // 加载状态  
            case LV_LOADING: {  
                Log.e("!!!!!!!!!!!", "convert to IListViewState.LVS_LOADING");  
                mHeadProgressBar.setVisibility(View.VISIBLE);  
                mArrowImageView.clearAnimation();  
                mArrowImageView.setVisibility(View.GONE);  
                mRefreshTextview.setText("载入中...");  
            }  
                break;  
            default:  
                return;  
            }  
            // 切记不要忘记时时更新状态。  
            mlistViewState = state;  
      
        }  
      
        /***
         * 下拉刷新
         */  
        private void onRefresh() {  
            if (onRefreshLoadingMoreListener != null) {  
                onRefreshLoadingMoreListener.onRefresh();  
            }  
        }  
      
        /***
         * 下拉刷新完毕
         */  
        public void onRefreshComplete() {  
            mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);// 回归.  
            switchViewState(mlistViewState.LV_NORMAL);//  
        }  
      
        /***
         * 点击加载更多
         *  
         * @param flag
         *            数据是否已全部加载完毕
         */  
        public void onLoadMoreComplete(boolean flag) {  
            if (flag) {  
                updateLoadMoreViewState(DListViewLoadingMore.LV_OVER);  
            } else {  
                updateLoadMoreViewState(DListViewLoadingMore.LV_NORMAL);  
            }  
      
        }  
      
        // 更新Footview视图  
        private void updateLoadMoreViewState(DListViewLoadingMore state) {  
            switch (state) {  
            // 普通状态  
            case LV_NORMAL:  
                mLoadingView.setVisibility(View.GONE);  
                mLoadMoreTextView.setVisibility(View.VISIBLE);  
                mLoadMoreTextView.setText("查看更多");  
                break;  
            // 加载中状态  
            case LV_LOADING:  
                mLoadingView.setVisibility(View.VISIBLE);  
                mLoadMoreTextView.setVisibility(View.GONE);  
                break;  
            // 加载完毕状态  
            case LV_OVER:  
                mLoadingView.setVisibility(View.GONE);  
                mLoadMoreTextView.setVisibility(View.VISIBLE);  
                mLoadMoreTextView.setText("加载完毕");  
                break;  
            default:  
                break;  
            }  
            loadingMoreState = state;  
        }  
      
        /***
         * ListView 滑动监听
         */  
        @Override  
        public void onScrollStateChanged(AbsListView view, int scrollState) {  
      
        }  
      
        @Override  
        public void onScroll(AbsListView view, int firstVisibleItem,  
                int visibleItemCount, int totalItemCount) {  
            mFirstItemIndex = firstVisibleItem;  
        }  
      
        /***
         * 底部点击事件
         */  
        @Override  
        public void onClick(View v) {  
            // 防止重复点击  
            if (onRefreshLoadingMoreListener != null  
                    && loadingMoreState == DListViewLoadingMore.LV_NORMAL) {  
                updateLoadMoreViewState(DListViewLoadingMore.LV_LOADING);  
                onRefreshLoadingMoreListener.onLoadMore();// 对外提供方法加载更多.  
            }  
      
        }  
      
        /***
         * 自定义接口
         */  
        public interface OnRefreshLoadingMoreListener {  
            /***
             * // 下拉刷新执行
             */  
            void onRefresh();  
      
            /***
             * 点击加载更多
             */  
            void onLoadMore();  
        }  
      
    }  



上面就是全部代码,其实重要的是明白理解,这样我们还可以进行拓展.

具体应用:(只需要这样引用即可.)
[java] view plaincopy

    <com.jj.drag.DragListView  
        android:id="@+id/dlv_main"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:cacheColorHint="#00000000" />  

在Activity中的调用,相比大家都清楚,开个异步或线程进行加载数据,这里我简单说一下异步使用,线程同理.

代码如下:
[java] view plaincopy

    /***
         * 执行类 异步
         *  
         * @author zhangjia
         *  
         */  
        class MyAsyncTask extends AsyncTask<Void, Void, Void> {  
            private Context context;  
            private int index;// 用于判断是下拉刷新还是点击加载更多  
      
            public MyAsyncTask(Context context, int index) {  
                this.context = context;  
                this.index = index;  
            }  
      
            @Override  
            protected Void doInBackground(Void... params) {  
                try {  
                    Thread.sleep(2000);  
                } catch (InterruptedException e1) {  
                    e1.printStackTrace();  
                }  
      
                return null;  
            }  
      
            @Override  
            protected void onPreExecute() {  
                super.onPreExecute();  
            }  
      
            @Override  
            protected void onPostExecute(Void result) {  
                super.onPostExecute(result);  
                if (index == DRAG_INDEX)  
                    dlv_main.onRefreshComplete();  
                else if (index == LOADMORE_INDEX)  
                    dlv_main.onLoadMoreComplete(false);  
            }  
      
        }  

先声明一点,这个只是个示例,所以这部分代码写的不够友好,也请见谅.

就说道这里,最后展示一下效果:

                

至于如果显示,如何adapter.notifyDataSetChanged();那就要大家开发时候自己调理了.

最后说明一点:网上有好多介绍下拉刷新的例子,但是他们没有对滑动进行处理,比如,我下拉的时候现在不想刷新了,这时我又向上滑动,正常的处理,应该滑动到FirstItemIndex=1就是顶部,滑动就结束了.(意思就是要下拉和listview正常滑动要分开)可是网上一些案例都没有对之处理,用起来不友好,大家可以看看成功案例,那些新浪,腾讯,百度等.

解决方法:(这是onTouch方法中的一部分.)

[java] view plaincopy

    /***
             * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动.
             * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行.
             */  
            if (isScroller) {  
                return super.onTouchEvent(ev);  
            } else {  
                return true;  
            }  

要问Why的话,那么你就要去详细看Touch种种事件,记住,这里用到的不是分发与拦截,分发拦截流程如下:

Activity 的dispatchTouchEvent开始分发给子的View,如果该View是ViewGroup的话,那么执行其dispatchTouchEvent进行分发,在执行相应的onInterceptTouchEvent拦截.如果要想实现上诉说的那种效果,那么在自定义ListView中对拦截分发方法是无效的,只有在ListView的上一层进行处理,比我我们在外层自定义一个布局,等等,实现起来总之麻烦一个字,其实我们也可以考虑考虑onTouchEvent事件的实现,

ListView.java

[java] view plaincopy

    @Override  
       public boolean onTouchEvent(MotionEvent ev) {  
           if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {  
               // Don't handle edge touches immediately -- they may actually belong to one of our  
               // descendants.  
               return false;  
           }  
           return super.onTouchEvent(ev);  
       }  

继续点击查看父类,这里就不显示了,自己可以查看源码,其实就是我们ListView滑动的具体实现,而此时我们只是想临时屏蔽掉此滑动,那么我们只需要不调用父类的onTouchEvent不就OK的,是的,确实如此,而何时进行屏蔽,大家就仔细看上面源码实现吧,解释的也很清楚,这样大家都明白了吧。注:有疑问请留言!之前这个例子android 自定义ScrollView实现反弹效果(以及解决和ListView之间的冲突)没有解决这个问题,因为处境不同.(不过正在完善,相信也会完美的实现这些效果,因为原理上是行的通的。)


知识拓展:

首先我们还是看一些案例:

      

效果就是可以上下拖拽.而用在最多的地方就是ListView,而普通的布局拖拽直接自定义布局就OK了,详情请参考上面连接那篇文章.

实现起来也不是很麻烦,就是对上面那个自定义类稍作修改,把底部也做成动态拖拽效果就OK了.

这里不详细讲解,因为注释相当明确,如有疑问,请指出.

代码如下:

[java] view plaincopy

    package com.jj.drag;  
      
    import android.content.Context;  
    import android.os.AsyncTask;  
    import android.util.AttributeSet;  
    import android.util.Log;  
    import android.view.LayoutInflater;  
    import android.view.MotionEvent;  
    import android.view.View;  
    import android.view.View.OnClickListener;  
    import android.view.ViewGroup;  
    import android.view.animation.Animation;  
    import android.view.animation.LinearInterpolator;  
    import android.view.animation.RotateAnimation;  
    import android.widget.AbsListView;  
    import android.widget.AbsListView.OnScrollListener;  
    import android.widget.ImageView;  
    import android.widget.LinearLayout;  
    import android.widget.ListView;  
    import android.widget.ProgressBar;  
    import android.widget.RelativeLayout;  
    import android.widget.TextView;  
      
    /***
     * 自定义拖拉ListView
     *  
     * @author zhangjia
     *  
     */  
    public class DragListView extends ListView implements OnScrollListener,  
            OnClickListener {  
        // 下拉ListView枚举所有状态  
        private enum DListViewState {  
            LV_NORMAL, // 普通状态  
            LV_PULL_REFRESH, // 下拉状态(为超过mHeadViewHeight)  
      
        }  
      
        // 点击加载更多枚举所有状态  
        private enum DListViewLoadingMore {  
            LV_NORMAL, // 普通状态  
            LV_PULL_REFRESH, // 上拉状态(为超过mHeadViewHeight)  
        }  
      
        private View mHeadView, mFootView;// 头部headView  
      
        private int mHeadViewWidth; // headView的宽(mHeadView)  
        private int mHeadViewHeight;// headView的高(mHeadView)  
      
        private int mFirstItemIndex = -1;// 当前视图能看到的第一个项的索引  
      
        private int mLastItemIndex = -1;// 当前视图中是否是最后一项.  
      
        // 用于保证startY的值在一个完整的touch事件中只被记录一次  
        private boolean mIsRecord = false;// 针对下拉  
      
        private boolean mIsRecord_B = false;// 针对上拉  
      
        private int mStartY, mMoveY;// 按下是的y坐标,move时的y坐标  
      
        private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉状态.(自定义枚举)  
      
        private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 加载更多默认状态.  
      
        private final static int RATIO = 2;// 手势下拉距离比.  
      
        private boolean isScroller = true;// 是否屏蔽ListView滑动。  
      
        private MyAsynTask myAsynTask;// 任务  
        private final static int DRAG_UP = 1, DRAG_DOWN = 2;  
      
        public DragListView(Context context) {  
            super(context, null);  
            initDragListView(context);  
        }  
      
        public DragListView(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            initDragListView(context);  
        }  
      
        /***
         * 初始化ListView
         */  
        public void initDragListView(Context context) {  
      
            initHeadView(context);// 初始化该head.  
      
            initFooterView(context);// 初始化footer  
      
            setOnScrollListener(this);// ListView滚动监听  
        }  
      
        /***
         * 初始话头部HeadView
         *  
         * @param context
         *            上下文
         * @param time
         *            上次更新时间
         */  
        public void initHeadView(Context context) {  
            mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);  
            measureView(mHeadView);  
            // 获取宽和高  
            mHeadViewWidth = mHeadView.getMeasuredWidth();  
            mHeadViewHeight = mHeadView.getMeasuredHeight();  
      
            addHeaderView(mHeadView, null, false);// 将初始好的ListView add进拖拽ListView  
            // 在这里我们要将此headView设置到顶部不显示位置.  
            mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
      
        }  
      
        /***
         * 初始化底部加载更多控件
         */  
        private void initFooterView(Context context) {  
            mFootView = LayoutInflater.from(context).inflate(R.layout.head, null);  
            addFooterView(mFootView, null, false);// 将初始好的ListView add进拖拽ListView  
            // 在这里我们要将此FooterView设置到底部不显示位置.  
            mFootView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
        }  
      
        /***
         * 作用:测量 headView的宽和高.
         *  
         * @param child
         */  
        private void measureView(View child) {  
            ViewGroup.LayoutParams p = child.getLayoutParams();  
            if (p == null) {  
                p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,  
                        ViewGroup.LayoutParams.WRAP_CONTENT);  
            }  
            int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);  
            int lpHeight = p.height;  
            int childHeightSpec;  
            if (lpHeight > 0) {  
                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,  
                        MeasureSpec.EXACTLY);  
            } else {  
                childHeightSpec = MeasureSpec.makeMeasureSpec(0,  
                        MeasureSpec.UNSPECIFIED);  
            }  
            child.measure(childWidthSpec, childHeightSpec);  
        }  
      
        /***
         * touch 事件监听
         */  
        @Override  
        public boolean onTouchEvent(MotionEvent ev) {  
      
            switch (ev.getAction()) {  
            // 按下  
            case MotionEvent.ACTION_DOWN:  
                doActionDown_B(ev);  
                doActionDown(ev);  
                break;  
            // 移动  
            case MotionEvent.ACTION_MOVE:  
                doActionMove_B(ev);  
                doActionMove(ev);  
                break;  
            // 抬起  
            case MotionEvent.ACTION_UP:  
                doActionUp_B(ev);  
                doActionUp(ev);  
                break;  
            default:  
                break;  
            }  
      
            /***
             * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动.
             * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行.
             */  
            if (isScroller) {  
                return super.onTouchEvent(ev);  
            } else {  
                return true;  
            }  
      
        }  
      
        /***
         * 摁下操作
         *  
         * 作用:获取摁下是的y坐标
         *  
         * @param event
         */  
        void doActionDown(MotionEvent event) {  
            // 如果是第一项且是一次touch  
            if (mIsRecord == false && mFirstItemIndex == 0) {  
                mStartY = (int) event.getY();  
                mIsRecord = true;  
            }  
        }  
      
        /***
         * 摁下操作 底部
         *  
         * 作用:获取摁下是的y坐标
         */  
        void doActionDown_B(MotionEvent event) {  
            // 如果是第一项且是一次touch  
            if (mIsRecord_B == false && mLastItemIndex == getCount()) {  
                mStartY = (int) event.getY();  
                mIsRecord_B = true;  
            }  
        }  
      
        /***
         * 拖拽移动操作
         *  
         * @param event
         */  
        void doActionMove(MotionEvent event) {  
      
            // 判断是否是第一项,若不是直接返回  
            mMoveY = (int) event.getY();// 获取实时滑动y坐标  
      
            // 检测是否是一次touch事件.  
            if (mIsRecord == false && mFirstItemIndex == 0) {  
                mStartY = (int) event.getY();  
                mIsRecord = true;  
            }  
            // 直接返回说明不是第一项  
            if (mIsRecord == false)  
                return;  
      
            // 向下啦headview移动距离为y移动的一半.(比较友好)  
            int offset = (mMoveY - mStartY) / RATIO;  
      
            switch (mlistViewState) {  
            // 普通状态  
            case LV_NORMAL: {  
                // 说明下拉  
                if (offset > 0) {  
                    // 设置headView的padding属性.  
                    mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
                    mlistViewState = DListViewState.LV_PULL_REFRESH;// 下拉状态  
                }  
            }  
                break;  
            // 下拉状态  
            case LV_PULL_REFRESH: {  
                setSelection(0);// 时时保持在顶部.  
                // 设置headView的padding属性.  
                mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
                if (offset < 0) {  
                    /***
                     * 要明白为什么isScroller = false;
                     */  
                    isScroller = false;  
                    mlistViewState = mlistViewState.LV_NORMAL;  
                }  
            }  
                break;  
            default:  
                return;  
            }  
        }  
      
        void doActionMove_B(MotionEvent event) {  
            mMoveY = (int) event.getY();// 获取实时滑动y坐标  
            // 检测是否是一次touch事件.(若mFirstItemIndex为0则要初始化mStartY)  
            if (mIsRecord_B == false && mLastItemIndex == getCount()) {  
                mStartY = (int) event.getY();  
                mIsRecord_B = true;  
            }  
            // 直接返回说明不是最后一项  
            if (mIsRecord_B == false)  
                return;  
      
            // 向下啦headview移动距离为y移动的一半.(比较友好)  
            int offset = (mMoveY - mStartY) / RATIO;  
      
            switch (loadingMoreState) {  
            // 普通状态  
            case LV_NORMAL: {  
                // 说明上拉  
                if (offset < 0) {  
                    int distance = Math.abs(offset);  
                    // 设置headView的padding属性.  
                    mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);  
                    loadingMoreState = loadingMoreState.LV_PULL_REFRESH;// 下拉状态  
                }  
            }  
                break;  
            // 上拉状态  
            case LV_PULL_REFRESH: {  
                setSelection(getCount() - 1);// 时时保持最底部  
                // 设置headView的padding属性.  
                int distance = Math.abs(offset);  
                mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);  
                // 说明下滑  
                if (offset > 0) {  
                    /***
                     * 要明白为什么isScroller = false;
                     */  
                    isScroller = false;  
                    loadingMoreState = loadingMoreState.LV_NORMAL;  
                }  
            }  
                break;  
            default:  
                return;  
            }  
        }  
      
        /***
         * 手势抬起操作
         *  
         * @param event
         */  
        public void doActionUp(MotionEvent event) {  
            mIsRecord = false;// 此时的touch事件完毕,要关闭。  
            mIsRecord_B = false; // 此时的touch事件完毕,要关闭。  
            isScroller = true;// ListView可以Scrooler滑动.  
            mlistViewState = mlistViewState.LV_NORMAL;// 状态也回归最初状态  
      
            // 执行相应动画.  
            myAsynTask = new MyAsynTask();  
            myAsynTask.execute(DRAG_UP);  
      
        }  
      
        private void doActionUp_B(MotionEvent event) {  
            mIsRecord = false;// 此时的touch事件完毕,要关闭。  
            isScroller = true;// ListView可以Scrooler滑动.  
      
            loadingMoreState = loadingMoreState.LV_NORMAL;// 状态也回归最初状态  
      
            // 执行相应动画.  
            myAsynTask = new MyAsynTask();  
            myAsynTask.execute(DRAG_DOWN);  
        }  
      
        /***
         * ListView 滑动监听
         */  
        @Override  
        public void onScrollStateChanged(AbsListView view, int scrollState) {  
      
        }  
      
        @Override  
        public void onScroll(AbsListView view, int firstVisibleItem,  
                int visibleItemCount, int totalItemCount) {  
            mFirstItemIndex = firstVisibleItem;  
            mLastItemIndex = firstVisibleItem + visibleItemCount;  
      
        }  
      
        @Override  
        public void onClick(View v) {  
      
        }  
      
        /***
         * 用于产生动画
         *  
         * @author zhangjia
         *  
         */  
        private class MyAsynTask extends AsyncTask<Integer, Integer, Void> {  
            private final static int STEP = 30;// 步伐  
            private final static int TIME = 5;// 休眠时间  
            private int distance;// 距离(该距离指的是:mHeadView的PaddingTop+mHeadView的高度,及默认位置状态.)  
            private int number;// 循环执行次数.  
            private int disPadding;// 时时padding距离.  
            private int DRAG;  
      
            @Override  
            protected Void doInBackground(Integer... params) {  
                try {  
                    this.DRAG = params[0];  
                    if (params[0] == DRAG_UP) {  
                        // 获取距离.  
                        distance = mHeadView.getPaddingTop()  
                                + Math.abs(mHeadViewHeight);  
                    } else {  
                        // 获取距离.  
                        distance = mFootView.getPaddingTop()  
                                + Math.abs(mHeadViewHeight);  
                    }  
      
                    // 获取循环次数.  
                    if (distance % STEP == 0) {  
                        number = distance / STEP;  
                    } else {  
                        number = distance / STEP + 1;  
                    }  
                    // 进行循环.  
                    for (int i = 0; i < number; i++) {  
                        Thread.sleep(TIME);  
                        publishProgress(STEP);  
                    }  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                return null;  
            }  
      
            @Override  
            protected void onProgressUpdate(Integer... values) {  
                super.onProgressUpdate(values);  
      
                switch (DRAG) {  
                case DRAG_UP:  
                    disPadding = Math.max(mHeadView.getPaddingTop() - STEP, -1  
                            * mHeadViewHeight);  
                    mHeadView.setPadding(0, disPadding, 0, 0);// 回归.  
                    break;  
                case DRAG_DOWN:  
                    disPadding = Math.max(mFootView.getPaddingTop() - STEP, -1  
                            * mHeadViewHeight);  
                    mFootView.setPadding(0, disPadding, 0, 0);// 回归.  
                    break;  
                default:  
                    break;  
                }  
      
            }  
      
        }  
      
    }  

运行效果:

                             

         默认效果                                             下拉拖拽效果(会自动回缩)               上拉拖拽效果(会自动回缩)           

前面那章实现起来有点小BUG,正在处理,不过这个实现起来没有发现什么BUG,要说BUG的话,那么就是优化,因为我觉得上面效果是实现了,可是性能觉得有点差,比如说“我每次UP的时候要执行任务,那么就要创建任务对象,你想想看,每次执行都要创建,那么要创建多少对象,虽说JAVA虚拟机会自动回收,但是总觉得不是很完善,嗯,临时就如此了,自己在研究研究看.

至于微信,陌陌等大多数应用都是(数据少的话,就上下都可以拖拽,只是一个人性效果,而数据多的话,上部用于加载过时数据.下部只是个形式.),效果实现起来也不难,只是进行了些判断,效果嘛,其实上面自定义ListView整理下就OK了.

上面我详细给出了两个自定义源码的实现,大家可以直接引用.


在这里我将源码上传,如果上面看的不明白的话,你可以下载,只要你耐心看,我相信大家都能弄明白,都会进行响应扩展的.其实我们要的就是创新,而不是简单应用.


源码一


源码二
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值