Andriod自定义View之(下拉刷新)

学习自定义View已经有一段时间了,现在都有个毛病了,看了其它应用一些效果,然后就在那研究半天,这个东西我能实现吗?我能! 哈哈哈,以前我都是看到自定义控件望尘莫及的,现在都能有点自信的说我能了,不错!只要坚持并不断总结,一定会有收获的,废话不说了,今天也算是前面学习自定义View的一个总结,一步一步的实现一个下拉刷新View。
滑动到顶部的时候弹出下拉刷新,滑动到底部自动弹出进度条加载更多,也可以手动上拉加载更多
当然如果只用ListView的话也可以直接拿代码用:https://github.com/913453448/PullToRefreshView
先看下效果:
这里写图片描述
实现思路:
1、还是自定一个View继承LineaLayout,orientation为Vertical
2、定义一个HeaderView放在LinearLayout的顶部,ListView放中间,自定义一个FooterView放在最底下。
3、通过改变HeaderVeiw的topMargin来实现刷新控件的显示与隐藏。
可能我这样说有一点抽象了,下面我通过改变布局文件来演示下显示的过程:
首先我们定义一个Layout文件
test.layout:
1、把HeaderView的topMargin设置0,这个时候我们的HeaderView是显示出来的,也就是下拉刷新中。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=match_parent
    android:layout_height=match_parent
    android:orientation=vertical>
    <TextView
        android:layout_marginTop=0dp
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=wrap_content
        android:layout_height=50dp
        android:text=HEADER VIEW
        />
    <TextView
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=match_parent
        android:layout_height=match_parent
        android:text=CONTENT VIEW
        />
    <TextView
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=match_parent
        android:layout_height=50dp
        android:text=FOOTER VIEW
        />
</LinearLayout>

效果图:
HeaderView的topMargin设置0

2、我们把HeaderView的topMargin设置成-50dp,这时HeaderView隐藏了,也就是正常状态。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=match_parent
    android:layout_height=match_parent
    android:orientation=vertical>
    <TextView
        android:layout_marginTop=-50dp
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=wrap_content
        android:layout_height=50dp
        android:text=HEADER VIEW
        />
    <TextView
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=match_parent
        android:layout_height=match_parent
        android:text=CONTENT VIEW
        />
    <TextView
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=match_parent
        android:layout_height=50dp
        android:text=FOOTER VIEW
        />
</LinearLayout>

效果图:
这里写图片描述

3、我们再把HeaderView的topMargin改为-100dp,这时footerView可见了,也就是上拉加载中。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=match_parent
    android:layout_height=match_parent
    android:orientation=vertical>
    <TextView
        android:layout_marginTop=-100dp
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=wrap_content
        android:layout_height=50dp
        android:text=HEADER VIEW
        />
    <TextView
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=match_parent
        android:layout_height=match_parent
        android:text=CONTENT VIEW
        />
    <TextView
        android:layout_gravity=center
        android:gravity=center
        android:layout_width=match_parent
        android:layout_height=50dp
        android:text=FOOTER VIEW
        />
</LinearLayout>

这里写图片描述

好啦! 演示完毕了,看完整个过程是不是有点思路了呢,其实看起来还挺容易的对吧,呵呵,不过实现起来还不是那么容易的咯,既然是学习嘛,下面我们来一步一步实现……

第一步:创建一个叫PullRefreshView的类继承LinearLayout,然后覆盖三个构造方法,不要再问为什么要覆盖三个构造方法了,因为父类实现了这几个构造方法,身为子类要显示的用super()来调用父类的构造方法,带一个参数的构造方法会在比如我们直接new一个组件的时候用到,带两个参数的,如果我们写在了布局文件中系统new的时候会调用,带三个参数的构造方法,当我们new一个View的时候需要指定默认style的时候调用,待会的demo也会用到带三个参数的构造方法。

public class PullRefreshView extends LinearLayout{
    /**上下文*/
    private Context mContext;
    public PullRefreshView(Context context) {
        this(context, null);
    }

    public PullRefreshView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullRefreshView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //设置LinearLayout默认方向为垂直方向
        this.setOrientation(LinearLayout.VERTICAL);
        this.mContext=context;
}

第二步:我们要往主布局LinearLayout中添加一个HeaderView了,在添加之前我们先定一个自己的PullHeaderView,画的有点丑
大概的样子:
这里写图片描述

照样是自定义View的步骤:
1、自定义一个View继承LinearLayout,覆盖三个构造方法,然后初始化View(也就是添加布局)

/**
 * Author:Yqy
 * Date:2016-08-23
 * Desc:下拉刷新HeaderView
 * Company:cisetech
 */
public class PullHeaderView extends LinearLayout {
    private Context mContext;
    /**
     * 主View
     */
    private LinearLayout headerView;
    /**
     * 箭头图标View
     */
    private ImageView arrowImageView;
    /**
     * 进度图标View
     */
    private ProgressBar headerProgressBar;
    /**
     * 箭头图标
     */
    private Bitmap arrowBitmap;
    /**
     * 文本提示的View
     */
    private TextView tipsTextView;
    /**
     * 提示刷新时间的View
     */
    private TextView headerTimeView;
    /**
     * 当前控件的状态
     */
    private int mState = -1;
    /**
     * 箭头向上时候的动画
     */
    private Animation mRotateUpAnim;
    /**
     * 箭头向下时候的动画
     */
    private Animation mRotateDownAnim;
    /**
     * 动画持续的时间
     */
    private final int ROTATE_ANIM_DURATION = 180;
    /**
     * 提示下拉刷新
     */
    public final static int STATE_NORMAL = 0;
    /**
     * 提示松开刷新
     */
    public final static int STATE_READY = 1;
    /**
     * 提示正在刷新
     */
    public final static int STATE_REFRESHING = 2;
    /**
     * 上一次刷新的时间
     */
    private String lastRefreshTime;
    /**
     * Header的高度
     */
    private int headerHeight;

    public PullHeaderView(Context context) {
        this(context, null);
    }

    public PullHeaderView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        initView();
    }

    /**
     * 初始化控件
     */
    private void initView() {
        /**
         * 创建一个整体的刷新栏布局,然后放在this中,
         * 也就是我们画图中的第二层,方向为水平
         * setOrientation(LinearLayout.HORIZONTAL);
         */
        headerView = new LinearLayout(mContext);
        headerView.setOrientation(LinearLayout.HORIZONTAL);
        headerView.setGravity(Gravity.CENTER);
        headerView.setPadding(0, Utils.dp2px(mContext, 10), 0, Utils.dp2px(mContext, 10));//设置padding
        /**
         * 创建一个FramLayout,
         * 因为进度条跟箭头是放在一起的
         */
        FrameLayout headImage = new FrameLayout(mContext);
        arrowBitmap = Utils.getBitmapFromSrc(mContext,R.mipmap.arrow);
        arrowImageView=new ImageView(mContext);
        arrowImageView.setImageBitmap(arrowBitmap);
        /**
         * 创建一个进度条,默认Style为
         * android.R.attr.progressBarStyleSmall
         * 默认是不显示的
         */
        headerProgressBar = new ProgressBar(mContext, null, android.R.attr.progressBarStyleSmall);
        headerProgressBar.setVisibility(View.GONE);
        /**
         * 然后把箭头跟进度条放入FramLayout中
         * 大小为40dp
         */
        LinearLayout.LayoutParams imgLp = new LinearLayout.LayoutParams(-2, -2);
        imgLp.gravity = Gravity.CENTER;
        imgLp.width = Utils.dp2px(mContext, 40);
        imgLp.height = Utils.dp2px(mContext, 40);
        headImage.addView(arrowImageView, imgLp);
        headImage.addView(headerProgressBar, imgLp);
        /**
         * 添加提示文字跟刷新时间
         * 放入headTextLayout(LinearLayout中)
         * 方向为LinearLayout.VERTICAL
         */
        LinearLayout headTextLayout = new LinearLayout(mContext);
        headTextLayout.setOrientation(LinearLayout.VERTICAL);
        headTextLayout.setGravity(Gravity.CENTER);
        tipsTextView = new TextView(mContext);
        tipsTextView.setTextColor(Color.DKGRAY);
        tipsTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14.5f);
        headerTimeView = new TextView(mContext);
        headerTimeView.setTextColor(Color.DKGRAY);
        headerTimeView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14.5f);
        LinearLayout.LayoutParams textLp = new LinearLayout.LayoutParams(-2, -2);
        headTextLayout.addView(tipsTextView, textLp);
        headTextLayout.addView(headerTimeView, textLp);

        /**
         * 创建一个叫headerLayout(LinearLayout)
         * 把headImage跟headTextLayout包裹起来
         */
        LinearLayout.LayoutParams headLp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        headLp.gravity = Gravity.CENTER;
        headLp.rightMargin = Utils.dp2px(mContext, 10);
        LinearLayout headerLayout = new LinearLayout(mContext);
        headerLayout.setOrientation(LinearLayout.HORIZONTAL);
        headerLayout.setGravity(Gravity.CENTER);
        headerLayout.addView(headImage, headLp);
        headerLayout.addView(headTextLayout, headLp);
        /**
         * 把创建一个叫headerLayout放入到headerView中
         */
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
        lp.gravity = Gravity.BOTTOM;
        headerView.addView(headerLayout, lp);
        /**
         * 最后把headerView主布局添加到PullHeaderView中
         */
        this.addView(headerView, lp);
        //获取控件的高度,获取之前先measure一下,不然拿不到宽高
        Utils.measureView(this);
        headerHeight = this.getMeasuredHeight();
        /**
         * 初始化箭头朝上的动画
         */
        mRotateUpAnim = new RotateAnimation(0.0f, -180f, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
        mRotateUpAnim.setFillAfter(true);
        /**
         * 初始化箭头朝下的动画
         */
        mRotateDownAnim= new RotateAnimation(-180f,0.0f, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
        mRotateDownAnim.setFillAfter(true);

    }
  }

第二步:定义setState方法,因为下拉刷新有几个状态,我们需要根据PullRefreshView传入的状态进行HeaderView的显示状态,状态跟HeaderView显示的状态为:
/**
* 提示下拉刷新
*/
public final static int STATE_NORMAL = 0;
/**
* 提示松开刷新
*/
public final static int STATE_READY = 1;
/**
* 提示正在刷新
*/
public final static int STATE_REFRESHING = 2;

/**
     * 设置当前HeaderView的状态
     * @param state
     */
    public void setState(int state){
        //当前状态跟设置的状态一致的时候
        if(state==mState){
            return ;
        }
        //为刷新中的时候,箭头隐藏,进度条显示
        if(state==STATE_REFRESHING){
            arrowImageView.clearAnimation();
            arrowImageView.setVisibility(View.GONE);
            headerProgressBar.setVisibility(View.VISIBLE);
        }else{
            arrowImageView.setVisibility(View.VISIBLE);
            headerProgressBar.setVisibility(View.GONE);
        }
        switch (state){
            //当为下拉刷新的时候,箭头朝下
            case STATE_NORMAL:
                if(mState==STATE_READY){//如果前面状态是箭头朝上
                    arrowImageView.startAnimation(mRotateDownAnim);
                }
                if(mState==STATE_REFRESHING){
                    arrowImageView.clearAnimation();
                }
                tipsTextView.setText("下拉刷新");
                if(TextUtils.isEmpty(lastRefreshTime)){
                    lastRefreshTime=Utils.getCurrentDate();
                    headerTimeView.setText("刷新时间: "+lastRefreshTime);
                }else{
                    headerTimeView.setText("上次刷新时间:" +lastRefreshTime);
                }
                break;
            case STATE_READY:
                if (mState != STATE_READY) {
                    arrowImageView.clearAnimation();
                    arrowImageView.startAnimation(mRotateUpAnim);
                    tipsTextView.setText("该放手啦!");
                    headerTimeView.setText("上次刷新时间:" + lastRefreshTime);
                }
                break;
            case STATE_REFRESHING:
                lastRefreshTime=Utils.getCurrentDate();
                tipsTextView.setText("正在刷新...");
                headerTimeView.setText("本次刷新时间:" + lastRefreshTime);
                break;
            default:
        }
        mState=state;
    }

整个HeaderView的内容还是比较多的,但是只要思路不要乱就可以了,代码多一点也无所谓啦,可以写完后再去优化一下代码的,自我感觉有些地方写的还是有点臃肿了。

第三步:照样来定义一个FooterView,因为FooterView的布局很简单,就一个ProgressBar,我就不一一描述了,直接贴代码了

/**
 * Author:Yqy
 * Date:2016-08-23
 * Desc:上拉加载footView
 * Company:cisetech
 */
public class PullFootView extends LinearLayout {
    private Context mContext;
    private LinearLayout footView;
    private ProgressBar mProgressBar;
    private int footHeight;
    public PullFootView(Context context) {
        this(context,null);
    }

    public PullFootView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullFootView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    /**
     * 初始化
     * @param context
     */
    private void initView(Context context) {
        this.mContext=context;
        this.setOrientation(LinearLayout.HORIZONTAL);
        footView=new LinearLayout(mContext);
        mProgressBar=new ProgressBar(mContext,null,android.R.attr.progressBarStyleSmall);
        LinearLayout.LayoutParams progressLp=new LinearLayout.LayoutParams(-2,-2);
        progressLp.gravity= Gravity.CENTER;
        progressLp.width= Utils.dp2px(mContext,40);
        progressLp.height=Utils.dp2px(mContext,40);
        footView.addView(mProgressBar,progressLp);
        LinearLayout.LayoutParams footLp=new LinearLayout.LayoutParams(-2,-2);
        footLp.gravity=Gravity.CENTER;
        footView.setGravity(Gravity.CENTER);
        this.addView(footView,footLp);
        Utils.measureView(this);
        footHeight=this.getMeasuredHeight();
    }

    /**
     * 获取footview的高度
     * @return
     */
    public int getFootViewHeight() {
        return footHeight;
    }
    private int mSate;
    public void setState(int state) {
        if(mSate==state){
            return;
        }
        if(state==PullHeaderView.STATE_REFRESHING||state==PullHeaderView.STATE_READY){
            this.setVisibility(View.VISIBLE);
        }else{
            this.setVisibility(View.GONE);
        }
        mSate=state;
    }
}

第四步:写完了HeaderView跟FooterView,我们要添加到PullRefreshView中去了
1、先添加HeaderView,因为我们的HeaderView是放在最上面的,接下来添加的是我们的ContentView(当然,布局中已经添加进来了),最后当加载完布局文件后我们添加FooterView,跟我一开始演示的那个一样的顺序了。
我们在构造方法中调用addHeaderView方法:

public PullRefreshView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setOrientation(LinearLayout.VERTICAL);
        this.mContext=context;
        /**
         * 添加头部刷新view
         */
        addHeaderView();
    }
 /**
     * 添加头部刷新View
     */
    private void addHeaderView() {
        mHeaderView=new PullHeaderView(mContext);
        mHeaderViewHeight=mHeaderView.getHeaderHeight();
        mHeaderView.setGravity(Gravity.BOTTOM);

        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mHeaderViewHeight);
        // 设置topMargin的值为负的header View高度,隐藏在最上方
        params.topMargin = -mHeaderViewHeight;
        addView(mHeaderView, params);
    }

2、当布局文件加载完毕的时候,这个时候PullRefreshView中已经有两个布局了,依次是HeaderView、ContentView、然后该添加我们的FooterView了。

 /**
     * 当加载完布局后,获取ListView
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //添加footView,
        addFootView();
        //获取AdapterView
        initContentAdapterView();
    }

    /**
     * 添加FootView
     */
    private void addFootView() {
        mFootView=new PullFootView(mContext);
        mFootViewHeight=mFootView.getFootViewHeight();
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mFootViewHeight);
        mFootView.setGravity(Gravity.CENTER_HORIZONTAL);
        addView(mFootView, params);
    }

前面都是比较简单的步骤,现在到了下拉刷新组件关键的几步了


第五步:重写onIntercepterTouchEvent方法,拦截事件,这里先拿ListView来说了。
当ListView滑动到顶部的时候:
也就是ListView的getFirstVisiblePosition()为0,然后第一个child的top为0
当ListView滑动到底部的时候:
最后一个child的bottom=PullRefreshView的高度并且getLastVisiblePosition()==mAdapterView.getCount()-1
当前最后一个child为所有child中最后一个child。

当满足滑动到底部或者顶部其中一个条件的时候,这个时候需要拦截事件交给父控件处理了,并且判断是上拉还是下拉,然后在父控件的onTouchEvent方法中去处理滑动了。

 /**xx
     * 处理事件拦截
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        int action=ev.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN://记录按下时候的位置
                mLastMotionX=x;
                mLastMotionY=y;
                break;
            case MotionEvent.ACTION_MOVE:
                //duraY>0是向下滑动,<0是向上滑动
                int duraX=x-mLastMotionX;
                int duraY=y-mLastMotionY;
                //解决错误滑动操作
                if(Math.abs(duraX)<Math.abs(duraY)&&Math.abs(duraY)>10){
                    if(isRefreshScroll(duraY)){//判断是否滑动到底部或者顶部
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                isAllowFootRefreshing=true;
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 判断是否需要拦截事件,也就是是否滑动到底部或者顶部
     * @param duraY
     * @return boolean
     */
    private boolean isRefreshScroll(int duraY) {
        //如果正在加载或者正在刷新的时候,直接不拦截事件
        if(mPullRefreshing||mPullLoading){
            return false;
        }
        if(mAdapterView!=null){
            if(duraY>0){//向下滑动
                if(!mEnablePullRefresh){
                    return false;//如果禁止了下拉刷新那么不拦截
                }
                View child=mAdapterView.getChildAt(0);
                if(child==null){
                    return false;
                }
                int top=child.getTop();
                int padding=mAdapterView.getPaddingTop();
                //判断是否滑动到了顶部
                if(mAdapterView.getFirstVisiblePosition()==0&&Math.abs(top - padding)<=11){
                    mPullState=PULL_DOWN_STATE;
                    return true;
                }
            }else if(duraY<0){//上拉的时候
                isAllowFootRefreshing=false;
                View child = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);
                if(child!=null&&child.getBottom()<=getMeasuredHeight()&&mAdapterView.getLastVisiblePosition()==mAdapterView.getCount()-1){
                    mPullState=PULL_UP_STATE;
                    return true;
                }
            }
        }
        return false;
    }

第六步:当父类拦截到事件自己处理的时候,这个时候说明需要根据滑动的状态和距离对HeaderView和FooterView进行操作了。
我们onTouchEvent方法中不处理ACTION_DOWN的操作,
因为前面onInterceopterTouchEvent方法中记录了是下拉还是上拉状态了,所以当ACTION为ACTION_MOVE移动的时候,根据记录的状态处理上拉和下拉

 case MotionEvent.ACTION_MOVE:
                int duraY=y-mLastMotionY;
                if(mPullState==PULL_DOWN_STATE){
                    //执行下拉操作
                    headerPrepareToRefresh(duraY);
                }else if(mPullState==PULL_UP_STATE){
                    //执行上拉
                    footPrepareToRefresh(duraY);
                }
                mLastMotionY=y;
                break;
 /**
     * 执行下拉操作
     * @param duraY
     */
    private void headerPrepareToRefresh(int duraY) {
        if(mPullRefreshing||mPullLoading){
            return ;
        }
        /**
         * 根据滑动的距离计算出HeaderView的topMargin
         */
        int newTopMargin=updateHeaderViewTopMargin(duraY);
        /**
         * 当HeaderView新的topMargin>=0的时候,也就是我们上面演示的marginTop=-50dp
         * 的时候,也就是HeaderView全部显示出来
         * HeaderView此时显示“松手刷新”
         * 
         * 反之如果还有一截在没有显示出来就显示
         * “下拉刷新”状态
         */
        if(newTopMargin>=0&&mHeaderView.getState()!=PullHeaderView.STATE_REFRESHING){
            mHeaderView.setState(PullHeaderView.STATE_READY);
        }else if(newTopMargin<0&&newTopMargin>-mHeaderViewHeight){
            mHeaderView.setState(PullHeaderView.STATE_NORMAL);
        }
    }

当手指抬起或者cancel的时候判断是否需要下拉刷新了:

 case MotionEvent.ACTION_UP:
                int topMargin=((LayoutParams)mHeaderView.getLayoutParams()).topMargin;
                if(mPullState==PULL_DOWN_STATE){
                    if(topMargin>0){
                        //提示正在刷新中,
                        headerRefresh();
                    }else{
                        //从新隐藏headerView
                        setHeaderTopMargin(-mHeaderViewHeight);
                    }
                }else if(mPullState==PULL_UP_STATE){
                    //上拉加载中
                    isAllowFootRefreshing=true;
                   if(topMargin<=-(mHeaderViewHeight+mFootViewHeight)){
                        footRefreshing();
                    }else{
                        //从新隐藏footView
                        setHeaderTopMargin(-mHeaderViewHeight);
                    }
                }
                break;
/**
     * 下拉刷新中
     */
    private void headerRefresh() {
        mPullRefreshing=true;
        mHeaderView.setState(PullHeaderView.STATE_REFRESHING);
        setHeaderTopMargin(0);
        if(onHeaderRefreshListener!=null){
            onHeaderRefreshListener.onHeaderRefresh(this);
        }
    }

footerview的判断逻辑跟HeaderView也差不多,我就不多解释了,直接贴代码了
提示一下:footerView全部显示出来也就是当HeaderView的topMargin为-(footerView的高度+HeaderView的高度)

case MotionEvent.ACTION_MOVE:
                int duraY=y-mLastMotionY;
                if(mPullState==PULL_DOWN_STATE){
                    //执行下拉操作
                    headerPrepareToRefresh(duraY);
                }else if(mPullState==PULL_UP_STATE){
                    //执行上拉
                    footPrepareToRefresh(duraY);
                }
                mLastMotionY=y;
                break;
/**
     * 执行上拉加载操作
     * @param duraY
     */
    private void footPrepareToRefresh(int duraY) {
        if(mPullRefreshing||mPullLoading){
            return ;
        }
        int newTopMargin=updateHeaderViewTopMargin(duraY);
        mFootView.setState(PullHeaderView.STATE_READY);
    }

当手抬起或Cancle的时候,判断是否需要上拉加载

case MotionEvent.ACTION_UP:
                int topMargin=((LayoutParams)mHeaderView.getLayoutParams()).topMargin;
                if(mPullState==PULL_DOWN_STATE){
                    if(topMargin>0){
                        //提示正在刷新中,
                        headerRefresh();
                    }else{
                        //从新隐藏headerView
                        setHeaderTopMargin(-mHeaderViewHeight);
                    }
                }else if(mPullState==PULL_UP_STATE){
                    //上拉加载中
                    isAllowFootRefreshing=true;
                   if(topMargin<=-(mHeaderViewHeight+mFootViewHeight)){
                        footRefreshing();
                    }else{
                        //从新隐藏footView
                        setHeaderTopMargin(-mHeaderViewHeight);
                    }
                }
                break;
  /**
     * 底部加载中..
     */
    private void footRefreshing() {
        if(mPullLoading||mPullRefreshing){
            return;
        }
        mPullLoading=true;
        isAllowFootRefreshing=false;
        mFootView.setState(PullHeaderView.STATE_REFRESHING);
        setHeaderTopMargin(-(mFootViewHeight + mHeaderViewHeight));
        if(onfootRefreshListener!=null){
            onfootRefreshListener.onFootRefresh(this);
        }
    }

最后一步了:完成刷新时候操作,也就是给HeaderView的topMargin改为-HeaderView的高度,这样就只有一个ListView显示了,然后恢复所有的状态,定义下拉刷新跟上拉加载的监听器

/**
     * 刷新完成,隐藏刷新view
     */
    public void refreshComplete(){
        mPullLoading=false;//是否上拉加载中置为false
        mPullRefreshing=false;//是否下拉加载置为false
        /**
         * HeaderView的状态置为初始状态
         */
        mHeaderView.setState(PullHeaderView.STATE_NORMAL);
        /**
         * 隐藏HeaderView跟FooterView,
         * 不明白的可以看我一开始演示的那几个过程
         */
        setHeaderTopMargin(-mHeaderViewHeight);
        mFootView.setState(PullHeaderView.STATE_NORMAL);
    }
/**
     * 下拉刷新监听
     */
    public interface IOnHeaderRefreshListener{
        void onHeaderRefresh(PullRefreshView view);
    }

    /**
     * 加载更多监听接口
     */
    public interface IOnfootRefreshListener{
        void onFootRefresh(PullRefreshView view);
    }
    /**
     * 设置下拉刷新监听器
     * @param onHeaderRefreshListener
     */
    public void setHeaderRefreshListener(IOnHeaderRefreshListener onHeaderRefreshListener){
        this.onHeaderRefreshListener=onHeaderRefreshListener;
    }

    /**
     * 设置加载更多监听
     * @param onfootRefreshListener
     */
    public void setOnfootRefreshListener(IOnfootRefreshListener onfootRefreshListener) {
        this.onfootRefreshListener = onfootRefreshListener;
    }

实现ListView滑动到底部的时候自动上拉加载更多数据
思路:监听ListView的滑动过程,然后判断是否自由滑动到最底部了,自由滑动到最底部的时候就自动加载更多。

/**
     * 获取AdapterView
     */
    private void initContentAdapterView() {
        int count=getChildCount();
        if(count<2){
            throw new IllegalArgumentException("this layout must contain 2 child views,and AdapterView or ScrollView must in the second position!");
        }
        View view=null;
        for (int i = 0; i < count; i++) {
            view=getChildAt(i);
            if(view instanceof AdapterView<?>){
                mAdapterView= (AdapterView<?>) view;
                /**
                 * 只针对ListView做上拉加载操作
                 */
                ListView lv= (ListView) mAdapterView;
                /**
                 * 设置ListView的滚动监听
                 */
                lv.setOnScrollListener(this);
            }else if(view instanceof ScrollView){
                mScrollerView = (ScrollView) view;
            }
        }
        if (mAdapterView == null && mScrollerView == null) {
            throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!");
        }
    }

判断是否是自由滑动,是的话就判断是否到达了底部,到了就自动加载更多

/**
     * ListView滑动的状态,当为SCROLL_STATE_FLING的时候滑动到底部就自动加载更多
     */
    private int mScrollState;
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mScrollState=scrollState;
    }
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if(mAdapterView!=null&&mAdapterView.getCount()>0){
            View child = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);
            if(child!=null&&child.getBottom()<=getMeasuredHeight()&&mAdapterView.getLastVisiblePosition()==mAdapterView.getCount()-1){
                if(mScrollState== AbsListView.OnScrollListener.SCROLL_STATE_FLING){
                    footRefreshing();
                }
            }
        }
    }

好啦!!整个PullRefreshView就实现了,肯定还有不少bug,关键是知道原理,然后多巧几遍,我相信会有收获的,感兴趣的可以自己实现下PullRefreshScrollView或者各种View,只需要将下面方法写活就可以了。

/**
     * 获取AdapterView
     */
    private void initContentAdapterView() {
        int count=getChildCount();
        if(count<2){
            throw new IllegalArgumentException("this layout must contain 2 child views,and AdapterView or ScrollView must in the second position!");
        }
        View view=null;
        for (int i = 0; i < count; i++) {
            view=getChildAt(i);
            if(view instanceof AdapterView<?>){
                mAdapterView= (AdapterView<?>) view;
                /**
                 * 只针对ListView做上拉加载操作
                 */
                ListView lv= (ListView) mAdapterView;
                /**
                 * 设置ListView的滚动监听
                 */
                lv.setOnScrollListener(this);
            }else if(view instanceof ScrollView){
                mScrollerView = (ScrollView) view;
            }
        }
        if (mAdapterView == null && mScrollerView == null) {
            throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!");
        }
    }

好啦!!!结束啦,,3Q!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值