下拉刷新 是请求网络数据中经常会用的一种功能.
实现步骤如下:
1.新建项目   PullToRefreshDemo,定义下拉显示的头部布局pull_to_refresh_refresh.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"
    android:id="@+id/pull_to_refresh_head"
    android:layout_height="60dip"
     >
     <LinearLayout 
         android:layout_width="200dip"
         android:layout_height="60dip"
         android:layout_centerInParent="true"
         android:orientation="horizontal"
         >
         <RelativeLayout 
             android:layout_width="0dip"
             android:layout_height="60dip"
             android:layout_weight="3"
             >
             <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/pb"
                 android:layout_width="30dip"
                 android:layout_height="30dip"
                 android:layout_centerInParent="true"
                 android:visibility="gone"
                 />
         </RelativeLayout>
         <LinearLayout 
             android:layout_width="0dip"
             android:layout_height="60dip"
             android:layout_weight="12"
             android:orientation="vertical"
             >
             <TextView 
                 android:id="@+id/tv_description"
                 android:layout_width="fill_parent"
                 android:layout_height="0dip"
                 android:layout_weight="1"
                 android:gravity="center_horizontal|bottom"
                 android:text="下拉可以刷新"
                 />
             <TextView 
                 android:id="@+id/tv_update"
                 android:layout_width="fill_parent"
                 android:layout_height="0dip"
                 android:layout_weight="1"
                 android:gravity="center_horizontal|top"
                 android:text="上次更新于%1$s前"
                 />
         </LinearLayout>
     </LinearLayout>
    
</RelativeLayout>

2.新建一个RefreshView继承自LinearLayout.
public class RefreshView extends LinearLayout implements OnTouchListener {
    //下拉状态
    public static final int STATUS_PULL_TO_REFRESH=0;
    //释放立即刷新状态
    public static final int STATUS_RELEASE_TO_REFRESH=1;
    //正在刷新状态
    public static final int STATUS_REFRESHING=2;
    //刷新完成或未刷新状态
    public static final int STATUS_REFRESH_FINISH=3;
    
    //下拉时头部回滚的速度
    public static final int SCROLL_SPEED=-20;
    
    //一分钟的毫秒值,判断上次的更新时间
    public static final long ONE_MINUTE=60*1000;
    //一小时的毫秒值,用于判断上次的更新时间
    public static final long ONE_HOUR=60*ONE_MINUTE;
    //一天的毫秒值
    public static final long ONE_DAY=24*ONE_HOUR;
    //一月的毫秒值
    public static final long ONE_MONTH=30*ONE_DAY;
    //一年的毫秒值
    public static final long ONE_YEAR=12*ONE_MONTH;
    
    //上次更新时间的字符串常量,用来做SharedPreference的键值
    public static final String UPDATE_AT="update_at";
    
    //存储上次更新时间
    private SharedPreferences mShared;
    
    //下拉时显示的View
    private View header;
    
    //下拉刷新的ListView
    private ListView lv;  
    
    //刷新时显示的进度条
    private ProgressBar mProgressBar;
    
    //指示下拉和释放的箭头
    private ImageView arrow;
    
    //指示下拉和释放的文字描述
    private TextView tv_des;
    
    //上次更新时间的文字描述
    private TextView tv_update;
    
    //下拉头的布局参数
    private MarginLayoutParams headerLayoutParams;
    
    //上次更新时间的毫秒数
    private long lastUpdateTime;
    
    //为了防止不同界面的下拉刷新与上次更新时间互相有冲突,使用id来做区分
    private int mId=-1;
    
    //下拉头的高度
    private int hideHeaderHeight;
    
    
    //标志当前是什么状态
    private int currentStatus=STATUS_REFRESH_FINISH;
    
    //记录上次的状态是什么,避免进行重复操作
    private int lastStatus=currentStatus;
    
    
    //手指按下时 的屏幕纵坐标
    private float yDown;
    
    //在被判断为滚动之前用户手指可以移动的最大值
    private int touchSlop;
    
    //判断已加载过一次layout,这里的onLayout的初始化只需加载一次
    private boolean loadOnce;
    
    //当前是否可以下拉,只有ListView滚到头才允许下拉
    private boolean ableToPull;
        
    //下拉刷新的回调接口
    private PullToRefreshListener mListener;
    
    public RefreshView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mShared=PreferenceManager.getDefaultSharedPreferences(context);   
        header=LayoutInflater.from(context).inflate(R.layout.pull_to_refresh,null,true);   
        mProgressBar=(ProgressBar) header.findViewById(R.id.pb);
        arrow=(ImageView) header.findViewById(R.id.iv_arrow);
        tv_des=(TextView) header.findViewById(R.id.tv_description);
        tv_update=(TextView) header.findViewById(R.id.tv_update);
        touchSlop=ViewConfiguration.get(context).getScaledTouchSlop()*3;   //得到  至少移动的距离
        refreshUpdatedAtValue();  //更新文字描述
        setOrientation(VERTICAL);  //设置摆放方向
        addView(header, 0);     
    }
    
    //进行一些关键的初始化操作,比如:将下拉头向上偏移进行隐藏,给ListView注册touch事件
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if(changed&&!loadOnce){   //只执行一次
            hideHeaderHeight=-header.getHeight();     //设置成负值   刚好隐藏在页面的最上方
            headerLayoutParams = (MarginLayoutParams) header.getLayoutParams(); 
            headerLayoutParams.topMargin=hideHeaderHeight;    //设置布局的topMargin
            header.setLayoutParams(headerLayoutParams);
            lv=(ListView) getChildAt(1);   //找到 listView  因为第一个Child是上拉头   所以第二个才是 ListView.
            lv.setOnTouchListener(this);
            loadOnce=true;
        }
    }
    
    //给下拉刷新控件注册一个监听器
    public void setOnRefreshListener(PullToRefreshListener mListener,int id){
        this.mListener=mListener;
        mId=id;
    }
    
    //更新下拉头中的信息
    private void updateHeaderView(){
        if(lastStatus!=currentStatus){
            if(currentStatus==STATUS_PULL_TO_REFRESH){
                tv_des.setText("下拉刷新");
                arrow.setVisibility(View.VISIBLE);
                mProgressBar.setVisibility(View.GONE);
                rotateArrow();
            }
            else if(currentStatus==STATUS_RELEASE_TO_REFRESH){
                tv_des.setText("释放刷新");
                arrow.setVisibility(View.VISIBLE);
                mProgressBar.setVisibility(View.GONE);
                rotateArrow();
            }
            else if(currentStatus==STATUS_REFRESHING){
                tv_des.setText("正在刷新中");
                mProgressBar.setVisibility(View.VISIBLE);
                arrow.clearAnimation(); //清除动画效果
                arrow.setVisibility(View.GONE);
            }
            refreshUpdatedAtValue();
        }
    }
    //根据当前的状态来旋转箭头
    private void rotateArrow(){
        float pivoX=arrow.getWidth()/2f;
        float pivoY=arrow.getHeight()/2f;
        
        float fromDegress=0f;
        float toDegress=0f;
        
        if(currentStatus==STATUS_PULL_TO_REFRESH){
            fromDegress=180f;
            toDegress=360f;
        }
        else{
            fromDegress=0f;
            toDegress=180f;
        }
        RotateAnimation animation=new RotateAnimation(fromDegress,toDegress,pivoX,pivoY);
        animation.setDuration(100);
        animation.setFillAfter(true);
        arrow.startAnimation(animation);
    }
    
    
    //根据当前listView的滚动状态来设定   ableToPull 的值
    //每次都需要在onTouch中的一个执行,这样可以判断出当前滚动的是listView,还是应该进行下拉
    private void setIsAbleToPull(MotionEvent event){
        View firstView=lv.getChildAt(0);
        if(firstView!=null){
            int firstVisiblePos=lv.getFirstVisiblePosition();   //获得listView顶头项的是该列数据的第几个
            if(firstVisiblePos==0&&firstView.getTop()==0){
                if(!ableToPull){
                    yDown=event.getRawY();
                }
                //如果首个元素的上边缘,距离父布局值为0,就说明 listView滚到了最顶部,此时允许下拉刷新
                ableToPull=true;
            }
            else{
                if(headerLayoutParams.topMargin!=hideHeaderHeight){
                    headerLayoutParams.topMargin=hideHeaderHeight;
                    header.setLayoutParams(headerLayoutParams);
                }
                ableToPull=false;
            }
        }
    }
    
    //当所有刷新的逻辑执行完成后,停止刷新, 并记录
    public void finishRefreshing(){
        currentStatus=STATUS_REFRESH_FINISH;
        mShared.edit().putLong(UPDATE_AT+mId, System.currentTimeMillis()).commit();
        new HideHeaderTask().execute();
    }
    
    
    //更新下拉头中上次更新时间的文字描述
    private void refreshUpdatedAtValue(){
        lastUpdateTime=mShared.getLong(UPDATE_AT+mId,-1);  //从配置文件中取出上次更新的时间的毫秒数
        long currentTime=System.currentTimeMillis();       //获得当前时间毫秒数
        long timePassed=currentTime-lastUpdateTime;        //中间相差的毫秒数  
        long timeIntoFormat;                               
        String updateAtValue;                             
        if(lastUpdateTime==-1){
            updateAtValue="暂未更新过";
        }
        else if(timePassed<0){
            updateAtValue="时间故障";
        }
        else if(timePassed<ONE_MINUTE){
            updateAtValue="刚刚更新";
        }
        else if(timePassed<ONE_HOUR){
            timeIntoFormat=timePassed/ONE_HOUR;
            String value=timeIntoFormat+"分钟";
            updateAtValue=String.format("上次更新于%1$s前",value);
        }
        else if(timePassed<ONE_DAY){
            timeIntoFormat=timePassed/ONE_HOUR;
            String value=timeIntoFormat+"小时";
            updateAtValue=String.format("上次更新于%1$s前",value);
        }
        else if(timePassed<ONE_MONTH){
            timeIntoFormat=timePassed/ONE_DAY;
            String value=timeIntoFormat+"天";
            updateAtValue=String.format("上次更新于%1$s前",value);
        }
        else if(timePassed<ONE_YEAR){
            timeIntoFormat=timePassed/ONE_MONTH;
            String value=timeIntoFormat+"月";
            updateAtValue=String.format("上次更新于%1$s前",value);
        }
        else{
            timeIntoFormat=timePassed/ONE_YEAR;
            String value=timeIntoFormat+"年";
            updateAtValue=String.format("上次更新于%1$s前",value);
        }
        tv_update.setText(updateAtValue);
    }
    
    //当listView被触摸时调用,其中处理了各种下拉刷新的具体逻辑
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        setIsAbleToPull(event);
        if(ableToPull){
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                yDown=event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                float yMove=event.getRawY();
                int distance=(int)(yMove-yDown);
                if(distance<=0&&headerLayoutParams.topMargin<=hideHeaderHeight){
                    return false;
                }
                if(distance<touchSlop){
                    return false;
                }
                if(currentStatus!=STATUS_REFRESHING){
                    if(headerLayoutParams.topMargin>0){
                        currentStatus=STATUS_RELEASE_TO_REFRESH;
                    }
                    else{
                        currentStatus=STATUS_PULL_TO_REFRESH;
                    }
                    headerLayoutParams.topMargin=(distance/2)+hideHeaderHeight;
                    header.setLayoutParams(headerLayoutParams);   //让  ListView可以弹动
                }
                break;
            case MotionEvent.ACTION_UP:
            default:
                if(currentStatus==STATUS_RELEASE_TO_REFRESH){
                    //松开手 如果是释放立即刷新  ,则去调用刷新的任务
                    new RefreshingTask().execute();
                }
                else if(currentStatus==STATUS_PULL_TO_REFRESH){
                    //松开手 如果是下拉状态,则去隐藏下拉头的任务
                    new HideHeaderTask().execute();
                }
                break;
            }
            
            if(currentStatus==STATUS_PULL_TO_REFRESH||currentStatus==STATUS_RELEASE_TO_REFRESH){
                updateHeaderView();
                //当前处于 下拉或释放 状态,要让listView失去焦点,否则被点击的那一项会一直处于选中状态
                lv.setPressed(false);
                lv.setFocusable(false);
                lv.setFocusableInTouchMode(false);
                lastStatus=currentStatus;
                return true;
            }
        }
        return false;
    }
    
    //正在刷新的任务
    class RefreshingTask extends AsyncTask<Void, Integer, Void>{
        @Override
        protected Void doInBackground(Void... params) {
            int topMargin=headerLayoutParams.topMargin;
            while(true){
                topMargin=topMargin+SCROLL_SPEED;
                if(topMargin<=0){
                    topMargin=0;
                    break;
                }
                publishProgress(topMargin);
                sleep(10);
            }
            currentStatus=STATUS_REFRESHING;
            publishProgress(0);
            if(mListener!=null){
                mListener.onRefresh();  //通知刷新
            }
            return null;
        }
        
        @Override
        protected void onProgressUpdate(Integer... topMargin) {
            updateHeaderView();
            headerLayoutParams.topMargin=topMargin[0];
            header.setLayoutParams(headerLayoutParams);
        }
    }
    
    //隐藏下拉头的任务
    class HideHeaderTask extends AsyncTask<Void, Integer, Integer>{
        @Override
        protected Integer doInBackground(Void... params) {
            int topMargin=headerLayoutParams.topMargin;
            while(true){
                topMargin=topMargin+SCROLL_SPEED;   //慢慢往回收缩
                if(topMargin<=hideHeaderHeight){    //判断是不是回到了原位
                    topMargin=hideHeaderHeight;
                    break;
                }
                publishProgress(topMargin);  //设置  收缩动作
                sleep(10);
            }
            return topMargin;
        }
        
        @Override
        protected void onProgressUpdate(Integer... values) {
            headerLayoutParams.topMargin=values[0];
            header.setLayoutParams(headerLayoutParams);
        }
        @Override
        protected void onPostExecute(Integer result) {
            headerLayoutParams.topMargin=result;
            header.setLayoutParams(headerLayoutParams);  
            currentStatus=STATUS_REFRESH_FINISH;
        }
        
        
    }
    
    /** 
     * 使当前线程睡眠指定的毫秒数。 
     *  
     * @param time 
     *            指定当前线程睡眠多久,以毫秒为单位 
     */  
    private void sleep(int time) {  
        try {  
            Thread.sleep(time);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
    
    
    //下拉刷新的监听器
    public interface PullToRefreshListener{
        void onRefresh();
    }
}

首先,在构造函数中动态添加了pull_to_refresh这个布局作为下拉头,然后将onLayout方法中将下拉头向上偏移出了屏幕,再给ListView注册了Touch事件.
如果在ListView上进行滑动,onTouch就会执行,onTouch首先会用setIsAbleToPull方法判断ListView是否滚动到了最顶部,只有滚动到最顶部才会执行后面的代码,否则就是ListView的正常滚动,不作处理.当ListView滚动到最顶部,如果手指还在向下拖动,就会改变下拉头的偏移值,让下拉头显示出来,如果下拉的距离足够大,在松手后就会执行刷新操作,如果距离不够大,则会隐藏下拉头.
具体刷新方法操作在RefreshingTask中进行,其中在doInBackground方法中回调了PullToRefreshListener接口的onRefresh()方法.
具体使用方法如下:
3.在activity_main.xml中
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <com.cy.pulltorefreshDemo.RefreshView 
        android:id="@+id/refresh_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
        <ListView 
            android:id="@+id/lv"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:cacheColorHint="@android:color/transparent"
            ></ListView>
    </com.cy.pulltorefreshDemo.RefreshView>
</RelativeLayout>

只要将需要刷新的ListView包含在  RefreshView中.
4.MainActivity.java
public class MainActivity extends Activity {
    RefreshView refreshView;
    ListView lv;
    ArrayAdapter<String> adapter;
    List<String> items=new ArrayList<String>();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        items.add("A");
        items.add("B");
        refreshView=(RefreshView) findViewById(R.id.refresh_view);
        lv=(ListView) findViewById(R.id.lv);
        adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,items);
        lv.setAdapter(adapter);
        refreshView.setOnRefreshListener(new PullToRefreshListener() {
            @Override
            public void onRefresh() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                items.add("222");//自动添加 到 ListView中
                refreshView.finishRefreshing();
            }
        }, 0);
    }
}
就这样,一个完整的下拉刷新.
来自为知笔记(Wiz)

转载于:https://www.cnblogs.com/aibuli/p/8ebf33bdc624ff23b4a65c1831fdc336.html

android 自定义控件之下拉刷新源码详解相关推荐

  1. Android进阶笔记:Messenger源码详解

    Messenger可以理解为一个是用于发送消息的一个类用法也很多,这里主要分析一下再跨进程的情况下Messenger的实现流程与源码分析.相信结合前面两篇关于aidl解析文章能够更好的对aidl有一个 ...

  2. 安卓开发基础面试题,Android面试必备的集合源码详解,附小技巧

    去年无疑是 Flutter 技术如火如荼发展的一年. 每一个移动开发者都在为 Flutter 带来的"快速开发.富有表现力和灵活的 UI.原生性能"的特色和理念而痴狂,从超级 Ap ...

  3. Android 事件分发机制分析及源码详解

    Android 事件分发机制分析及源码详解 文章目录 Android 事件分发机制分析及源码详解 事件的定义 事件分发序列模型 分发序列 分发模型 事件分发对象及相关方法 源码分析 事件分发总结 一般 ...

  4. Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解

    Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解 目录 Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解 一.OpenGL ES渲染管线 1.基本处 ...

  5. 源码详解Android 9.0(P) 系统启动流程之SystemServer

    源码详解Android 9.0(P) 系统启动流程目录: 源码详解Android 9.0(P)系统启动流程之init进程(第一阶段) 源码详解Android 9.0(P)系统启动流程之init进程(第 ...

  6. Android编程之Intent源码详解

    Intent源码详解,直接开始入题: Intent源码6700多行代码,但真正核心代码 就那么几百行,大部分都用来定义常量字符串了 先来看一下 public class Intent implemen ...

  7. cocos android-1,Cocos2D-Android-1之源码详解:5.Box2dTest

    Cocos2D-Android-1之源码详解:5.Box2dTest 发布时间:2020-08-06 06:19:28 来源:51CTO 阅读:398 作者:abab99 package org.co ...

  8. 李沐d2l《动手学深度学习》第二版——风格迁移源码详解

    本文是对李沐Dive to DL<动手学深度学习>第二版13.12节风格迁移的源码详解,整体由Jupyter+VSCode完成,几乎所有重要代码均给出了注释,一看就懂.需要的同学可以在文末 ...

  9. Vue-Watcher观察者源码详解

    源码调试地址 https://github.com/KingComedy/vue-debugger 什么是Watcher Watcher是Vue中的观察者类,主要任务是:观察Vue组件中的属性,当属性 ...

  10. Integer源码详解

    尊重原创,转载请标明出处    http://blog.csdn.net/abcdef314159 对于Integer这个类估计大家也都非常熟悉了,以前看过他的源码,但也只是粗略的看了一下,最近有时间 ...

最新文章

  1. java实现计分_非常实用的java自动答题计时计分器
  2. Facebook有1万名员工在研发AR/VR设备 占员工总数近1/5
  3. How is assignment blocks of overview pages read from configuration
  4. Leetcode题目:House Robber
  5. Oracle VM VirtualBox UUID already exists 问题解决
  6. 小波同态滤波 matlab,matlab同态滤波程序
  7. html英语单词不换行,css如何设置英文单词不换行?
  8. Android 删除文件app,手机删除文件怎么恢复?手机恢复删除App
  9. 基于SBO程序开发框架的实例:仓库扩展属性设置
  10. 计算机实习生听课记录,实习生听课记录
  11. 微信支付宝,个人支付收款接口现状剖析
  12. 8种开源的ETL工具
  13. cop2000实现补码两位乘
  14. Mysql优化_慢查询开启说明及Mysql慢查询分析工具mysqldumpslow用法讲解
  15. 【附源码】计算机毕业设计java制造型企业仓储管理系统设计与实现
  16. android连接otg摄像头,Android系统OTG_usb链接摄像头的驱动和使用
  17. 毛球修剪器方案开发的工作原理和构成
  18. 学python对数学要求吗_python 学习和数学知识 - 文章分类 - 风中小郎君 - 博客园...
  19. Linux系统的目录树
  20. 对接快递100快递管家API之物流信息推送接口

热门文章

  1. PHP Opcache(ZendOptimizerPlus)的安装配置详解
  2. Spring ApplicationListener 事件监听器,能监听容器中所有实例
  3. @Transactional什么情况才生效
  4. 小D课堂 - 零基础入门SpringBoot2.X到实战_第8节 数据库操作之整合Mybaties和事务讲解_33、SpringBoot2.x整合Mybatis3.x注解实战...
  5. 阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第3节 线程同步机制_4_解决线程安全问题_同步代码块...
  6. ActiveMQ学习笔记(1)----初识ActiveMQ
  7. 设计模式那点事读书笔记(3)----建造者模式
  8. 实现AJAX回传过程中禁止用户重复提交
  9. 滚动条----SCROLLBAR
  10. 物流管理系统(数据库+后台+springMVC+Mybatis+layui)(一)