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

来源:互联网 发布:sql delete from table 编辑:程序博客网 时间:2024/06/05 16:55

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


首先放一下效果

  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 0