listview 代码简洁功能强大的下拉刷新和上拉加载

首先在listview 里套一个父容器
  <xxx.view.PullToRefreshView
        android:id="@+id/lay_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <ListView
            android:id="@+id/chatListView"
            android:layout_width="match_parent"
            android:layout_height="fill_parent"
            android:divider="#d7d7d7"
            android:footerDividersEnabled="true" >
        </ListView>
    </xxx.view.PullToRefreshView>

自定义的view

public class PullToRefreshView extends LinearLayout {
    private boolean canScroolUp = true;// 是否可以上拉
    private boolean canScrooldown = true;// 是否可以下拉

    private boolean canScroolUpNodata = true;
    private boolean canScroolDownNoData = true;
    // private static final String TAG = "PullToRefreshView";
    // refresh states
    private static final int PULL_TO_REFRESH = 2;
    private static final int RELEASE_TO_REFRESH = 3;
    private static final int REFRESHING = 4;
    // pull state
    private static final int PULL_UP_STATE = 0;
    private static final int PULL_DOWN_STATE = 1;

    private double MIN_Y_VALUE = 5.0;

    public void setMiny(double miny) {
        MIN_Y_VALUE = miny;
    }

    /**
     * last y
     */
    private int mLastMotionY;
    private int mLastMotionX;
    /**
     * lock
     */
    private boolean mLock;
    /**
     * header view
     */
    private View mHeaderView;
    /**
     * footer view
     */
    private View mFooterView;
    /**
     * list or grid
     */
    private AdapterView<?> mAdapterView;
    /**
     * scrollview
     */
    private ScrollView mScrollView;
    /**
     * header view height
     */
    private int mHeaderViewHeight;
    /**
     * footer view height
     */
    private int mFooterViewHeight;
    /**
     * header view image
     */
    private ImageView mHeaderImageView;
    /**
     * footer view image
     */
    private ImageView mFooterImageView;
    /**
     * header tip text
     */
    private TextView mHeaderTextView;
    /**
     * 
     */
    private TextView mHeaderTimeTV;
    /**
     * footer tip text
     */
    private TextView mFooterTextView;
    /**
     * header refresh time
     */
    // private TextView mHeaderUpdateTextView;
    /**
     * footer refresh time
     */
    // private TextView mFooterUpdateTextView;
    /**
     * header progress bar
     */
    private ProgressBar mHeaderProgressBar;
    /**
     * footer progress bar
     */
    private ProgressBar mFooterProgressBar;
    /**
     * layout inflater
     */
    private LayoutInflater mInflater;
    /**
     * header view current state
     */
    private int mHeaderState;
    /**
     * footer view current state
     */
    private int mFooterState;
    /**
     * pull state,pull up or pull down;PULL_UP_STATE or PULL_DOWN_STATE
     */
    private int mPullState;
    /**
     * 变为向下的箭头,改变箭头方向
     */
    private RotateAnimation mFlipAnimation;
    /**
     * 变为逆向的箭头,旋转
     */
    private RotateAnimation mReverseFlipAnimation;
    /**
     * footer refresh listener
     */
    private OnFooterRefreshListener mOnFooterRefreshListener;
    /**
     * footer refresh listener
     */
    private OnHeaderRefreshListener mOnHeaderRefreshListener;

    /**
     * last update time
     */

    public PullToRefreshView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PullToRefreshView(Context context) {
        super(context);
        init();
    }

    /**
     * init
     * 
     * @description
     * @param context
     *            hylin 2012-7-26上午10:08:33
     */
    private void init() {
        // Load all of the animations we need in code rather than through XML
        mFlipAnimation = new RotateAnimation(0, -180,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);// 创建旋转动画,动画从0度你逆时针旋转180度,旋转中心为本视图自身的中心。
        mFlipAnimation.setInterpolator(new LinearInterpolator());// 设置动画的为匀速动画
        mFlipAnimation.setDuration(250);// 设置动画持续时间为250毫秒
        mFlipAnimation.setFillAfter(true);// 设置动画结束后是否让动画保持在结束的位置
        mReverseFlipAnimation = new RotateAnimation(-180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
        mReverseFlipAnimation.setDuration(250);
        mReverseFlipAnimation.setFillAfter(true);
        mInflater = LayoutInflater.from(getContext());// 获得一个layoutinflater
        // header view 在此添加,保证是第一个添加到linearlayout的最上端
        addHeaderView();
    }

    private void addHeaderView() {
        // header view
        mHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);

        mHeaderImageView = (ImageView) mHeaderView
                .findViewById(R.id.pull_to_refresh_image);
        mHeaderTextView = (TextView) mHeaderView
                .findViewById(R.id.pull_to_refresh_text);
        mHeaderTimeTV = (TextView) mHeaderView
                .findViewById(R.id.head_lastUpdatedTextView);
        // mHeaderUpdateTextView = (TextView) mHeaderView
        // .findViewById(R.id.pull_to_refresh_updated_at);
        mHeaderProgressBar = (ProgressBar) mHeaderView
                .findViewById(R.id.pull_to_refresh_progress);
        // header layout
        measureView(mHeaderView);
        // 得到视图的实际大小,和getHeight的区别是,当视图超出屏幕,这是得到的值为getHeight加上超出部分的大小
        mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
                mHeaderViewHeight);
        // 设置topMargin的值为负的header View高度,即将其隐藏在最上方
        params.topMargin = -(mHeaderViewHeight);
        // mHeaderView.setLayoutParams(params1);
        addView(mHeaderView, params);

    }

    private void addFooterView() {
        // footer view
        mFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);
        mFooterImageView = (ImageView) mFooterView
                .findViewById(R.id.pull_to_load_image);
        mFooterTextView = (TextView) mFooterView
                .findViewById(R.id.pull_to_load_text);
        mFooterProgressBar = (ProgressBar) mFooterView
                .findViewById(R.id.pull_to_load_progress);
        // footer layout
        measureView(mFooterView);
        mFooterViewHeight = mFooterView.getMeasuredHeight();
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
                mFooterViewHeight);
        // int top = getHeight();
        // params.topMargin
        // =getHeight();//在这里getHeight()==0,但在onInterceptTouchEvent()方法里getHeight()已经有值了,不再是0;
        // getHeight()什么时候会赋值,稍候再研究一下
        // 由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏
        addView(mFooterView, params);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // footer view 在此添加保证添加到linearlayout中的最后
        addFooterView();
        initContentAdapterView();
    }

    /**
     * init AdapterView like ListView,GridView and so on;or init ScrollView
     * 
     * @description hylin 2012-7-30下午8:48:12
     */
    private void initContentAdapterView() {
        int count = getChildCount();
        if (count < 3) {
            throw new IllegalArgumentException(
                    "this layout must contain 3 child views,and AdapterView or ScrollView must in the second position!");
        }
        View view = null;
        for (int i = 0; i < count - 1; ++i) {
            view = getChildAt(i);
            if (view instanceof AdapterView<?>) {
                mAdapterView = (AdapterView<?>) view;
            }
            if (view instanceof ScrollView) {
                // finish later
                mScrollView = (ScrollView) view;
            }
        }
        if (mAdapterView == null && mScrollView == null) {
            throw new IllegalArgumentException(
                    "must contain a AdapterView or ScrollView in this layout!");
        }
    }

    private void measureView(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_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);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        int y = (int) e.getRawY();
        int x = (int) e.getRawX();
        switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 首先拦截down事件,记录y坐标
            mLastMotionY = y;
            mLastMotionX = x;
            break;
        case MotionEvent.ACTION_MOVE:
            // deltaY > 0 是向下运动,< 0是向上运动
            int deltaY = y - mLastMotionY;
            int deltaX = x - mLastMotionX;
            final float xDiff = Math.abs(deltaX);
            final float yDiff = Math.abs(deltaY);
            if (yDiff <= MIN_Y_VALUE || xDiff >= yDiff) {
                return false;
            }
            if (isRefreshViewScroll(deltaY)) {
                return true;
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            break;
        }
        return false;
    }

    /*
     * 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return
     * false)则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理)
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mLock) {
            return true;
        }
        int y = (int) event.getRawY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // onInterceptTouchEvent已经记录
            // mLastMotionY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaY = y - mLastMotionY;
            if (mPullState == PULL_DOWN_STATE) {
                // PullToRefreshView执行下拉
                headerPrepareToRefresh(deltaY);
                // setHeaderPadding(-mHeaderViewHeight);
            } else if (mPullState == PULL_UP_STATE) {
                // PullToRefreshView执行上拉
                footerPrepareToRefresh(deltaY);
            }
            mLastMotionY = y;
            break;
        case MotionEvent.ACTION_UP:

        case MotionEvent.ACTION_CANCEL:
            int topMargin = getHeaderTopMargin();
            if (mPullState == PULL_DOWN_STATE) {
                if (topMargin >= 0) {
                    // 开始刷新
                    headerRefreshing();
                } else {
                    // 还没有执行刷新,重新隐藏
                    setHeaderTopMargin(-mHeaderViewHeight);
                }
            } else if (mPullState == PULL_UP_STATE) {
                if (Math.abs(topMargin) >= mHeaderViewHeight
                        + mFooterViewHeight) {
                    // 开始执行footer 刷新
                    footerRefreshing();
                } else {
                    // 还没有执行刷新,重新隐藏
                    setHeaderTopMargin(-mHeaderViewHeight);
                }
            }
            break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 可以上拉
     */
    public void enableScroolUp() {
        canScroolUp = true;
    }

    /**
     * 不可以上拉
     */
    public void disableScroolUp() {
        canScroolUp = false;
    }

    /**
     * 可以下拉
     */
    public void enableScroolDown() {
        canScrooldown = true;
    }

    /**
     * 不可以下拉
     */
    public void disableScroolDown() {
        canScrooldown = false;
    }

    /**
     * 当没有数据的时候可以上拉
     */
    public void enableScroolUpNoData() {
        canScroolUpNodata = true;
    }

    /**
     * 当没有数据的时候不可以上拉
     */
    public void disableScroolUpNoData() {
        canScroolUpNodata = false;
    }

    /**
     * 当没有数据的时候可以下拉
     */
    public void enableScroolDownNoData() {
        canScroolDownNoData = true;
    }

    /**
     * 当没有数据的时候不可以下拉
     */
    public void disableScroolDownData() {
        canScroolDownNoData = false;
    }

    /**
     * 是否应该到了父View,即PullToRefreshView滑动
     * 
     * @param deltaY
     *            , deltaY > 0 是向下运动,< 0是向上运动
     * @return
     */
    private boolean isRefreshViewScroll(int deltaY) {
        if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {
            return false;
        }
        if (deltaY > 0 && !canScrooldown)
            return false;
        if (deltaY < 0 && !canScroolUp)
            return false;
        // 对于ListView和GridView
        if (mAdapterView != null) {
            // 子view(ListView or GridView)滑动到最顶端
            if (deltaY > 0) {
                View child = mAdapterView.getChildAt(0);
                if (child == null) {
                    // 如果mAdapterView中没有数据,不拦截
                    mPullState = PULL_DOWN_STATE;
                    return canScroolUpNodata;
                }
                if (mAdapterView.getFirstVisiblePosition() == 0
                        && child.getTop() == 0) {
                    mPullState = PULL_DOWN_STATE;
                    return true;
                }
                int top = child.getTop();
                int padding = mAdapterView.getPaddingTop();
                if (mAdapterView.getFirstVisiblePosition() == 0
                        && Math.abs(top - padding) <= 8) {// 这里之前用3可以判断,但现在不行,还没找到原因
                    mPullState = PULL_DOWN_STATE;
                    return true;
                }

            } else if (deltaY < 0) {
                View lastChild = mAdapterView.getChildAt(mAdapterView
                        .getChildCount() - 1);
                if (lastChild == null) {
                    // 如果mAdapterView中没有数据,不拦截
                    mPullState = PULL_UP_STATE;
                    return canScroolDownNoData;
                }
                // 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view,
                // 等于父View的高度说明mAdapterView已经滑动到最后
                if (lastChild.getBottom() <= getHeight()
                        && mAdapterView.getLastVisiblePosition() == mAdapterView
                                .getCount() - 1) {
                    mPullState = PULL_UP_STATE;
                    return true;
                }
            }
        }
        // 对于ScrollView
        if (mScrollView != null) {
            // 子scroll view滑动到最顶端
            View child = mScrollView.getChildAt(0);
            if (deltaY > 0 && mScrollView.getScrollY() == 0) {
                mPullState = PULL_DOWN_STATE;
                return true;
            } else if (deltaY < 0
                    && child.getMeasuredHeight() <= getHeight()
                            + mScrollView.getScrollY()) {
                mPullState = PULL_UP_STATE;
                return true;
            }
        }
        return false;
    }

    /**
     * header 准备刷新,手指移动过程,还没有释放
     * 
     * @param deltaY
     *            ,手指滑动的距离
     */
    private void headerPrepareToRefresh(int deltaY) {
        int newTopMargin = changingHeaderViewTopMargin(deltaY);
        // 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态
        if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {
            mHeaderTextView.setText(R.string.pull_to_refresh_release_label);
            // mHeaderUpdateTextView.setVisibility(View.VISIBLE);
            mHeaderImageView.clearAnimation();
            mHeaderImageView.startAnimation(mFlipAnimation);
            mHeaderState = RELEASE_TO_REFRESH;
        } else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放

            mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);
            if (mHeaderState == RELEASE_TO_REFRESH) {
                mHeaderImageView.clearAnimation();
                mHeaderImageView.startAnimation(mReverseFlipAnimation);
            }
            mHeaderState = PULL_TO_REFRESH;
        }
    }

    /**
     * footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view
     * 高度是一样,都是通过修改header view的topmargin的值来达到
     * 
     * @param deltaY
     *            ,手指滑动的距离
     */
    private void footerPrepareToRefresh(int deltaY) {
        int newTopMargin = changingHeaderViewTopMargin(deltaY);
        // 如果header view topMargin 的绝对值大于或等于header + footer 的高度
        // 说明footer view 完全显示出来了,修改footer view 的提示状态
        if (Math.abs(newTopMargin) >= (mHeaderViewHeight + mFooterViewHeight)
                && mFooterState != RELEASE_TO_REFRESH) {
            mFooterTextView
                    .setText(R.string.pull_to_refresh_footer_release_label);
            mFooterImageView.clearAnimation();
            mFooterImageView.startAnimation(mFlipAnimation);
            mFooterState = RELEASE_TO_REFRESH;
        } else if (Math.abs(newTopMargin) < (mHeaderViewHeight + mFooterViewHeight)) {

            mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);
            System.out.println(mFooterTextView.getText().toString()
                    .equals(R.string.pull_to_refresh_footer_pull_label));
            System.out.println(mFooterTextView.getText().toString());
            System.out.println(R.string.pull_to_refresh_footer_pull_label);
            if (mFooterState == RELEASE_TO_REFRESH) {
                mFooterImageView.clearAnimation();
                mFooterImageView.startAnimation(mReverseFlipAnimation);
            }
            mFooterState = PULL_TO_REFRESH;
        }
    }

    /**
     * 修改Header view top margin的值
     * 
     * @description
     * @param deltaY
     * @return hylin 2012-7-31下午1:14:31
     */
    private int changingHeaderViewTopMargin(int deltaY) {
        LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
        float newTopMargin = params.topMargin + deltaY * 0.3f;
        // 这里对上拉做一下限制,因为当前上拉后然后不释放手指直接下拉,会把下拉刷新给触发了,感谢网友yufengzungzhe的指出
        // 表示如果是在上拉后一段距离,然后直接下拉
        if (deltaY > 0 && mPullState == PULL_UP_STATE
                && Math.abs(params.topMargin) <= mHeaderViewHeight) {
            return params.topMargin;
        }
        // 同样地,对下拉做一下限制,避免出现跟上拉操作时一样的bug
        if (deltaY < 0 && mPullState == PULL_DOWN_STATE
                && Math.abs(params.topMargin) >= mHeaderViewHeight) {
            return params.topMargin;
        }
        params.topMargin = (int) newTopMargin;
        mHeaderView.setLayoutParams(params);
        invalidate();
        return params.topMargin;
    }

    /**
     * header refreshing
     * 
     * @description hylin 2012-7-31上午9:10:12
     */
    private void headerRefreshing() {
        mHeaderState = REFRESHING;
        setHeaderTopMargin(0);
        mHeaderImageView.setVisibility(View.GONE);
        mHeaderImageView.clearAnimation();
        mHeaderImageView.setImageDrawable(null);
        mHeaderProgressBar.setVisibility(View.VISIBLE);
        mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);
        if (mOnHeaderRefreshListener != null) {
            mOnHeaderRefreshListener.onHeaderRefresh(this);
        }
    }

    /**
     * footer refreshing
     * 
     * @description hylin 2012-7-31上午9:09:59
     */
    private void footerRefreshing() {
        mFooterState = REFRESHING;
        int top = mHeaderViewHeight + mFooterViewHeight;
        setHeaderTopMargin(-top);
        mFooterImageView.setVisibility(View.GONE);
        mFooterImageView.clearAnimation();
        mFooterImageView.setImageDrawable(null);
        mFooterProgressBar.setVisibility(View.VISIBLE);
        mFooterTextView
                .setText(R.string.pull_to_refresh_footer_refreshing_label);
        if (mOnFooterRefreshListener != null) {
            mOnFooterRefreshListener.onFooterRefresh(this);
        }
    }

    /**
     * 设置header view 的topMargin的值
     * 
     * @description
     * @param topMargin
     *            ,为0时,说明header view 刚好完全显示出来; 为-mHeaderViewHeight时,说明完全隐藏了
     *            hylin 2012-7-31上午11:24:06
     */
    private void setHeaderTopMargin(int topMargin) {
        LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
        params.topMargin = topMargin;
        mHeaderView.setLayoutParams(params);
        invalidate();
    }

    /**
     * header view 完成更新后恢复初始状态
     * 
     * @description hylin 2012-7-31上午11:54:23
     */
    public void onHeaderRefreshComplete() {
        setHeaderTopMargin(-mHeaderViewHeight);
        mHeaderImageView.setVisibility(View.VISIBLE);
        mHeaderImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow);
        mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);
        SimpleDateFormat format = new SimpleDateFormat("MM-dd  HH:mm",
                Locale.getDefault());
        String date = format.format(new Date());
        mHeaderTimeTV.setText("最近更新:" + date);
        mHeaderProgressBar.setVisibility(View.GONE);
        // mHeaderTimeTV.setText("");
        mHeaderState = PULL_TO_REFRESH;
    }

    /**
     * Resets the list to a normal state after a refresh.
     * 
     * @param lastUpdated
     *            Last updated at.
     */
    public void onHeaderRefreshComplete(CharSequence lastUpdated) {
        setLastUpdated(lastUpdated);
        onHeaderRefreshComplete();
    }

    /**
     * footer view 完成更新后恢复初始状态
     */
    public void onFooterRefreshComplete() {
        setHeaderTopMargin(-mHeaderViewHeight);
        mFooterImageView.setVisibility(View.VISIBLE);
        mFooterImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow_up);
        mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);
        mFooterProgressBar.setVisibility(View.GONE);
        // mHeaderUpdateTextView.setText("");
        mFooterState = PULL_TO_REFRESH;
    }

    /**
     * Set a text to represent when the list was last updated.
     * 
     * @param lastUpdated
     *            Last updated at.
     */
    public void setLastUpdated(CharSequence lastUpdated) {
        if (lastUpdated != null) {
            mHeaderTimeTV.setVisibility(View.VISIBLE);
            mHeaderTimeTV.setText(lastUpdated);
        } else {
            mHeaderTimeTV.setVisibility(View.GONE);
        }
    }

    /**
     * 获取当前header view 的topMargin
     * 
     * @description
     * @return hylin 2012-7-31上午11:22:50
     */
    private int getHeaderTopMargin() {
        LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
        return params.topMargin;
    }

    // /**
    // * lock
    // *
    // * @description hylin 2012-7-27下午6:52:25
    // */
    // private void lock() {
    // mLock = true;
    // }
    //
    // /**
    // * unlock
    // *
    // * @description hylin 2012-7-27下午6:53:18
    // */
    // private void unlock() {
    // mLock = false;
    // }

    /**
     * set headerRefreshListener
     * 
     * @description
     * @param headerRefreshListener
     *            hylin 2012-7-31上午11:43:58
     */
    public void setOnHeaderRefreshListener(
            OnHeaderRefreshListener headerRefreshListener) {
        mOnHeaderRefreshListener = headerRefreshListener;
    }

    public void setOnFooterRefreshListener(
            OnFooterRefreshListener footerRefreshListener) {
        mOnFooterRefreshListener = footerRefreshListener;
    }

    /**
     * Interface definition for a callback to be invoked when list/grid footer
     * view should be refreshed.
     */
    public interface OnFooterRefreshListener {
        public void onFooterRefresh(PullToRefreshView view);
    }

    /**
     * Interface definition for a callback to be invoked when list/grid header
     * view should be refreshed.
     */
    public interface OnHeaderRefreshListener {
        public void onHeaderRefresh(PullToRefreshView view);
    }
}

主要逻辑代码
private PullToRefreshView prv_refresh;
prv_refresh = (PullToRefreshView) findViewById(R.id.lay_refresh);
       
        prv_refresh.setOnFooterRefreshListener(new OnFooterRefreshListener() {
            @Override
            public void onFooterRefresh(PullToRefreshView view) {
                // TODO Auto-generated method stub
                OnLoad();

            }
        });
        prv_refresh.setOnHeaderRefreshListener(new OnHeaderRefreshListener() {
            @Override
            public void onHeaderRefresh(PullToRefreshView view) {
                // TODO Auto-generated method stub
                prv_refresh.onHeaderRefreshComplete();

            }
        });

/**
 * 自定义的布局,用来管理三个子控件,其中一个是下拉头,一个是包含内容的pullableView(可以是实现Pullable接口的的任何View),
 * 还有一个上拉头,更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38868463
 *
 * @author 陈靖
 */
public class PullToRefreshLayout extends RelativeLayout
{
public static final String TAG = "PullToRefreshLayout";
// 初始状态
public static final int INIT = 0;
// 释放刷新
public static final int RELEASE_TO_REFRESH = 1;
// 正在刷新
public static final int REFRESHING = 2;
// 释放加载
public static final int RELEASE_TO_LOAD = 3;
// 正在加载
public static final int LOADING = 4;
// 操作完毕
public static final int DONE = 5;
// 当前状态
private int state = INIT;
// 刷新回调接口
private OnRefreshListener mListener;
// 刷新成功
public static final int SUCCEED = 0;
// 刷新失败
public static final int FAIL = 1;
// 按下Y坐标,上一个事件点Y坐标
private float downY, lastY;


// 下拉的距离。注意:pullDownY和pullUpY不可能同时不为0
public float pullDownY = 0;
// 上拉的距离
private float pullUpY = 0;


// 释放刷新的距离
private float refreshDist = 200;
// 释放加载的距离
private float loadmoreDist = 200;


private MyTimer timer;
// 回滚速度
public float MOVE_SPEED = 8;
// 第一次执行布局
private boolean isLayout = false;
// 在刷新过程中滑动操作
private boolean isTouch = false;
// 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化
private float radio = 2;


// 下拉箭头的转180°动画
private RotateAnimation rotateAnimation;
// 均匀旋转动画
private RotateAnimation refreshingAnimation;


// 下拉头
private View refreshView;
// 下拉的箭头
private View pullView;
// 正在刷新的图标
private View refreshingView;
// 刷新结果图标
private View refreshStateImageView;
// 刷新结果:成功或失败
private TextView refreshStateTextView;


// 上拉头
private View loadmoreView;
// 上拉的箭头
private View pullUpView;
// 正在加载的图标
private View loadingView;
// 加载结果图标
private View loadStateImageView;
// 加载结果:成功或失败
private TextView loadStateTextView;


// 实现了Pullable接口的View
private View pullableView;
// 过滤多点触碰
private int mEvents;
// 这两个变量用来控制pull的方向,如果不加控制,当情况满足可上拉又可下拉时没法下拉
private boolean canPullDown = true;
private boolean canPullUp = true;


private Context mContext;


private boolean flagEvent = true;


/**
* 执行自动回滚的handler
*/
Handler updateHandler = new Handler()
{


@Override
public void handleMessage(Message msg)
{
// 回弹速度随下拉距离moveDeltaY增大而增大
MOVE_SPEED = (float) (8 + 5 * Math.tan(Math.PI / 2/ getMeasuredHeight() * (pullDownY + Math.abs(pullUpY))));
if (!isTouch)
{
// 正在刷新,且没有往上推的话则悬停,显示"正在刷新..."
if (state == REFRESHING && pullDownY <= refreshDist)
{
pullDownY = refreshDist;
timer.cancel();
} else if (state == LOADING && -pullUpY <= loadmoreDist)
{
pullUpY = -loadmoreDist;
timer.cancel();
}


}
if (pullDownY > 0)
pullDownY -= MOVE_SPEED;
else if (pullUpY < 0)
pullUpY += MOVE_SPEED;
if (pullDownY < 0)
{
// 已完成回弹
pullDownY = 0;
pullView.clearAnimation();
// 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态
if (state != REFRESHING && state != LOADING)
changeState(INIT);
timer.cancel();
requestLayout();
}
if (pullUpY > 0)
{
// 已完成回弹
pullUpY = 0;
pullUpView.clearAnimation();
// 隐藏上拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态
if (state != REFRESHING && state != LOADING)
changeState(INIT);
timer.cancel();
requestLayout();
}
Log.d("handle", "handle");
// 刷新布局,会自动调用onLayout
requestLayout();
// 没有拖拉或者回弹完成
if (pullDownY + Math.abs(pullUpY) == 0)
timer.cancel();
}


};


public void setOnRefreshListener(OnRefreshListener listener)
{
mListener = listener;
}


public PullToRefreshLayout(Context context)
{
super(context);
initView(context);
}


public PullToRefreshLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
initView(context);
}


public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initView(context);
}


private void initView(Context context)
{
mContext = context;
timer = new MyTimer(updateHandler);
rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(context, R.anim.reverse_anim);
refreshingAnimation = (RotateAnimation) AnimationUtils.loadAnimation(context, R.anim.rotating);
// 添加匀速转动动画
LinearInterpolator lir = new LinearInterpolator();
rotateAnimation.setInterpolator(lir);
refreshingAnimation.setInterpolator(lir);
}


private void hide()
{
timer.schedule(5);
}


/**
* 完成刷新操作,显示刷新结果。注意:刷新完成后一定要调用这个方法
*/
/**
* @param refreshResult PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失败
*/
public void refreshFinish(int refreshResult)
{
refreshingView.clearAnimation();
refreshingView.setVisibility(View.GONE);
switch (refreshResult)
{
case SUCCEED:
// 刷新成功
refreshStateImageView.setVisibility(View.VISIBLE);
refreshStateTextView.setText(R.string.refresh_succeed);
refreshStateImageView.setBackgroundResource(R.mipmap.refresh_succeed);
break;
case FAIL:
default:
// 刷新失败
refreshStateImageView.setVisibility(View.VISIBLE);
refreshStateTextView.setText(R.string.refresh_fail);
refreshStateImageView.setBackgroundResource(R.mipmap.refresh_failed);
break;
}
if (pullDownY > 0)
{
// 刷新结果停留1秒
new Handler()
{
@Override
public void handleMessage(Message msg)
{
changeState(DONE);
hide();
}
}.sendEmptyMessageDelayed(0, 1000);
} else
{
changeState(DONE);
hide();
}
}


/**
* 加载完毕,显示加载结果。注意:加载完成后一定要调用这个方法
* @param refreshResult PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失败
*/
public void loadmoreFinish(int refreshResult)
{
loadingView.clearAnimation();
loadingView.setVisibility(View.GONE);
switch (refreshResult)
{
case SUCCEED:
// 加载成功
loadStateImageView.setVisibility(View.VISIBLE);
loadStateTextView.setText(R.string.load_succeed);
loadStateImageView.setBackgroundResource(R.mipmap.load_succeed);
break;
case FAIL:
default:
// 加载失败
loadStateImageView.setVisibility(View.VISIBLE);
loadStateTextView.setText(R.string.load_fail);
loadStateImageView.setBackgroundResource(R.mipmap.load_failed);
break;
}
if (pullUpY < 0)
{
// 刷新结果停留1秒
new Handler()
{
@Override
public void handleMessage(Message msg)
{
changeState(DONE);
hide();
}
}.sendEmptyMessageDelayed(0, 1000);
} else
{
changeState(DONE);
hide();
}
}


private void changeState(int to)
{
state = to;
switch (state)
{
case INIT:
// 下拉布局初始状态
refreshStateImageView.setVisibility(View.GONE);
refreshStateTextView.setText(R.string.pull_to_refresh);
pullView.clearAnimation();
pullView.setVisibility(View.VISIBLE);
// 上拉布局初始状态
loadStateImageView.setVisibility(View.GONE);
loadStateTextView.setText(R.string.pullup_to_load);
pullUpView.clearAnimation();
pullUpView.setVisibility(View.VISIBLE);
break;
case RELEASE_TO_REFRESH:
// 释放刷新状态
refreshStateTextView.setText(R.string.release_to_refresh);
pullView.startAnimation(rotateAnimation);
break;
case REFRESHING:
// 正在刷新状态
pullView.clearAnimation();
refreshingView.setVisibility(View.VISIBLE);
pullView.setVisibility(View.INVISIBLE);
refreshingView.startAnimation(refreshingAnimation);
refreshStateTextView.setText(R.string.refreshing);
break;
case RELEASE_TO_LOAD:
// 释放加载状态
loadStateTextView.setText(R.string.release_to_load);
pullUpView.startAnimation(rotateAnimation);
break;
case LOADING:
// 正在加载状态
pullUpView.clearAnimation();
loadingView.setVisibility(View.VISIBLE);
pullUpView.setVisibility(View.INVISIBLE);
loadingView.startAnimation(refreshingAnimation);
loadStateTextView.setText(R.string.loading);
break;
case DONE:
// 刷新或加载完毕,啥都不做
break;
}
}


/**
* 不限制上拉或下拉
*/
private void releasePull()
{
canPullDown = true;
canPullUp = true;
}


public void banPull (boolean flag) {
flagEvent = flag;
}




/*
* (非 Javadoc)由父控件决定是否分发事件,防止事件冲突
*
* @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
switch (ev.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
lastY = downY;
timer.cancel();
mEvents = flagEvent ? 0 : -1;
releasePull();
Log.d(TAG, "dispatchTouchEvent: 1111111111111111");
break;
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
// 过滤多点触碰
mEvents = -1;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent: mEvents:" + mEvents);
if (mEvents == 0) {
Log.d(TAG, "dispatchTouchEvent: eeeeeeeeeeeeee");
if (pullDownY > 0 || (((Pullable) pullableView).canPullDown() && canPullDown && state != LOADING))
{
// 可以下拉,正在加载时不能下拉
// 对实际滑动距离做缩小,造成用力拉的感觉
pullDownY = pullDownY + (ev.getY() - lastY) / radio;
if (pullDownY < 0)
{
pullDownY = 0;
canPullDown = false;
canPullUp = true;
}
if (pullDownY > getMeasuredHeight())
pullDownY = getMeasuredHeight();
if (state == REFRESHING)
{
// 正在刷新的时候触摸移动
isTouch = true;
}
} else if (pullUpY < 0 || (((Pullable) pullableView).canPullUp() && canPullUp && state != REFRESHING)) {
// 可以上拉,正在刷新时不能上拉
pullUpY = pullUpY + (ev.getY() - lastY) / radio;
if (pullUpY > 0)
{
pullUpY = 0;
canPullDown = true;
canPullUp = false;
}
if (pullUpY < -getMeasuredHeight())
pullUpY = -getMeasuredHeight();
if (state == LOADING)
{
// 正在加载的时候触摸移动
isTouch = true;
}
} else
releasePull();
} else
mEvents = flagEvent ? 0 : -1;
lastY = ev.getY();
// 根据下拉距离改变比例
radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight()
* (pullDownY + Math.abs(pullUpY))));
if (pullDownY > 0 || pullUpY < 0)
requestLayout();
if (pullDownY > 0)
{
if (pullDownY <= refreshDist
&& (state == RELEASE_TO_REFRESH || state == DONE))
{
// 如果下拉距离没达到刷新的距离且当前状态是释放刷新,改变状态为下拉刷新
changeState(INIT);
}
if (pullDownY >= refreshDist && state == INIT)
{
// 如果下拉距离达到刷新的距离且当前状态是初始状态刷新,改变状态为释放刷新
changeState(RELEASE_TO_REFRESH);
}
} else if (pullUpY < 0)
{
// 下面是判断上拉加载的,同上,注意pullUpY是负值
if (-pullUpY <= loadmoreDist
&& (state == RELEASE_TO_LOAD || state == DONE))
{
changeState(INIT);
}
// 上拉操作
if (-pullUpY >= loadmoreDist && state == INIT)
{
changeState(RELEASE_TO_LOAD);
}


}
// 因为刷新和加载操作不能同时进行,所以pullDownY和pullUpY不会同时不为0,因此这里用(pullDownY +
// Math.abs(pullUpY))就可以不对当前状态作区分了
if ((pullDownY + Math.abs(pullUpY)) > 8)
{
// 防止下拉过程中误触发长按事件和点击事件
ev.setAction(MotionEvent.ACTION_CANCEL);
}
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent: 22222222222222222");
if (pullDownY > refreshDist || -pullUpY > loadmoreDist)
// 正在刷新时往下拉(正在加载时往上拉),释放后下拉头(上拉头)不隐藏
{
isTouch = false;
}
if (state == RELEASE_TO_REFRESH)
{
changeState(REFRESHING);
// 刷新操作
if (mListener != null)
mListener.onRefresh(this);
} else if (state == RELEASE_TO_LOAD)
{
changeState(LOADING);
// 加载操作
if (mListener != null)
mListener.onLoadMore(this);
}
hide();
default:
break;
}
// 事件分发交给父类
super.dispatchTouchEvent(ev);
return true;
}


/**
* @author chenjing 自动模拟手指滑动的task
*
*/
private class AutoRefreshAndLoadTask extends
AsyncTask<Integer, Float, String>
{


@Override
protected String doInBackground(Integer... params)
{
while (pullDownY < 4 / 3 * refreshDist)
{
pullDownY += MOVE_SPEED;
publishProgress(pullDownY);
try
{
Thread.sleep(params[0]);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
return null;
}


@Override
protected void onPostExecute(String result)
{
changeState(REFRESHING);
// 刷新操作
if (mListener != null)
mListener.onRefresh(PullToRefreshLayout.this);
hide();
}


@Override
protected void onProgressUpdate(Float... values)
{
if (pullDownY > refreshDist)
changeState(RELEASE_TO_REFRESH);
requestLayout();
}


}


/**
* 自动刷新
*/
public void autoRefresh()
{
AutoRefreshAndLoadTask task = new AutoRefreshAndLoadTask();
task.execute(20);
}


/**
* 自动加载
*/
public void autoLoad()
{
pullUpY = -loadmoreDist;
requestLayout();
changeState(LOADING);
// 加载操作
if (mListener != null)
mListener.onLoadMore(this);
}


private void initView()
{
// 初始化下拉布局
pullView = refreshView.findViewById(R.id.pull_icon);
refreshStateTextView = (TextView) refreshView.findViewById(R.id.state_tv);
refreshingView = refreshView.findViewById(R.id.refreshing_icon);
refreshStateImageView = refreshView.findViewById(R.id.state_iv);
// 初始化上拉布局
pullUpView = loadmoreView.findViewById(R.id.pullup_icon);
loadStateTextView = (TextView) loadmoreView.findViewById(R.id.loadstate_tv);
loadingView = loadmoreView.findViewById(R.id.loading_icon);
loadStateImageView = loadmoreView.findViewById(R.id.loadstate_iv);
}


@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
Log.d("Test", "Test");
if (!isLayout)
{
// 这里是第一次进来的时候做一些初始化
refreshView = getChildAt(0);
pullableView = getChildAt(1);
loadmoreView = getChildAt(2);
isLayout = true;
initView();
refreshDist = ((ViewGroup) refreshView).getChildAt(0).getMeasuredHeight();
loadmoreDist = ((ViewGroup) loadmoreView).getChildAt(0).getMeasuredHeight();
}
// 改变子控件的布局,这里直接用(pullDownY + pullUpY)作为偏移量,这样就可以不对当前状态作区分
refreshView.layout(0,(int) (pullDownY + pullUpY) - refreshView.getMeasuredHeight(),refreshView.getMeasuredWidth(), (int) (pullDownY + pullUpY));
pullableView.layout(0, (int) (pullDownY + pullUpY),pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight());
loadmoreView.layout(0, (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight(), loadmoreView.getMeasuredWidth(), (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight() + loadmoreView.getMeasuredHeight());
}


class MyTimer
{
private Handler handler;
private Timer timer;
private MyTask mTask;


public MyTimer(Handler handler)
{
this.handler = handler;
timer = new Timer();
}


public void schedule(long period)
{
if (mTask != null)
{
mTask.cancel();
mTask = null;
}
mTask = new MyTask(handler);
timer.schedule(mTask, 0, period);
}


public void cancel()
{
if (mTask != null)
{
mTask.cancel();
mTask = null;
}
}


class MyTask extends TimerTask
{
private Handler handler;


public MyTask(Handler handler)
{
this.handler = handler;
}


@Override
public void run()
{
handler.obtainMessage().sendToTarget();
}


}
}


/**
* 刷新加载回调接口
*
* @author chenjing
*
*/
public interface OnRefreshListener
{
/**
* 刷新操作
*/
void onRefresh(PullToRefreshLayout pullToRefreshLayout);


/**
* 加载操作
*/
void onLoadMore(PullToRefreshLayout pullToRefreshLayout);
}


}


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pull_to_refresh_header"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:gravity="center" >


    <FrameLayout
        android:id="@+id/pull_to_refresh_progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" >


        <ProgressBar
            android:id="@+id/pull_to_refresh_progress"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminate="true"
            android:visibility="gone" />


        <ImageView
            android:id="@+id/pull_to_refresh_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:contentDescription="@layout/refresh_header"
            android:gravity="center"
            android:src="@drawable/ic_pulltorefresh_arrow"
            android:visibility="visible" />
    </FrameLayout>
    <!-- 提示、最近更新 -->


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/pull_to_refresh_progress_bar"
        android:gravity="center"
        android:orientation="vertical" >


        <!-- 提示 -->


        <TextView
            android:id="@+id/pull_to_refresh_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@string/pull_to_refresh_pull_label"
            android:textSize="@dimen/dim20"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="@color/black" />


        <!-- 最近更新 -->


        <TextView
            android:id="@+id/head_lastUpdatedTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:singleLine="true"
            android:textSize="@dimen/dim18" />
    </LinearLayout>


</RelativeLayout>


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pull_to_refresh_header"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:gravity="center" >


    <FrameLayout
        android:id="@+id/pull_to_refresh_progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" >


        <ProgressBar
            android:id="@+id/pull_to_refresh_progress"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminate="true"
            android:visibility="gone" />


        <ImageView
            android:id="@+id/pull_to_refresh_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:contentDescription="@layout/refresh_header"
            android:gravity="center"
            android:src="@drawable/ic_pulltorefresh_arrow"
            android:visibility="visible" />
    </FrameLayout>
    <!-- 提示、最近更新 -->



头部布局
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/pull_to_refresh_progress_bar"
        android:gravity="center"
        android:orientation="vertical" >


        <!-- 提示 -->


        <TextView
            android:id="@+id/pull_to_refresh_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@string/pull_to_refresh_pull_label"
            android:textSize="@dimen/dim20"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="@color/black" />


        <!-- 最近更新 -->


        <TextView
            android:id="@+id/head_lastUpdatedTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:singleLine="true"
            android:textSize="@dimen/dim18" />
    </LinearLayout>


</RelativeLayout>


底部布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pull_to_refresh_header"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:gravity="center" >


    <FrameLayout
        android:id="@+id/pull_to_load_progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" >


        <ProgressBar
            android:id="@+id/pull_to_load_progress"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminate="true"
            android:visibility="gone" />


        <ImageView
            android:id="@+id/pull_to_load_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:contentDescription="@layout/refresh_footer"
            android:gravity="center"
            android:src="@drawable/ic_pulltorefresh_arrow_up" />
    </FrameLayout>


    <TextView
        android:id="@+id/pull_to_load_text"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/pull_to_load_progress_bar"
        android:gravity="center"
        android:textSize="12sp"
        android:text="@string/pull_to_refresh_footer_pull_label"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textColor="@color/black" />


</RelativeLayout>


    <string name="pull_to_refresh_pull_label">下拉刷新</string>
    <string name="pull_to_refresh_release_label">释放刷新</string>
    <string name="pull_to_refresh_refreshing_label">刷新\u2026</string>
    <string name="pull_to_refresh_footer_refreshing_label">加载中\u2026</string>
    <string name="pull_to_refresh_footer_release_label">释放加载更多</string>
    <string name="pull_to_refresh_footer_pull_label">上拉加载更多</string>



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值