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


这次就不上图了,例子太多太多了,想必大家都见过.这个功能的实现,简直是开发者必备的.
我也不过多介绍了,网上详细介绍的博客太多太多了,若想深入了解,请参考网上其他博文.
在这里,我只是按照自己的理解,模拟实现了一个,顺便代码贡献出来.
我对之详细标明的注释,想必如果不懂的同学们,看注释也应该明白,前提是,你要耐心看,因为代码有点多,但是我整理过了,还算清晰.
详细代码:
[java]
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] 
<com.jj.drag.DragListView 
    android:id="@+id/dlv_main" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:cacheColorHint="#00000000" /> 
在Activity中的调用,相比大家都清楚,开个异步或线程进行加载数据,这里我简单说一下异步使用,线程同理.
代码如下:
[java] 
/***
     * 执行类 异步
     * 
     * @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]
/***
         * 如果是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]
@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] 
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、付费专栏及课程。

余额充值