关闭

View系列(1)--自定义一个ListView的下拉刷新

标签: android
207人阅读 评论(0) 收藏 举报
分类:

下拉刷新是常见的Android效果之一,下面我们来看看它是怎么实现的。

下拉刷新会在头部出现一个头部,然后我们控制这个头部显示,隐藏,来实现下拉刷新的效果。在listview里面的item是从0开始数的,这个头部item就是在第0个item前面一个,我们现在要做的是控制这个头部的绘画和动作事件。

/**
     * 初始化界面,添加顶部布局文件到 listview
     *
     * @param context
     */
    private void initView(Context context) {
        LayoutInflater inflater = LayoutInflater.from(context);
        header = inflater.inflate(R.layout.header_layout, null);
        measureView(header);
        headerHeight = header.getMeasuredHeight();
        Log.i("tag", "headerHeight = " + headerHeight);
        topPadding(-headerHeight);
        this.addHeaderView(header);
        this.setOnScrollListener(this);
    }

header是设置头部布局,这个布局在header_layout.xml里面,然后测量高度。addHeaderView是listview为我们提供的头部,setOnScrollListener是监听滑动事件。
我的头部布局如下,这里我就不贴xml里面的代码了。
header_layout布局
header_layout

measureView是测量子空间的宽高

 /**
     * 通知父布局,占用的宽,高;
     *
     * @param view
     */
    private void measureView(View view) {
        ViewGroup.LayoutParams p = view.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
        //spec,padding均为0,这是子布局为实际大小。
        //   spec 父窗口传递给子视图的大小和模式
        //  padding 父窗口的边距,也就是xml中的android:padding
        //   childDimension 子视图想要绘制的准确大小,但最终不一定绘制此值        
        int height;
        int tempHeight = p.height;
        if (tempHeight > 0) {
            height = MeasureSpec.makeMeasureSpec(tempHeight,
                    MeasureSpec.EXACTLY);
        } else {
            height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        view.measure(width, height);
    }

topPadding设置上边距,控制头部里上边距的多少,换句话说就是控制head的显示和隐藏。

/**
     * 设置header 布局 上边距;
     *
     * @param topPadding
     */
    private void topPadding(int topPadding) {
        header.setPadding(header.getPaddingLeft(), topPadding,
                header.getPaddingRight(), header.getPaddingBottom());
        header.invalidate();
    }

初始化搞定之后,这时候的头部是隐藏了的。下面将设置点击事件,并和绘图结合。随事件发生而更改画面
在此,先设置一些标志。

    View header;// 顶部布局文件;
    int headerHeight;// 顶部布局文件的高度;
    int firstVisibleItem;// 当前第一个可见的item的位置;
    int scrollState;// listview 当前滚动状态;
    boolean isRemark;// 标记,当前是在listview最顶端按下的;
    int startY;// 按下时的Y值;

    int state;// 当前的状态;
    final int NONE = 0;// 正常状态;
    final int PULL = 1;// 提示下拉状态;
    final int RELESE = 2;// 提示释放状态;
    final int REFLASHING = 3;// 刷新状态;
    IReflashListener iReflashListener;//刷新数据的接口

这里我设置了下拉刷新的四个状态。

点击事件

   @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (firstVisibleItem == 0) {//表示listview已经在最顶端了,显示第0个item
                    isRemark = true;
                    startY = (int) ev.getY();
                }
                break;

            case MotionEvent.ACTION_MOVE:
                onMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                if (state == RELESE) {
                    state = REFLASHING;
                    // 加载最新数据;
                    reflashViewByState();
                    iReflashListener.onReflash();
                } else if (state == PULL) {
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

手指按下时候记录一下当前的坐标,并标志isRemark为true表示按下了。
移动过程中调用onMove(ev)来操作各种移动操作。
手指离开时,判断一下当前的状态,如果是下拉完成了就加载数据,回调加载完成操作。
如果是下拉没完成,就恢复状态。
四个下拉状态都在onTouchEvent里完成标志,当然在移动过程中也会设置状态标志。

下拉的四个状态区分好之后,下面就是根据这些状态设置不同的动作了。
reflashViewByState这个方法设置了这四个状态不同动作。这里我设置了箭头翻转动作、文字变化和加载动作。

/**
     * 根据当前状态,改变界面显示;
     */
    private void reflashViewByState() {
        TextView tip = (TextView) header.findViewById(R.id.tip);
        ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
        ProgressBar progress = (ProgressBar) header.findViewById(R.id.progress);
        //翻转180度
        RotateAnimation anim = new RotateAnimation(0, 180,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        anim.setDuration(500);//设置延时
        anim.setFillAfter(true);
        //再翻转180度,翻转回来
        RotateAnimation anim1 = new RotateAnimation(180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        anim1.setDuration(500);
        anim1.setFillAfter(true);
        switch (state) {
            case NONE:
                arrow.clearAnimation();
                topPadding(-headerHeight);
                break;

            case PULL:
                arrow.setVisibility(View.VISIBLE);
                progress.setVisibility(View.GONE);
                tip.setText("下拉可以刷新!");
                arrow.clearAnimation();
                arrow.setAnimation(anim1);
                break;
            case RELESE:
                arrow.setVisibility(View.VISIBLE);
                progress.setVisibility(View.GONE);
                tip.setText("松开可以刷新!");
                arrow.clearAnimation();
                arrow.setAnimation(anim);
                break;
            case REFLASHING:
                topPadding(50);
                arrow.setVisibility(View.GONE);
                progress.setVisibility(View.VISIBLE);
                tip.setText("正在刷新...");
                arrow.clearAnimation();
                break;
        }
    }

在移动的过程中,我们将根据移动的距离来设置状态标志,并且刷新画面。

/**
     * 判断移动过程操作;
     *
     * @param ev
     */
    private void onMove(MotionEvent ev) {
        if (!isRemark) {
            return;
        }
        int tempY = (int) ev.getY();//当前坐标
        int space = tempY - startY;//移动间距
        int topPadding = space - headerHeight;//移动距离和头部高度的差距
        switch (state) {
            case NONE:
                if (space > 0) {
                    state = PULL;
                    reflashViewByState();
                }
                break;
            case PULL:
                topPadding(topPadding);
                if (space > headerHeight + 30
                        && scrollState == SCROLL_STATE_TOUCH_SCROLL) {
                    state = RELESE;
                    reflashViewByState();
                }
                break;
            case RELESE:
                topPadding(topPadding);
                if (space < headerHeight + 30) {//退回了
                    state = PULL;
                    reflashViewByState();
                } else if (space <= 0) {
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }//其他情况还是RELESE状态,不变。
                break;
        }
    }

在移动过程中状态是在NONE -> PULL -> RELESE 一步步加深,往下转移的。

上面还有些参数需要获取的,比如scrollState 和 firstVisibleItem 。它是在AbsListView.OnScrollListener 的回调中获取到的。

   @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        this.scrollState = scrollState;
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.firstVisibleItem = firstVisibleItem;
    }

好了,现在下拉刷新操作基本完成,剩下就设置给外面调用的接口和方法。

回调接口

public void setInterface(IReflashListener iReflashListener){
        this.iReflashListener = iReflashListener;
    }
    /**
     * 刷新数据接口
     * @author Administrator
     */
    public interface IReflashListener{
        public void onReflash();
    }

reflashComplete,外面调用的方法。记录刷新完成后获取数据的时间。

/**
     * 获取完数据;
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    public void reflashComplete() {
        state = NONE;
        isRemark = false;
        reflashViewByState();
        TextView lastupdatetime = (TextView) header
                .findViewById(R.id.lastupdate_time);
        SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        String time = format.format(date);
        lastupdatetime.setText(time);
    }

再来个总体代码

package com.example.uidemo.listviewfrash.view;

import android.content.Context;
import android.icu.text.SimpleDateFormat;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.uidemo.R;

import java.util.Date;

/**
 * Created by Administrator on 2017/6/21.
 */

public class ReFlashListView extends ListView implements AbsListView.OnScrollListener {

    View header;// 顶部布局文件;
    int headerHeight;// 顶部布局文件的高度;
    int firstVisibleItem;// 当前第一个可见的item的位置;
    int scrollState;// listview 当前滚动状态;
    boolean isRemark;// 标记,当前是在listview最顶端摁下的;
    int startY;// 摁下时的Y值;

    int state;// 当前的状态;
    final int NONE = 0;// 正常状态;
    final int PULL = 1;// 提示下拉状态;
    final int RELESE = 2;// 提示释放状态;
    final int REFLASHING = 3;// 刷新状态;
    IReflashListener iReflashListener;//刷新数据的接口


    public ReFlashListView(Context context) {
        super(context);
        initView(context);
    }

    public ReFlashListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

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

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public ReFlashListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initView(context);
    }

    /**
     * 初始化界面,添加顶部布局文件到 listview
     *
     * @param context
     */
    private void initView(Context context) {
        LayoutInflater inflater = LayoutInflater.from(context);
        header = inflater.inflate(R.layout.header_layout, null);
        measureView(header);
        headerHeight = header.getMeasuredHeight();
        Log.i("tag", "headerHeight = " + headerHeight);
        topPadding(-headerHeight);
        this.addHeaderView(header);
        this.setOnScrollListener(this);
    }

    /**
     * 通知父布局,占用的宽,高;
     *
     * @param view
     */
    private void measureView(View view) {
        ViewGroup.LayoutParams p = view.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
        int height;
        int tempHeight = p.height;
        if (tempHeight > 0) {
            height = MeasureSpec.makeMeasureSpec(tempHeight,
                    MeasureSpec.EXACTLY);
        } else {
            height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        view.measure(width, height);
    }


    /**
     * 设置header 布局 上边距;
     *
     * @param topPadding
     */
    private void topPadding(int topPadding) {
        header.setPadding(header.getPaddingLeft(), topPadding,
                header.getPaddingRight(), header.getPaddingBottom());
        header.invalidate();
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (firstVisibleItem == 0) {
                    isRemark = true;
                    startY = (int) ev.getY();
                }
                break;

            case MotionEvent.ACTION_MOVE:
                onMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                if (state == RELESE) {
                    state = REFLASHING;
                    // 加载最新数据;
                    reflashViewByState();
                    iReflashListener.onReflash();
                } else if (state == PULL) {
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }


    /**
     * 判断移动过程操作;
     *
     * @param ev
     */
    private void onMove(MotionEvent ev) {
        if (!isRemark) {
            return;
        }
        int tempY = (int) ev.getY();
        int space = tempY - startY;
        int topPadding = space - headerHeight;
        switch (state) {
            case NONE:
                if (space > 0) {
                    state = PULL;
                    reflashViewByState();
                }
                break;
            case PULL:
                topPadding(topPadding);
                if (space > headerHeight + 30
                        && scrollState == SCROLL_STATE_TOUCH_SCROLL) {
                    state = RELESE;
                    reflashViewByState();
                }
                break;
            case RELESE:
                topPadding(topPadding);
                if (space < headerHeight + 30) {
                    state = PULL;
                    reflashViewByState();
                } else if (space <= 0) {
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;
        }
    }

    /**
     * 根据当前状态,改变界面显示;
     */
    private void reflashViewByState() {
        TextView tip = (TextView) header.findViewById(R.id.tip);
        ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
        ProgressBar progress = (ProgressBar) header.findViewById(R.id.progress);
        //翻转
        RotateAnimation anim = new RotateAnimation(0, 180,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        anim.setDuration(500);
        anim.setFillAfter(true);
        RotateAnimation anim1 = new RotateAnimation(180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        anim1.setDuration(500);
        anim1.setFillAfter(true);
        switch (state) {
            case NONE:
                arrow.clearAnimation();
                topPadding(-headerHeight);
                break;

            case PULL:
                arrow.setVisibility(View.VISIBLE);
                progress.setVisibility(View.GONE);
                tip.setText("下拉可以刷新!");
                arrow.clearAnimation();
                arrow.setAnimation(anim1);
                break;
            case RELESE:
                arrow.setVisibility(View.VISIBLE);
                progress.setVisibility(View.GONE);
                tip.setText("松开可以刷新!");
                arrow.clearAnimation();
                arrow.setAnimation(anim);
                break;
            case REFLASHING:
                topPadding(50);
                arrow.setVisibility(View.GONE);
                progress.setVisibility(View.VISIBLE);
                tip.setText("正在刷新...");
                arrow.clearAnimation();
                break;
        }
    }

    /**
     * 获取完数据;
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    public void reflashComplete() {
        state = NONE;
        isRemark = false;
        reflashViewByState();
        TextView lastupdatetime = (TextView) header
                .findViewById(R.id.lastupdate_time);
        SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        String time = format.format(date);
        lastupdatetime.setText(time);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        this.scrollState = scrollState;
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.firstVisibleItem = firstVisibleItem;
    }

    public void setInterface(IReflashListener iReflashListener){
        this.iReflashListener = iReflashListener;
    }
    /**
     * 刷新数据接口
     * @author Administrator
     */
    public interface IReflashListener{
        public void onReflash();
    }
}

效果图

下拉刷新

下拉刷新中

0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

Android自定义ListView,轻松实现上下拉刷新,一看就懂,一学就会,超简单。

之前用别人的ListView,总是不能满足项目需求,故此特意研究一下自定义listview,和大家分享一下 ,简单易懂。
  • u013790519
  • u013790519
  • 2015-09-24 17:20
  • 3017

Android自定义控件——ListView的下拉刷新与上拉加载

无疑,在Android开发中,ListView是使用非常频繁的控件之一,ListView提供一个列表的容易,允许我们以列表的形式将数据展示到界面上,但是Google给我们提供的原生ListView的控...
  • lee_tianya
  • lee_tianya
  • 2014-10-10 20:14
  • 17157

再也不用担心下拉刷新,上拉加载啦!-自定义ListView对上拉刷新,上拉加载的详解

前言:       看过许多下拉刷新的例子,好多大牛们的代码写的很完美,让人羡慕嫉妒恨~~~,可是,对于下拉刷新时的手势操作却没有给出详细的解释,当一堆堆逻辑代码出来的时候,对于我们这些菜鸟来说,理解...
  • yyh448522331
  • yyh448522331
  • 2015-10-12 21:20
  • 2300

Android自定义View(一)---ListView 下拉刷新

一直都在向构架方向努力,也知道UI方面自己是一个白痴,而且屡屡被人完虐。一直想学习UI以及动画。求人不如求自己。同时也为了给一群像我一样UI不怎么好的孩子一个能够补全Android知识的机会。 我不...
  • gezihau
  • gezihau
  • 2016-12-28 11:31
  • 166

Android自定义View之快速实现下拉刷新, 点击加载更多ListView

介绍 ListView是最常用UI组件之一. 由于手机的屏幕大小很有限, 如何在如此有限的空间简化交互操作, 将省下的空间用于显示更多的数据就显得相当有意义. 比如"刷新数据" 和 "加载下一页数据...
  • stzy00
  • stzy00
  • 2015-06-12 16:26
  • 1488

Android自定义控件下拉刷新上拉加载,所有View通用.(直接拿来用)

  • 2015-12-23 10:07
  • 7.61MB
  • 下载

文字碎片效果的下拉刷新,可以自定义refresh view

  • 2015-09-23 11:02
  • 817KB
  • 下载

自定义View Demo(实现下拉刷新)

  • 2016-10-22 22:25
  • 2.53MB
  • 下载

自定义ListView下拉刷新-拉出RefreshView

  • 2015-07-27 23:08
  • 1.20MB
  • 下载

listview 自定义上拉刷新和下拉加载

  • 2016-08-25 13:13
  • 14.32MB
  • 下载
    个人资料
    • 访问:36205次
    • 积分:676
    • 等级:
    • 排名:千里之外
    • 原创:31篇
    • 转载:1篇
    • 译文:0篇
    • 评论:6条
    最新评论