ListView刷新的实现(加上自己的分析)

来源:互联网 发布:金盾加密软件授权 编辑:程序博客网 时间:2024/06/02 19:42

本文转载地址为http://blog.csdn.net/guolin_blog/article/details/9255575

这里我们将采取的方案是使用组合View的方式,先自定义一个布局继承自LinearLayout,然后在这个布局中加入下拉头和ListView这两个子元素,并让这两个子元素纵向排列。初始化的时候,让下拉头向上偏移出屏幕,这样我们看到的就只有ListView了。然后对ListView的touch事件进行监听,如果当前ListView已经滚动到顶部并且手指还在向下拉的话,那就将下拉头显示出来,松手后进行刷新操作,并将下拉头隐藏。原理示意图如下:

                            

那我们现在就来动手实现一下,新建一个项目起名叫PullToRefreshTest,先在项目中定义一个下拉头的布局文件pull_to_refresh.xml,代码如下所示:

在这个布局中,我们包含了一个下拉指示箭头,一个下拉状态文字提示,和一个上次更新的时间。当然,还有一个隐藏的旋转进度条,只有正在刷新的时候我们才会将它显示出来。

[html] view plaincopy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/pull_to_refresh_head"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="60dip" >  
  6.   
  7.     <LinearLayout  
  8.         android:layout_width="200dip"  
  9.         android:layout_height="60dip"  
  10.         android:layout_centerInParent="true"  
  11.         android:orientation="horizontal" >  
  12.   
  13.         <RelativeLayout  
  14.             android:layout_width="0dip"  
  15.             android:layout_height="60dip"  
  16.             android:layout_weight="3"  
  17.             >  
  18.             <ImageView   
  19.                 android:id="@+id/arrow"  
  20.                 android:layout_width="wrap_content"  
  21.                 android:layout_height="wrap_content"  
  22.                 android:layout_centerInParent="true"  
  23.                 android:src="@drawable/arrow"  
  24.                 />  
  25.             <ProgressBar   
  26.                 android:id="@+id/progress_bar"  
  27.                 android:layout_width="30dip"  
  28.                 android:layout_height="30dip"  
  29.                 android:layout_centerInParent="true"  
  30.                 android:visibility="gone"  
  31.                 />  
  32.         </RelativeLayout>  
  33.   
  34.         <LinearLayout  
  35.             android:layout_width="0dip"  
  36.             android:layout_height="60dip"  
  37.             android:layout_weight="12"  
  38.             android:orientation="vertical" >  
  39.   
  40.             <TextView  
  41.                 android:id="@+id/description"  
  42.                 android:layout_width="fill_parent"  
  43.                 android:layout_height="0dip"  
  44.                 android:layout_weight="1"  
  45.                 android:gravity="center_horizontal|bottom"  
  46.                 android:text="@string/pull_to_refresh" />  
  47.   
  48.             <TextView  
  49.                 android:id="@+id/updated_at"  
  50.                 android:layout_width="fill_parent"  
  51.                 android:layout_height="0dip"  
  52.                 android:layout_weight="1"  
  53.                 android:gravity="center_horizontal|top"  
  54.                 android:text="@string/updated_at" />  
  55.         </LinearLayout>  
  56.     </LinearLayout>  
  57.   
  58. </RelativeLayout>  
首先
private void initView(Context context) {        preferences = PreferenceManager.getDefaultSharedPreferences(context);        header = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh, null , true);        progressBar = (ProgressBar) header.findViewById(R.id.progress_bar);        arrow = (ImageView) header.findViewById(R.id.arrow);        description = (TextView) header.findViewById(R.id.description);        updateAt = (TextView) header.findViewById(R.id.updated_at);        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();        refreshUpdatedAtValue();        setOrientation(VERTICAL);        addView(header, 0);    }
初始化一些药用到的控件,再Layout:
 @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 = (LayoutParams)header.getLayoutParams();
            headerLayoutParams.topMargin = hideHeaderHeight;
            listView = (ListView) getChildAt(1);
            listView.setOnTouchListener(this);
            loadOnce = true;
        }
    }
在这里吧Header给隐藏掉。 默认要在布局中加上一个ListView
然后在listView的onTouch事件中做详细处理:
首先要判断是否需要下拉
/** * 根据当前ListView的滚动状态来设定 {@link #ableToPull} * 的值,每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。 *  * @param event */private void setIsAbleToPull(MotionEvent event) {View firstChild = listView.getChildAt(0);if (firstChild != null) {int firstVisiblePos = listView.getFirstVisiblePosition();if (firstVisiblePos == 0 && firstChild.getTop() == 0) {if (!ableToPull) {yDown = event.getRawY();}// 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新ableToPull = true;} else {if (headerLayoutParams.topMargin != hideHeaderHeight) {headerLayoutParams.topMargin = hideHeaderHeight;header.setLayoutParams(headerLayoutParams);}ableToPull = false;}} else {// 如果ListView中没有元素,也应该允许下拉刷新ableToPull = true;}}
在这里只有2中情况下是可以下拉的
1. listView中没有任何的元素也是就 
firstChild  == null
2. 第一个可见位置为0,并且第一个view的相对于父控件的top是0,那么就可以下拉
如果不可以下拉的话,就不做任何的处理。
然后如果不是正在刷新状态的话,就进行判断是 正在下来 还是 释放就可以刷新<pre name="code" class="java">if (currentStatus != STATUS_REFRESHING) {if (headerLayoutParams.topMargin > 0) {currentStatus = STATUS_RELEASE_TO_REFRESH;} else {currentStatus = STATUS_PULL_TO_REFRESH;}// 通过偏移下拉头的topMargin值,来实现下拉效果headerLayoutParams.topMargin = (distance / 2) + hideHeaderHeight;header.setLayoutParams(headerLayoutParams);}

释放后根据这2中状态进行不同的操作:
if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
// 松手时如果是释放立即刷新状态,就去调用正在刷新的任务
new RefreshingTask().execute();
} else if (currentStatus == STATUS_PULL_TO_REFRESH) {
// 松手时如果是下拉状态,就去调用隐藏下拉头的任务
new HideHeaderTask().execute();
}
HideHeaderTask() 只是隐藏,因为下来的距离不够
RefreshingTask() 这个才是刷新,
在刷新的的task中,先把整个头在顶部显示出来
再多用用户 刷新方法
if (mListener != null) {
mListener.onRefresh();
}
在原文中,需要在onRefresh()中调用 finishRefreshing的方法,这个需要依靠用户自己调用!个人觉得在onRefresh完成后自己调用 finishRefreshing(),如果没有设置的话,那就更加不你更新状态了
改为:
<span style="white-space:pre"></span>if (mListener != null) {mListener.onRefresh();}finishRefreshing();


全部源码:
public class PullFreshView extends LinearLayout implements OnTouchListener{
    /** 
     * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次 
     */  
    private boolean loadOnce; 
    
    /**
     * 用于存储上次更新时间
     */
    private SharedPreferences preferences;
    
    /**
     * 下拉头的View
     */
    private View header;
    
    /**
     * 刷新时显示的进度条
     */
    private ProgressBar progressBar;


    /**
     * 指示下拉和释放的箭头
     */
    private ImageView arrow;


    /**
     * 指示下拉和释放的文字描述
     */
    private TextView description;


    /**
     * 上次更新时间的文字描述
     */
    private TextView updateAt;
    
    /**
     * 在被判定为滚动之前用户手指可以移动的最大值。
     */
    private int touchSlop;
    
    /**
     * 上次更新时间的毫秒值
     */
    private long lastUpdateTime;
    
    /**
    * 上次更新时间的字符串常量,用于作为SharedPreferences的键值
    */
   private static final String UPDATED_AT = "updated_at";
   
   /**
    * 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突,使用id来做区分
    */
   private int mId = -1;
   
   /**
    * 一分钟的毫秒值,用于判断上次的更新时间
    */
   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;
   
   /**
    * 下拉头的高度
    */
   private int hideHeaderHeight;
   
   /**
    * 需要去下拉刷新的ListView
    */
   private ListView listView;
   
   /**
    * 当前是否可以下拉,只有ListView滚动到头的时候才允许下拉
    * 
    */
   private boolean ableToPull;
   
   /**
    * 手指按下时的屏幕纵坐标
    */
   private float yDown;
   
  /**
   * 下拉头的布局参数
   */
  private MarginLayoutParams headerLayoutParams;
  
  /**
   * 当前处理什么状态,可选值有STATUS_PULL_TO_REFRESH, STATUS_RELEASE_TO_REFRESH,
   * STATUS_REFRESHING 和 STATUS_REFRESH_FINISHED
   */
  private int currentStatus = STATUS_REFRESH_FINISHED;
  /**
   * 下拉状态
   */
  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_FINISHED = 3;
  
  /**
   * 下拉头部回滚的速度
   */
  public static final int SCROLL_SPEED = -20;
  
  /**
   * 下拉刷新的回调接口
   */
  private PullToRefreshListener mListener;
  
  /**
   * 记录上一次的状态是什么,避免进行重复操作
   */
  private int lastStatus = currentStatus;


    
    public PullFreshView(Context context) {
        this(context , null);
    }
    
    public PullFreshView(Context context, AttributeSet attrs) {
        this(context , attrs , 0);
    }
    
    @SuppressLint("NewApi")
    public PullFreshView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView(context);
    }


    private void initView(Context context) {
        preferences = PreferenceManager.getDefaultSharedPreferences(context);
        header = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh, null , true);
        progressBar = (ProgressBar) header.findViewById(R.id.progress_bar);
        arrow = (ImageView) header.findViewById(R.id.arrow);
        description = (TextView) header.findViewById(R.id.description);
        updateAt = (TextView) header.findViewById(R.id.updated_at);
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        refreshUpdatedAtValue();
        setOrientation(VERTICAL);
        addView(header, 0);
    }


    /**
     * 刷新下拉头中上次更新时间的文字描述。
     */
    private void refreshUpdatedAtValue() {
        lastUpdateTime = preferences.getLong(UPDATED_AT + mId, -1);
        long currentTime = System.currentTimeMillis();
        long timePassed = currentTime - lastUpdateTime;
        long timeIntoFormat;
        String updateAtValue;
        if (lastUpdateTime == -1) {
            updateAtValue = getResources().getString(R.string.not_updated_yet);
        } else if (timePassed < 0) {
            updateAtValue = getResources().getString(R.string.time_error);
        } else if (timePassed < ONE_MINUTE) {
            updateAtValue = getResources().getString(R.string.updated_just_now);
        } else if (timePassed < ONE_HOUR) {
            timeIntoFormat = timePassed / ONE_MINUTE;
            String value = timeIntoFormat + "分钟";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_DAY) {
            timeIntoFormat = timePassed / ONE_HOUR;
            String value = timeIntoFormat + "小时";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_MONTH) {
            timeIntoFormat = timePassed / ONE_DAY;
            String value = timeIntoFormat + "天";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_YEAR) {
            timeIntoFormat = timePassed / ONE_MONTH;
            String value = timeIntoFormat + "个月";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else {
            timeIntoFormat = timePassed / ONE_YEAR;
            String value = timeIntoFormat + "年";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        }
        updateAt.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;
                        }
                     // 通过偏移下拉头的topMargin值,来实现下拉效果
                        headerLayoutParams.topMargin = (distance / 2) + hideHeaderHeight;
                        header.setLayoutParams(headerLayoutParams);
                    }
                    
                    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失去焦点,否则被点击的那一项会一直处于选中状态
                listView.setPressed(false);
                listView.setFocusable(false);
                listView.setFocusableInTouchMode(false);
                lastStatus = currentStatus;
                // 当前正处于下拉或释放状态,通过返回true屏蔽掉ListView的滚动事件
                return true;
            }
        }
        return false;
    }
    


    /**
     * 根据当前ListView的滚动状态来设定 {@link #ableToPull}
     * 的值,每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。
     * 
     * @param event
     */
    private void setIsAbleToPull(MotionEvent event) {
        View firstChild = listView.getChildAt(0);   
        if (null != firstChild){
            int firstPos = listView.getFirstVisiblePosition();
            if (firstPos == 0 && firstChild.getTop() == 0){
                if (!ableToPull){
                    yDown = event.getRawY();
                }
             // 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新
                ableToPull = true;
            }else{
                if (headerLayoutParams.topMargin != hideHeaderHeight) {
                    headerLayoutParams.topMargin = hideHeaderHeight;
                    header.setLayoutParams(headerLayoutParams);
                }
                ableToPull = false;
            }
        }else{
            // 如果listView中没有元素的话是可以下拉的
            ableToPull = true;
        }
    }


    @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 = (LayoutParams)header.getLayoutParams();
            headerLayoutParams.topMargin = hideHeaderHeight;
            listView = (ListView) getChildAt(1);
            listView.setOnTouchListener(this);
            loadOnce = true;
        }
    }
    
    public class HideHeaderTask extends AsyncTask<Void, Integer, Integer>{


        @Override
        protected Integer doInBackground(Void... params) {
            int topMargin = headerLayoutParams.topMargin;
            while(true){
                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_FINISHED;
        }
        
    }
    
    /**
     * 正在刷新的任务,在此任务中会去回调注册进来的下拉刷新监听器。
     * 
     * @author guolin
     */
    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);
        }


    }
    
    /**
     * 更新下拉头中的信息。
     */
    private void updateHeaderView() {
        if (lastStatus != currentStatus) {
            if (currentStatus == STATUS_PULL_TO_REFRESH) {
                description.setText(getResources().getString(R.string.pull_to_refresh));
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                rotateArrow();
            } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
                description.setText(getResources().getString(R.string.release_to_refresh));
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                rotateArrow();
            } else if (currentStatus == STATUS_REFRESHING) {
                description.setText(getResources().getString(R.string.refreshing));
                progressBar.setVisibility(View.VISIBLE);
                arrow.clearAnimation();
                arrow.setVisibility(View.GONE);
            }
            refreshUpdatedAtValue();
        }
    }
    
    /**
     * 根据当前的状态来旋转箭头。
     */
    private void rotateArrow() {
        float pivotX = arrow.getWidth() / 2f;
        float pivotY = arrow.getHeight() / 2f;
        float fromDegrees = 0f;
        float toDegrees = 0f;
        if (currentStatus == STATUS_PULL_TO_REFRESH) {
            fromDegrees = 180f;
            toDegrees = 360f;
        } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
            fromDegrees = 0f;
            toDegrees = 180f;
        }
        RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
        animation.setDuration(100);
        animation.setFillAfter(true);
        arrow.startAnimation(animation);
    }
    
    /**
     * 使当前线程睡眠指定的毫秒数。
     * 
     * @param time
     *            指定当前线程睡眠多久,以毫秒为单位
     */
    private void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 下拉刷新的监听器,使用下拉刷新的地方应该注册此监听器来获取刷新回调。
     * 
     * @author guolin
     */
    public interface PullToRefreshListener {


        /**
         * 刷新时会去回调此方法,在方法内编写具体的刷新逻辑。注意此方法是在子线程中调用的, 你可以不必另开线程来进行耗时操作。
         */
        void onRefresh();


    }
    
    /**
     * 给下拉刷新控件注册一个监听器。
     * 
     * @param listener
     *            监听器的实现。
     * @param id
     *            为了防止不同界面的下拉刷新在上次更新时间上互相有冲突, 请不同界面在注册下拉刷新监听器时一定要传入不同的id。
     */
    public void setOnRefreshListener(PullToRefreshListener listener, int id) {
        mListener = listener;
        mId = id;
    }
    
    /**
     * 当所有的刷新逻辑完成后,记录调用一下,否则你的ListView将一直处于正在刷新状态。
     */
    public void finishRefreshing() {
        currentStatus = STATUS_REFRESH_FINISHED;
        preferences.edit().putLong(UPDATED_AT + mId, System.currentTimeMillis()).commit();
        new HideHeaderTask().execute();
    }


}

Activity主要代码:
 ArrayAdapter<String> adapter;
    String[] items = { "A", "B", "C", "D", "E", "F", "G", "H", "I", 
            "J", "K", "L","M" ,"N","O" ,"P" ,"Q","R","S","T","U","V","W","X","Y","Z"};
 adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
 listView2.setAdapter(adapter);
        freshView = (PullFreshView)findViewById(R.id.pull_fresh_view);
        freshView.setOnRefreshListener(new PullFreshView.PullToRefreshListener() {
            
            @Override
            public void onRefresh() {
                // 做一些事情
                try {
                    Thread.sleep(3000);
                    items[0] = "AAAAAAAAA";
                    mHandler.sendEmptyMessage(MSG_FRESH_ADATER);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                freshView.finishRefreshing();
            }
        }, 2);
0 0
原创粉丝点击