listView实现下拉刷新,类似于微信朋友圈下拉刷新效果

本文介绍了一个自定义的Android ListView组件,支持下拉刷新功能。该组件通过改变头部视图的显示来指示不同的刷新状态,并提供了回调接口以便于外部实现刷新逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package com.telehot.quan.ui.view;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.telehot.quan.R;


/**
 * 下拉刷新的listview
 * @author 燕潇洒
 */
public class PullToRefreshListView extends ListView implements OnScrollListener {

   private static final int STATE_PULL_TO_REFRESH = 1;
   private static final int STATE_RELEASE_TO_REFRESH = 2;
   private static final int STATE_REFRESHING = 3;

   private int mCurrentState = STATE_PULL_TO_REFRESH;// 当前刷新状态

   private View mHeaderView;
   private int mHeaderViewHeight;
   private int startY = -1;

   private TextView tvTitle;
   private TextView tvTime;
   private ImageView ivArrow;

   private RotateAnimation animUp;
   private RotateAnimation animDown;
   private ProgressBar pbProgress;

   public PullToRefreshListView(Context context, AttributeSet attrs,
         int defStyle) {
      super(context, attrs, defStyle);
      initHeaderView();
//    initFooterView();
   }

   public PullToRefreshListView(Context context, AttributeSet attrs) {
      super(context, attrs);
      initHeaderView();
//    initFooterView();
   }

   public PullToRefreshListView(Context context) {
      super(context);
      initHeaderView();
//    initFooterView();
   }

   /**
    * 初始化头布局
    */
   private void initHeaderView() {
      mHeaderView = View.inflate(getContext(),
            R.layout.pull_to_refresh_header, null);
      this.addHeaderView(mHeaderView);

      tvTitle = (TextView) mHeaderView.findViewById(R.id.tv_title);
      tvTime = (TextView) mHeaderView.findViewById(R.id.tv_time);
      ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arrow);
      pbProgress = (ProgressBar) mHeaderView.findViewById(R.id.pb_loading);

      // 隐藏头布局
      mHeaderView.measure(0, 0);
      mHeaderViewHeight = mHeaderView.getMeasuredHeight();
      //燕潇洒,初始化时,不要隐藏这个高度,因为当前项目中首页的标题栏为自定义,正好挡住了头部的高度
//    mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
      mHeaderView.setPadding(0, 0, 0, 0);

      initAnim();
      setCurrentTime();
   }

   /**
    * 初始化脚布局
    */
   private void initFooterView() {
      mFooterView = View.inflate(getContext(),
            R.layout.pull_to_refresh_footer, null);
      this.addFooterView(mFooterView);

      mFooterView.measure(0, 0);
      mFooterViewHeight = mFooterView.getMeasuredHeight();

      mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);

      this.setOnScrollListener(this);// 滑动监听
   }

   // 设置刷新时间
   private void setCurrentTime() {
      SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      String time = format.format(new Date());

      tvTime.setText(time);
   }

   @Override
   public boolean onTouchEvent(MotionEvent ev) {
      switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
         startY = (int) ev.getY();
         break;

      case MotionEvent.ACTION_MOVE:
         if (startY == -1) {// 当用户按住头条新闻的viewpager进行下拉时,ACTION_DOWN会被viewpager消费掉,
                        // 导致startY没有赋值,此处需要重新获取一下
            startY = (int) ev.getY();
         }

         if (mCurrentState == STATE_REFRESHING) {
            // 如果是正在刷新, 跳出循环
            break;
         }

         int endY = (int) ev.getY();
         int dy = endY - startY;

         int firstVisiblePosition = getFirstVisiblePosition();// 当前显示的第一个item的位置

         // 必须下拉,并且当前显示的是第一个item
         if (dy > 0 && firstVisiblePosition == 0) {
            int padding = dy - mHeaderViewHeight;// 计算当前下拉控件的padding            mHeaderView.setPadding(0, padding, 0, 0);

            if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH) {
               // 改为松开刷新
               mCurrentState = STATE_RELEASE_TO_REFRESH;
               refreshState();
            } else if (padding < 0
                  && mCurrentState != STATE_PULL_TO_REFRESH) {
               // 改为下拉刷新
               mCurrentState = STATE_PULL_TO_REFRESH;
               refreshState();
            }

            return true;
         }
         break;

      case MotionEvent.ACTION_UP:
         startY = -1;

         if (mCurrentState == STATE_RELEASE_TO_REFRESH) {
            mCurrentState = STATE_REFRESHING;
            refreshState();

            // 完整展示头布局
            mHeaderView.setPadding(0, 0, 0, 0);

            // 4. 进行回调
            if (mListener != null) {
               mListener.onRefresh();
            }

         } else if (mCurrentState == STATE_PULL_TO_REFRESH) {
            // 隐藏头布局
//          mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
            mHeaderView.setPadding(0, 0, 0, 0);//变成0,解决向上滑动再回到顶部时,又被隐藏了一定的高度//燕潇洒
         }

         break;

      default:
         break;
      }

      return super.onTouchEvent(ev);
   }

   /**
    * 初始化箭头动画
    */
   private void initAnim() {
      animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
      animUp.setDuration(200);
      animUp.setFillAfter(true);

      animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF,
            0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
      animDown.setDuration(200);
      animDown.setFillAfter(true);
   }

   /**
    * 根据当前状态刷新界面
    */
   private void refreshState() {
      switch (mCurrentState) {
      case STATE_PULL_TO_REFRESH:
         tvTitle.setText("下拉刷新");
         pbProgress.setVisibility(View.INVISIBLE);
         ivArrow.setVisibility(View.VISIBLE);
         ivArrow.startAnimation(animDown);
         break;
      case STATE_RELEASE_TO_REFRESH:
         tvTitle.setText("松开刷新");
         pbProgress.setVisibility(View.INVISIBLE);
         ivArrow.setVisibility(View.VISIBLE);
         ivArrow.startAnimation(animUp);
         break;
      case STATE_REFRESHING:
         tvTitle.setText("正在刷新...");

         ivArrow.clearAnimation();// 清除箭头动画,否则无法隐藏

         pbProgress.setVisibility(View.VISIBLE);
         ivArrow.setVisibility(View.INVISIBLE);
         break;

      default:
         break;
      }
   }

   /**
    * 刷新结束,收起控件
    */
   public void onRefreshComplete(boolean success) {
      if(!isLoadMore) {
         mHeaderView.setPadding(0, 0, 0, 0);
//       mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);

         mCurrentState = STATE_PULL_TO_REFRESH;
         tvTitle.setText("下拉刷新");
         pbProgress.setVisibility(View.INVISIBLE);
         ivArrow.setVisibility(View.VISIBLE);

         if (success) {// 只有刷新成功之后才更新时间
            setCurrentTime();
         }
      }else {
         //加载更多
//       mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);//隐藏布局
         mFooterView.setPadding(0, 0, 0, 0);//隐藏布局
         isLoadMore = false;
      }
   }

   // 3. 定义成员变量,接收监听对象
   private OnRefreshListener mListener;
   private View mFooterView;
   private int mFooterViewHeight;

   /**
    * 2. 暴露接口,设置监听
    */
   public void setOnRefreshListener(OnRefreshListener listener) {
      mListener = listener;
   }

   /**
    * 1. 下拉刷新的回调接口
    * 
    * @author Kevin
    * @date 2015-10-21
    */
   public interface OnRefreshListener {
      public void onRefresh();
      
      //下拉加载更多
      public void onLoadMore();
   }

   private boolean isLoadMore;// 标记是否正在加载更多

   // 滑动状态发生变化
   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
      if (scrollState == SCROLL_STATE_IDLE) {// 空闲状态
         int lastVisiblePosition = getLastVisiblePosition();

         if (lastVisiblePosition == getCount() - 1 && !isLoadMore) {// 当前显示的是最后一个item并且没有正在加载更多
            // 到底了
            System.out.println("加载更多...");
            
            isLoadMore = true;

            mFooterView.setPadding(0, 0, 0, 0);// 显示加载更多的布局

            setSelection(getCount() - 1);// listview显示在最后一个item,
                                    // 从而加载更多会直接展示出来, 无需手动滑动
            
            //通知主界面加载下一页数据
            if(mListener!=null) {
               mListener.onLoadMore();
            }
         }
      }
   }

   // 滑动过程回调
   @Override
   public void onScroll(AbsListView view, int firstVisibleItem,
         int visibleItemCount, int totalItemCount) {

   }
}


上面的代码,由于我的标题栏是自己写的一个布局,所以如果是系统的标题栏的话,请将设置padding的方法,解绑,我已经将padding设置成了0,。下面是head和footor

//header布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/black"
    android:orientation="horizontal" >

    <FrameLayout
        android:layout_weight="1"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        >

        <ImageView
            android:visibility="gone"
            android:id="@+id/iv_arrow"
            android:layout_width="@dimen/dynamic_height5"
            android:layout_height="@dimen/dynamic_height5"
            android:layout_gravity="center"
            android:src="@mipmap/rainbow_ic" />
        <ProgressBar
            android:id="@+id/pb_loading"
            android:layout_gravity="center"
            android:layout_width="@dimen/dynamic_height5"
            android:layout_height="@dimen/dynamic_height5"
            android:indeterminateDrawable="@mipmap/rainbow_ic"
            android:background="@drawable/loadding_shape_layout"
            android:layout_centerInParent="true"
            android:visibility="invisible" />
    </FrameLayout>

    <LinearLayout
        android:visibility="gone"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉刷新"
            android:textColor="@color/red"
            android:textSize="@dimen/item_text_size" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/dynamic_margin"
            android:text="2015-10-21 09:00"
            android:textColor="@color/black"
            android:textSize="@dimen/item_text_size" />
    </LinearLayout>

</LinearLayout>
footot布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:background="@color/black"
    android:orientation="horizontal" >

    <ProgressBar
        android:id="@+id/pb_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:indeterminateDrawable="@mipmap/rainbow_ic" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:text="加载中..."
        android:textColor="#f00"
        android:textSize="18sp" />

</LinearLayout>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值