自定义控件——ListView下拉刷新和上拉加载

进入冰冷的季节了。好想在被窝敲代码。。。。

首先放一下效果

  1. 添加HeadView实现下拉刷新
    下拉刷新
  2. 添加FooterView实现上拉加载
    上拉加载
  3. 创建类继承ListView ,构造方法继承前两个
 /**
     * 重写创建时用的构造方法
     *
     * @param context
     */
    public PullPushListView(Context context) {
        super(context);
    }

    /**
     * 重写赋值参数时用的构造方法
     *
     * @param context
     * @param attrs
     */
    public PullPushListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //初始化头部信息
        initHeaderView();
        //初始化尾部信息
        initFooterView();
    }

4.下拉刷新的实现原理 通过padding属性,当属性为负数的时候就会隐藏如图:padding
5.上拉加载同样原理,不再演示–


首先我们分为两部分去学习

第一部分: 下拉部分–
首先我们要想加入一个headView 需要我们对这个要加入到顶部的条目进行布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="50dp"
        android:layout_height="50dp">

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:src="@drawable/arrow" />

        <ProgressBar
        android:id="@+id/iv_circle"
        style="@android:style/Widget.ProgressBar"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerInParent="true"
        android:indeterminateDrawable="@drawable/progress_medium_white"
        android:visibility="invisible" />
    </RelativeLayout>


    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_loadtext"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="下拉刷新"
            android:textColor="#ffff0000"
            android:textSize="18sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="2dp"
            android:layout_marginTop="5dp"
            android:gravity="center"
            android:text="上次刷新 2016-10-23 12:41:58" />
    </LinearLayout>

</LinearLayout>

1.1 初始化headView,将设定的头部信息隐藏,主要是对要inflate的头部布局进行测量

  /**
     * 初始化HeaderView
     */
    private void initHeaderView() {
        //为自定义ListView添加头部信息
        headerView = View.inflate(getContext(), R.layout.item_headview, null);
        tv_loadtext = (TextView) headerView.findViewById(R.id.tv_loadtext);
        iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
        pb_circle = (ProgressBar) headerView.findViewById(R.id.iv_circle);
        //将progressBar 设定为Gone
        pb_circle.setVisibility(View.GONE);
        //通知进行布局测量<<<<<<卧槽尼玛的,不能测量RelativeLayout布局开头>>>>>>>
        headerView.measure(0, 0);
        //获取测量高度,在之前需要测量一下,否则无法获 取正确数据
        meatureHeight = headerView.getMeasuredHeight();
        //通知进行布局测量
        headerView.measure(0, 0);
        Log.i(TAG, "PullPushListView: " + meatureHeight);
        //添加headerView到页面
        super.addHeaderView(headerView);
        //整体隐藏headerView
        inithideheaderView(-meatureHeight);
        headerView.setClickable(false);
        STATE_CURRENT = STATE_PULL;

    }

1.2 通过onTouchEvent(MotationEvent event);,对触摸事件处理实现动态拉出头部条目,以及涉及到状态的改变
在此之前,我们需要设定三个状态,用于记录当前是那种状态
1.2.1状态可以通过设定三个全局变量

 /**
     * 下拉状态
     */
    private static final int STATE_PULL = 0;
    /**
     * 松开刷新状态
     */
    private static final int STATE_PUSH = 1;
    /**
     * 正在刷新状态
     */
    private static final int STATE_REFRESH = 2;
    /**
     * 当前状态
     */
    private int STATE_CURRENT;

1.2.2 也可以通过获取TextView 中的条目内容获取,不过麻烦,这里舍弃

下面是触摸事件的实现

 /**
     * headerView 根据传入的数值动态改变HeaderView据上边界的高度
     */
    private void inithideheaderView(int top) {
        headerView.setPadding(0, top, 0, 0);
    }

    /**
     * 对ListView 添加触摸事件
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            //手指按下事件
            case MotionEvent.ACTION_DOWN:
                if (STATE_CURRENT == STATE_REFRESH) {//如果状态是正在刷新状态,就不执行其他操作
                    iv_arrow.setVisibility(GONE);
                    pb_circle.setVisibility(View.VISIBLE);
                    return super.onTouchEvent(event);
                }
                down_dot = event.getY();//如果不是正在刷新状态记录按下的点
                break;
            //手指移动事件
            case MotionEvent.ACTION_MOVE:
                if (STATE_CURRENT == STATE_REFRESH && getFirstVisiblePosition() == 0) {//如果状态是正在刷新状态,就不执行其他操作
                    iv_arrow.setVisibility(GONE);
                    pb_circle.setVisibility(View.VISIBLE);
                    return super.onTouchEvent(event);
                } else if ((STATE_CURRENT == STATE_PULL || STATE_CURRENT == STATE_PUSH) && getFirstVisiblePosition() == 0) {

                    //手指移动的点
                    float move_dot = event.getY();
                    //距离按下点的相对向下移动的距离
                    float movePullDistance = move_dot - down_dot;
                    //判断是否向下滑动,第一个条目是否是0,
                    if (movePullDistance > 0 && getFirstVisiblePosition() == 0 && movePullDistance < meatureHeight) {
                        inithideheaderView((int) movePullDistance - meatureHeight);
                        //向下滑动,动态改变HeardView距离上边界的距离,但是还没有到达修改箭头的边界
                        if (STATE_CURRENT == STATE_PUSH) {
                            tv_loadtext.setText("下拉刷新");
                            rotateArrow_changeText();
                            STATE_CURRENT = STATE_PULL;//修改状态,在这个范围内不再修改状态
                        }
                    } else if (movePullDistance == meatureHeight) {
                        STATE_CURRENT = STATE_CURRENT == STATE_PUSH ? STATE_PUSH : STATE_PULL;//判断状态
                    } else if (movePullDistance > meatureHeight && movePullDistance < 2 * meatureHeight) {
                        inithideheaderView((int) ((movePullDistance - meatureHeight) / 2 + 0));
                        //旋转箭头,修改文本,下拉幅度到边界值大于一个HeadView高度
                        if (STATE_CURRENT == STATE_PULL) {//如果是下拉状态,改变
                            tv_loadtext.setText("松开刷新");
                            rotateArrow_changeText();
                            STATE_CURRENT = STATE_PUSH;//修改状态,在这个范围内不再修改状态
                        }
                    }
                    return super.onTouchEvent(event);
                }
                break;
            //手指抬起事件
            case MotionEvent.ACTION_UP:
                if (STATE_CURRENT == STATE_PUSH) {//如果是松开刷新状态那就改变状态
                    inithideheaderView(0);
                    //松开手指后,图片修改为旋转progressBar,修改文本
                    pb_circle.setVisibility(View.VISIBLE);
                    //必须将动画清除掉。。不然无法隐藏
                    iv_arrow.clearAnimation();
                    iv_arrow.setVisibility(GONE);
                    tv_loadtext.setText("正在刷新");
                    STATE_CURRENT = STATE_REFRESH;//修改为刷新状态
                    if (mOnRefreshStateListener != null && !refreshingState) {//如果不是正在刷新的状态,而且回调接口非空
                        refreshingState = true;
                        mOnRefreshStateListener.OnRefresh();//执行回调的接口
                    }
                } else if (STATE_CURRENT == STATE_PULL) {
                    inithideheaderView(-meatureHeight);
                    pb_circle.setVisibility(View.GONE);
                    iv_arrow.setVisibility(View.VISIBLE);
                    //因为已经清除了动画,所以这里自动回去了,不必在设定转回原点
                    tv_loadtext.setText("下拉刷新");
                    STATE_CURRENT = STATE_PULL;
                }
                break;
        }
        return super.onTouchEvent(event);//必须有这个,这个是让ListView滑动的
    }

1.3 刷新中需要设定一些小动画,这些动画实现方法,如下

/**
     * 创建旋转动画
     */
    private void setRotateAnimation(float fromDegrees, float toDegrees) {
        mRotateAnimation = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateAnimation.setDuration(500);
        mRotateAnimation.setFillAfter(true);
    }

 /**
     * 刷新完成后的状态
     */
    public void refreshComplement() {
        inithideheaderView(-meatureHeight);
        pb_circle.setVisibility(View.GONE);
        iv_arrow.setVisibility(View.VISIBLE);
        //因为已经清除了动画,所以这里自动回去了,不必在设定转回原点
        tv_loadtext.setText("下拉刷新");
        STATE_CURRENT = STATE_PULL;
        refreshingState = false;
    }

    /**
     * 旋转箭头,修改文本
     */
    private void rotateArrow_changeText() {
        if (STATE_CURRENT == STATE_PULL) {//如果状态为下拉状态
            fromDegrees = 0f;
            toDegrees = 180f;
            //iv_arrow.clearAnimation();//先清除一下动画效果
            setRotateAnimation(fromDegrees, toDegrees);
            iv_arrow.startAnimation(mRotateAnimation);
        } else if (STATE_CURRENT == STATE_PUSH) {
            fromDegrees = 180f;
            toDegrees = 360f;
            //iv_arrow.clearAnimation();//先清除一下动画效果
            setRotateAnimation(fromDegrees, toDegrees);
            iv_arrow.startAnimation(mRotateAnimation);
        }
    }

写到这儿,下拉刷新的方法基本完成,在下一步就是在主Activity中创建监听,在自定义ListView中实现回调接口,

 /**
     * 创建是否刷新的监听
     */
    public void setOnRefreshStateListener(OnRefreshStateListener listener) {
        mOnRefreshStateListener = listener;
    }
 /**
     * 定义正在刷新回调接口
     */
    public interface OnRefreshStateListener {
        void OnRefresh();
    }

主Activity中调用监听方法

pplv_test.setOnRefreshStateListener(new PullPushListView.OnRefreshStateListener() {
            @Override
            public void OnRefresh() {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        objects.add(0, "卧槽,刷新出来了");
                        adapter.notifyDataSetChanged();
                        pplv_test.refreshComplement();//通知控件刷新操作已经完成
                    }
                }, 3000);
            }
        });

华丽分割线


第二部分:实现上拉加载,同样的原理使用padding隐藏,不过要注意的是,同样使用-padding数值,因为 -数值才能类似于隐藏效果


2.1 footerView的布局效果,布局没什么好说的

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="horizontal">

    <ProgressBar
        android:id="@+id/pb_loadmore"
        style="@android:style/Widget.ProgressBar.Small"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_marginBottom="13dp"
        android:layout_marginTop="13dp"
        android:indeterminateDrawable="@drawable/progress_medium_white" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="加载更多"
        android:textColor="#ff0000"
        android:textSize="17sp" />
</LinearLayout>

2.2 初始化footerView 数据

 /**
     * 添加尾部头目,加载更多
     */
    private void initFooterView() {
        //打气筒添加布局
        footerView = View.inflate(getContext(), R.layout.item_footerview, null);
        //获取测量的宽高
        footerView.measure(0, 0);
        footerViewHeight = footerView.getMeasuredHeight();
        int top = -footerViewHeight;
        //设定初始padding的位置
        initHideFooterView(top);
        //将布局添加到Listview 中
        super.addFooterView(footerView);
        super.setOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                //如果滑动状态为空闲,且滑动到了最后一条条目
                if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition() == getCount() - 1 && !loadState) {
                    setSelection(getCount() - 1);//设定选择最后一条条目
                    initHideFooterView(0);
                    loadState = true;
                    mOnLoadStateListener.onLoadState();
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }

    /**
     * 动态设定FooterView的Padding
     *
     * @param top 动态设定
     */
    private void initHideFooterView(int top) {
        footerView.setPadding(0, top, 0, 0);
    }

    /**

2.3 这里上拉刷新的操作不再OnTouch()中实现了,我们在OnscrollListener()方法中实现

 super.setOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                //如果滑动状态为空闲,且滑动到了最后一条条目
                if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition() == getCount() - 1 && !loadState) {
                    setSelection(getCount() - 1);//设定选择最后一条条目
                    initHideFooterView(0);
                    loadState = true;
                    mOnLoadStateListener.onLoadState();
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });

2.4 实现接口回调,以及在主Activity中实现监听

/**
     * 创建是否加载更多的刷新
     */
    public void setOnLoadStateListener(onLoadStateListener listener) {
        mOnLoadStateListener = listener;
    }

    /**
     * 完成加载,修改状态
     */
    public void loadComplement() {
        initHideFooterView(-footerViewHeight);
        loadState = false;
    }

    /**
     * 定义正在加载的回调接口
     */
    public interface onLoadStateListener {
        void onLoadState();
    }

主方法中创建加载监听

 pplv_test.setOnLoadStateListener(new PullPushListView.onLoadStateListener() {
            @Override
            public void onLoadState() {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        objects.add("卧槽,加载出来了");
                        adapter.notifyDataSetChanged();
                        pplv_test.loadComplement();//通知控件刷新操作已经完成
                    }
                }, 3000);
            }
        });

github源码地址

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值