• 自定义ListView下拉刷新上拉加载更多


    Listview现在用的很少了,基本都是使用Recycleview,但是不得不说Listview具有划时代的意义,拓展性很强,我们可以自己添加下拉刷新,上拉加载更多功能。他和recycleview不同,他生来具有addHeaderView和addFooterView的功能,这也导致同样都是列表控件,实现上拉下拉的方式缺截然不同。

    效果图

    1、创建刷新控件

    public class MyListview extends ListView {
    
    
        public MyListview(Context context) {
            this(context,null);
        }
    
        public MyListview(Context context, AttributeSet attrs) {
            this(context,attrs,0);
        }
    
        public MyListview(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
        public void init(Context context) {
    
    
        }

    1.1、创建头部View

     头部样式,我写的是最简单的,根据业务需求来定,下拉的时候无非就是几种

    • 产品logo作为箭头转动的icon
    • 添加刷新时间
    • 加入其他具有特色的动效

    
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        tools:ignore="MissingDefaultResource">
        <ProgressBar
            android:layout_width="20dp"
            android:layout_height="match_parent"
            style="?android:attr/progressBarStyle"
            android:layout_marginEnd="10dp"
            android:indeterminateTint="#E8AD56"
            />
        <TextView
            android:id="@+id/header_text"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:text="我是头部"
            android:gravity="center"
            android:textColor="#E8AD56"/>
    LinearLayout>

     样式写好后,我们需要添加到我们控件中,addHeaderView就起到作用了

    public void init(Context context) {
        //添加头部
        viewHeader = View.inflate(context, R.layout.view_header, null);
        viewHeader.measure(0, 0);
        //让系统自动检测头部高度
        heightHeader = viewHeader.getMeasuredHeight();
        header_text = viewHeader.findViewById(R.id.header_text);
        viewHeader.setPadding(0, -heightHeader, 0, 0);
        this.addHeaderView(viewHeader);
    }

     一些控件我定义成全局的是因为下面会用到。setPadding可以设置显示位置,左上右下,上为负数就是隐藏在顶部。我们需要手指下拉去控制他缓慢显示,就用到了OnTouchListener,我们实现OnTouch方法做一些事件的分发处理。

    1.2、下拉事件

    public class MyListview extends ListView implements View.OnTouchListener{
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return false;
        }
       public void init(Context context){
           setOnTouchListener(this);
       }
    }

    注意:别忘记了setOnTouchListener在init中添加

     这时候我们需要对event的down,move,up事件进行逻辑处理,当手指按在屏幕时会触发事件,一个down事件,0~无数次move事件,一个up事件,这里面着重对move事件做处理,我们记录一下down事件的Y,因为是上下拉动,没必要计算X。然后diffY就是手指滑动的距离,我们需要处理一下这个值,因为值太大,而且值是整数,会让我们下拉的时候产生错乱,我们本意是让其从-100到0缓慢滑出(比如头部高度是100,从隐藏到显示就是-100到0),小伙伴都可以试试viewHeader.setPadding(0, diffY, 0, 0);和viewHeader.setPadding(0, paddY, 0, 0);效果是不一样的,diff/3是让其有种阻尼的感觉,不然的话会很块就被拉出来了。还有事件消费的话一定要return true。

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = (int) event.getY();
                diffY = moveY - downY;
                //滑动的距离
                paddY = - heightHeader + diffY / 3;
                viewHeader.setPadding(0, paddY, 0, 0);
                return true;
                //break;
            case MotionEvent.ACTION_UP:
    
                break;
        }
        return false;
    }

     我们下面要做下拉时给人的反馈,我们下拉时有3种状态分别是

    • 开始刷新  (下拉距离不超过100)
    • 释放刷新   (下拉距离超过100)
    • 刷新中    (手指释放,up事件处理)

     

     

    //属性-开始刷新状态
    private final int PULL_REFRESH_STATE = 0;
    private final int PULL_REFRESH_RELEASE = 1;//释放刷新
    private final int PULL_REFRESHING = 2;//正在刷新
    private int pull_current_state = PULL_REFRESH_STATE;//当前状态

     

    public void updateHeaderState() {
        switch (pull_current_state) {
            //开始
            case PULL_REFRESH_STATE:
                header_text.setText("开始刷新");
                viewHeader.setPadding(0, -heightHeader, 0, 0);
                break;
            //释放
            case PULL_REFRESH_RELEASE:
                header_text.setText("释放刷新");
                break;
            //正在
            case PULL_REFRESHING:
                header_text.setText("刷新中.......");
                viewHeader.setPadding(0, 0, 0, 0);
                break;
    
        }
    }

    我们只需要判断是否处于刷新中,如果不是,则计算是不是第一个item可见并且滑动距离大于0,证明手指滑动了。然后不同滑出高度显示不同的文字即可。

    @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downY = (int) event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    moveY = (int) event.getY();
                    diffY = moveY - downY;
                    //滑动的距离
                    paddY = - heightHeader + diffY / 3;
                    //如果是正在刷新中不做任何操作
                    if (pull_current_state == PULL_REFRESHING) {
                        return false;
                    }
    //                下拉//第一条,并且滑动距离大于0
                    if (getFirstVisiblePosition() == 0 && diffY > 0) {
                        if (paddY > 0 && pull_current_state != PULL_REFRESH_RELEASE) {
                            //释放刷新
                            pull_current_state = PULL_REFRESH_RELEASE;
                            updateHeaderState();
                        } else if (paddY < 0 && pull_current_state != PULL_REFRESH_STATE) {
                            pull_current_state = PULL_REFRESH_STATE;
                            header_text.setText("开始刷新");
                            updateHeaderState();
                        }
                        viewHeader.setPadding(0, paddY, 0, 0);
                        return true;
                    }
    
                    break;
                case MotionEvent.ACTION_UP:
                    if (pull_current_state == PULL_REFRESH_STATE) {
                        updateHeaderState();
                    } else if (pull_current_state == PULL_REFRESH_RELEASE) {
                        pull_current_state = PULL_REFRESHING;
                        updateHeaderState();
                    }
                    break;
            }
            return false;
        }

     1.3、接口回调

    我们需要状态根据业务来动态调整,在可以刷新的时候做一些逻辑处理,同时处理完了,调整状态。

    public void setPullDownFinish() {
        pull_current_state = PULL_REFRESH_STATE;
        viewHeader.setPadding(0, -heightHeader, 0, 0);
    }
    public IPullDownRefreshService iPullDownRefreshService;
    public interface IPullDownRefreshService {
        void onPullDownRefresh();//下拉刷新
    
        void onLoadMore();//上拉加载更多刷新
    }
    
    public void setOnRefreshListener(IPullDownRefreshService iPullDownRefreshService) {
        this.iPullDownRefreshService = iPullDownRefreshService;
    }

     准备工作做好后,我们在更新状态的地方调用

    public void updateHeaderState() {
        switch (pull_current_state) {
            //开始
            case PULL_REFRESH_STATE:
                break;
            //释放
            case PULL_REFRESH_RELEASE:
                break;
            //正在
            case PULL_REFRESHING:
                header_text.setText("刷新中.......");
                viewHeader.setPadding(0, 0, 0, 0);
                if (iPullDownRefreshService != null) {
                    iPullDownRefreshService.onPullDownRefresh();
                }
                break;
    
        }
    }

     Activity中使用,来一个3秒刷新完成

    mBinding.listview.setOnRefreshListener(new MyListview.IPullDownRefreshService() {
                @Override
                public void onPullDownRefresh() {
                    refreshSuccess();
                    Toast.makeText(ActivityRefresh.this, "下拉-加载中.....", Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onLoadMore() {
                    Toast.makeText(ActivityRefresh.this, "more-加载中.....", Toast.LENGTH_SHORT).show();
                }
            });
    
        CountDownTimer countDownTimer;
        public void refreshSuccess() {
            if (countDownTimer == null) {
                countDownTimer = new CountDownTimer(3000, 1000) {
    
                    @Override
                    public void onTick(long millisUntilFinished) {
    
                    }
    
                    @Override
                    public void onFinish() {
                        mBinding.listview.setPullDownFinish();
                        countDownTimer.cancel();
                        countDownTimer = null;
                    }
                }.start();
            }
        }

    到这下拉刷新就结束了。

    上拉加载更多也是如此

    2、上拉加载更多

     2.1、底部样式

    
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        tools:ignore="MissingDefaultResource">
        <ProgressBar
            android:id="@+id/footer_prggress"
            android:layout_width="20dp"
            android:layout_height="match_parent"
            style="?android:attr/progressBarStyle"
            android:layout_marginEnd="10dp"
            android:indeterminateTint="#E8AD56"
            />
        <TextView
            android:id="@+id/footer_text"
            android:layout_width="wrap_content"
            android:layout_height="70dp"
            android:text="正在加载更多"
            android:gravity="center"
            android:textColor="#E8AD56"/>
    
    LinearLayout>

     2.2、布局添加

    public void init(Context context) {
        viewFooter = View.inflate(context, R.layout.view_footer, null);
        viewFooter.measure(0, 0);
        footer_text = viewFooter.findViewById(R.id.footer_text);
        footer_prggress = viewFooter.findViewById(R.id.footer_prggress);
        heightFooter = viewFooter.getMeasuredHeight();
        viewFooter.setPadding(0, -heightFooter, 0, 0);
        this.addFooterView(viewFooter);
    }

     到这里也布局算是添加完毕了,加载更多实现方式不同,我罗列两种

    1. 结合OnTouchListener
    2. 结合OnScrollListener

    方式一 

    这里我先说方式一,因为我们下拉也是用的OnTouchListener,上拉加载也有几种状态,有加载中,还有暂无数据,普遍大家会写, 已经到底了~,一直显示在最底部,提示用户没数据了,从而我们控件的上拉事件不可触发状态。

    private final int MORE_LOAD_STATE = 10;
    private final int MORE_LOADING = 11;
    private final int MORE_NO = 12;//已加载全部数据
    private int more_current_state = MORE_LOAD_STATE;//当前状态

     只有一点需要注意paddFooterY = paddFooterY > heightFooter ? 0 : paddFooterY;来判断不能滑出底部的高度。

    @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downY = (int) event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    moveY = (int) event.getY();
                    diffY = moveY - downY;
                    //滑动的距离
                    paddFooterY = heightFooter - diffY/3 ;  
                    //上拉加载更多
                    if (getLastVisiblePosition() == getCount() - 1 && more_current_state != MORE_NO) {
                        if( more_current_state!=MORE_LOADING){
                            more_current_state = MORE_LOADING;
                            if (iPullDownRefreshService != null) {
                                iPullDownRefreshService.onLoadMore();
                            }
                        }
                        paddFooterY = paddFooterY > heightFooter ? 0 : paddFooterY;
                        viewFooter.setPadding(0,paddFooterY , 0, 0);
                         return true;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
            }
            return false;
        }

    方式二

    public class MyListview extends ListView implements View.OnTouchListener, AbsListView.OnScrollListener{
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            //没有滚动,或者已经用户触摸滚动动画结束
            if(scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING){
                //最后一个可见
                if(getLastVisiblePosition() == getCount()-1){
                    viewFooter.setPadding(0,0,0,0);
                    if(iPullDownRefreshService!=null){
                        iPullDownRefreshService.onLoadMore();
                    }
                }
            }
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    
        }
    
    }

    注意:在init方法中添加

    public void init(Context context) {
        setOnTouchListener(this);
        ......
        this.setOnScrollListener(this);
    }

     完工

     

  • 相关阅读:
    ExtJS类成员-辅助函数功能
    Linux内核基础 - list_move_tail函数详解
    江苏服务器有哪些特点
    PMP子过程定义总结
    10.5作业
    前端Vue小兔鲜儿电商项目实战Day07
    react-通过useRef完成倒计时60秒发送验证码效果
    veImageX 演进之路:Web 图片加载提速50%
    LeetCode:9. 回文数
    Java 代码 格式化插件
  • 原文地址:https://www.cnblogs.com/cmusketeer/p/16769560.html