6.扩展已有的控件:RefreshListView(下拉刷新、上拉加载)

案例演示:

GIF.gif

关键点:

  • 1.下拉刷新
    1. 通过设置Padding为负数来隐藏头/脚布局;
    1. 通过公式int paddingTop=-自身测量高度+(moveY-downY);
    1. 通过偏移量=moveY-downY >0 从而判断是下拉
  • 2.下拉刷新
    1. 通过设置标记位,来判断是否能加载更多;
    1. 通过判断最后一个可见条目为空闲状态并且是刚好等于集合size,且标记位满足条件;
      实现思路:

实现步骤:

  • 1.下拉刷新:
  • 1.继承自ListView,并自定义头布局填充:
    View mHeaderView = View.inflate(getContext(), R.layout.layout_headview, null);
  • 2.设置padding为负值,来隐藏头布局
    // 提前手动测量宽高
    mHeaderView.measure(0, 0);// 按照设置的规则测量
    mHeaderViewHeight = mHeaderView.getMeasuredHeight();
    // 设置内边距, 可以隐藏当前控件 , -自身高度
    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
    // 在设置数据适配器之前执行添加 头布局/脚布局 的方法.
    addHeaderView(mHeaderView);
  • 3.下拉头部显示设置(不断改变修改paddingTop)
  • 4.监听触摸动作
    //公式:
    int paddingTop=-自身测量高度+(moveY-downY)
    
    //触摸监听:
     @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // 判断滑动距离, 给Header设置paddingTop
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = ev.getY();
                // 如果是正在刷新中, 就执行父类的处理
                if(currentState == REFRESHING){
                    return super.onTouchEvent(ev);
                }
                float offset = moveY - downY; // 移动的偏移量
                // 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
                if(offset > 0 && getFirstVisiblePosition() == 0){
                    int paddingTop = (int) (- mHeaderViewHeight + offset);
                    mHeaderView.setPadding(0, paddingTop, 0, 0);

                    if(paddingTop >= 0 && currentState != RELEASE_REFRESH){// 头布局完全显示
                        // 切换成释放刷新模式
                        currentState = RELEASE_REFRESH;
                        updateHeader(); // 根据最新的状态值更新头布局内容
                    }else if(paddingTop < 0 && currentState != PULL_TO_REFRESH){ // 头布局不完全显示
                        // 切换成下拉刷新模式
                        currentState = PULL_TO_REFRESH;
                        updateHeader(); // 根据最新的状态值更新头布局内容
                    }
                    return true; // 当前事件被我们处理并消费
                }
                break;
            case MotionEvent.ACTION_UP:
                // 根据刚刚设置状态
                if(currentState == PULL_TO_REFRESH){
                    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
                }else if(currentState == RELEASE_REFRESH){
                    mHeaderView.setPadding(0, 0, 0, 0);
                    currentState = REFRESHING;
                    updateHeader();
                }else{
                    //防止“下拉刷新”和“加载更多”一起出现:
                    if (currentState==REFRESHING && isLoadMore){
                        currentState=PULL_TO_REFRESH;
                    }
                }
                break;
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }
  • 5.下拉刷新数据监听设置
    public interface RefreshListener{
        void onRefresh();
        void onLoadMore();
    }

    public void setOnRefreshListener(RefreshListener refreshListener){
        this.listener=refreshListener;
    }
  • 2.加载更多:
  • 1.隐藏脚布局
  • 2.下拉刷新数据监听设置
  • 3.完整代码:
public class RefreshListView extends ListView implements AbsListView.OnScrollListener {

    private View mHeaderView,mFooterView; // 头布局,脚布局
    private float downY; // 按下的y坐标
    private float moveY; // 移动后的y坐标
    private int mHeaderViewHeight,mFooterViewHeight; // 头布局高度,脚布局高度
    public static final int PULL_TO_REFRESH = 0;// 下拉刷新
    public static final int RELEASE_REFRESH = 1;// 释放刷新
    public static final int REFRESHING = 2; // 刷新中
    private boolean isLoadMore=false;
    private int currentState = PULL_TO_REFRESH; // 当前刷新模式
    private RotateAnimation rotateUpAnim; // 箭头向上动画
    private RotateAnimation rotateDownAnim; // 箭头向下动画
    private View mArrowView;        // 箭头布局
    private TextView mTitleText,mTimeText;  // 头布局标题
    private ProgressBar pb;         // 进度指示器
    private  RefreshListener listener;
    public RefreshListView(Context context) {
        super(context);
        init();
    }
    public RefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    /**
     * 初始化头布局, 脚布局
     * 滚动监听
     */
    private void init() {
        initHeaderView();
        initFooterView();
        initAnimation();
        setOnScrollListener(this);
    }

    /**
     * 初始化头布局的动画
     */
    private void initAnimation() {
        // 向上转, 围绕着自己的中心, 逆时针旋转0 -> -180.
        rotateUpAnim = new RotateAnimation(0f, -180f,
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        rotateUpAnim.setDuration(300);
        rotateUpAnim.setFillAfter(true); // 动画停留在结束位置

        // 向下转, 围绕着自己的中心, 逆时针旋转 -180 -> -360
        rotateDownAnim = new RotateAnimation(-180f, -360,
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        rotateDownAnim.setDuration(300);
        rotateDownAnim.setFillAfter(true); // 动画停留在结束位置

    }

    /**
     * 初始化头布局
     */
    private void initHeaderView() {
        mHeaderView = View.inflate(getContext(), R.layout.layout_headview, null);
        mArrowView = mHeaderView.findViewById(R.id.iv_arrow);
        pb = mHeaderView.findViewById(R.id.pb);
        mTitleText =  mHeaderView.findViewById(R.id.tv_title);
        mTimeText=mHeaderView.findViewById(R.id.tv_time);

        // 提前手动测量宽高
        mHeaderView.measure(0, 0);// 按照设置的规则测量
        mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        // 设置内边距, 可以隐藏当前控件 , -自身高度
        mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
        // 在设置数据适配器之前执行添加 头布局/脚布局 的方法.
        addHeaderView(mHeaderView);
    }

    /**
     * 初始化脚布局
     */
    private void initFooterView() {
        mFooterView = View.inflate(getContext(), R.layout.layout_footview, null);
        mFooterView.measure(0,0);
        mFooterViewHeight=mFooterView.getMeasuredHeight();
        mFooterView.setPadding(0,-mFooterViewHeight,0,0);
        addFooterView(mFooterView);
    }



    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // 判断滑动距离, 给Header设置paddingTop
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = ev.getY();
                // 如果是正在刷新中, 就执行父类的处理
                if(currentState == REFRESHING){
                    return super.onTouchEvent(ev);
                }
                float offset = moveY - downY; // 移动的偏移量
                // 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
                if(offset > 0 && getFirstVisiblePosition() == 0){
                    int paddingTop = (int) (- mHeaderViewHeight + offset);
                    mHeaderView.setPadding(0, paddingTop, 0, 0);

                    if(paddingTop >= 0 && currentState != RELEASE_REFRESH){// 头布局完全显示
                        // 切换成释放刷新模式
                        currentState = RELEASE_REFRESH;
                        updateHeader(); // 根据最新的状态值更新头布局内容
                    }else if(paddingTop < 0 && currentState != PULL_TO_REFRESH){ // 头布局不完全显示
                        // 切换成下拉刷新模式
                        currentState = PULL_TO_REFRESH;
                        updateHeader(); // 根据最新的状态值更新头布局内容
                    }
                    return true; // 当前事件被我们处理并消费
                }
                break;
            case MotionEvent.ACTION_UP:
                // 根据刚刚设置状态
                if(currentState == PULL_TO_REFRESH){
                    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
                }else if(currentState == RELEASE_REFRESH){
                    mHeaderView.setPadding(0, 0, 0, 0);
                    currentState = REFRESHING;
                    updateHeader();
                }else{
                    //防止“下拉刷新”和“加载更多”一起出现:
                    if (currentState==REFRESHING && isLoadMore){
                        currentState=PULL_TO_REFRESH;
                    }
                }
                break;
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }


    /**
     * 根据状态更新头布局内容
     */
    private void updateHeader() {
        switch (currentState) {
            case PULL_TO_REFRESH: // 切换回下拉刷新
                // 做动画, 改标题
                mArrowView.startAnimation(rotateDownAnim);
                mTitleText.setText("下拉刷新");
                break;
            case RELEASE_REFRESH: // 切换成释放刷新
                // 做动画, 改标题
                mArrowView.startAnimation(rotateUpAnim);
                pb.setVisibility(INVISIBLE);
                mTitleText.setText("释放刷新");
                break;
            case REFRESHING: // 刷新中...
                mArrowView.clearAnimation();
                mArrowView.setVisibility(View.INVISIBLE);
                pb.setVisibility(View.VISIBLE);
                mTitleText.setText("正在刷新中...");
                //
                mTimeText.setText("最后更新时间:"+getTime());
                if (listener!=null){
                    listener.onRefresh();
                }
                break;
            default:
                break;
        }
    }
    private String getTime() {
        long currentTimeMillis = System.currentTimeMillis();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(currentTimeMillis);
    }

    public void endRefreshing(){
        if (isLoadMore){
            //关闭加载更多:
            isLoadMore=false;
            mFooterView.setPadding(0,-mFooterViewHeight,0,0);
        }else{
            //关闭正在刷新:
            mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
            mHeaderView.clearAnimation();
            currentState=PULL_TO_REFRESH;
        }

    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState==SCROLL_STATE_FLING || scrollState==SCROLL_STATE_IDLE ){
            //在最后一个条目 且为空闲状态时候才加载更多
            if (getLastVisiblePosition()==getCount()-1 && isLoadMore==false){
                isLoadMore=true;
                //脚布局显示:
                mFooterView.setPadding(0,0,0,0);
                setSelection(getCount());//跳转到最后一行,使其显示加载更多

                //加载更多:
                if (listener!=null){
                    listener.onLoadMore();
                }
            }
        }
    }

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

    }

    public interface RefreshListener{
        void onRefresh();
        void onLoadMore();
    }

    public void setOnRefreshListener(RefreshListener refreshListener){
        this.listener=refreshListener;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353