zoukankan      html  css  js  c++  java
  • android 自定义控件之下拉刷新源码详解

    下拉刷新 是请求网络数据中经常会用的一种功能.
    实现步骤如下:
    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);
        }
    }
    就这样,一个完整的下拉刷新.






    qq3061280@163.com
  • 相关阅读:
    1.2 流程控制
    SpringMVC-第一个MVC程序的搭建需要
    用户与权限
    自定义函数和存储过程
    触发器
    事务
    约束
    视图和索引
    函数二
    函数一
  • 原文地址:https://www.cnblogs.com/aibuli/p/8ebf33bdc624ff23b4a65c1831fdc336.html
Copyright © 2011-2022 走看看