首页 > 程序开发 > 移动开发 > 其他 >

自定义ListView和SwipeRefreshLayout实现上拉加载和下拉刷新

2017-01-23

自定义ListView和SwipeRefreshLayout实现上拉加载和下拉刷新:自定义ListView现在早已不是新鲜的了,这里也是作为一个归类罢了。

自定义ListView和SwipeRefreshLayout实现上拉加载和下拉刷新:自定义ListView现在早已不是新鲜的了,这里也是作为一个归类罢了。

直接上代码:

/**
 * 自定义ListView实现下拉刷新和上拉加载更多的界面效果,并设有刷新完成监听
 * */
public class RefreshListView extends ListView {

   private int downY = -1;
   private int headerMeasureHeight;
   private View header;

   private static final int PULLDOWN_STATE = 0;// 下拉刷新状态
   private static final int RELEASE_STATE = 1;// 松开刷新状态
   private static final int REFRESHING_STATE = 2;// 正在刷新状态
   private int CURREN_STATE = PULLDOWN_STATE;

   private ImageView  iv_refresh_arrow;
   private ProgressBar pb_refresh_progress;
   private TextView   tv_refresh_state;
   private TextView   tv_refresh_time;
   private RotateAnimation up;
   private RotateAnimation down;
   private MyrefreshLinstener mListener;// 记录外界传递进来的监听器
   private View footer;
   private int footerMeasuredHeight;
   private int itermAccount;

   public RefreshListView(Context context, AttributeSet attrs) {
      super(context, attrs);
      initHeader();
      iniAnimation();
      initFooter();
   }

   
   private void initFooter() {
      footer = View.inflate(getContext(), R.layout.refresh_footer, null);
      // 隐藏脚布局
      footer.measure(0, 0);
      footerMeasuredHeight = footer.getMeasuredHeight();
      footer.setPadding(0, -footerMeasuredHeight, 0, 0);
      this.addFooterView(footer);
      // 监听Listview滚动状态
      this.setOnScrollListener(new MyOnScrollListener());
   }

   private void iniAnimation() {
      up = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
      up.setDuration(300);
      up.setFillAfter(true); //补间动画设置最后图像位置为运动最后位置处

      down = new RotateAnimation(-180, -360, Animation.RELATIVE_TO_SELF,
            0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
      down.setDuration(300);
      down.setFillAfter(true);
   }

   private void initHeader() {
      // 添加下拉刷新头
      header = View.inflate(getContext(), R.layout.refresh_header, null);
      iv_refresh_arrow = (ImageView) header.findViewById(R.id.iv_refresh_arrow);
      tv_refresh_state = (TextView) header.findViewById(R.id.tv_refresh_state);
      tv_refresh_time = (TextView) header.findViewById(R.id.tv_refresh_time);
      pb_refresh_progress = (ProgressBar) header.findViewById(R.id.pb_refresh_progress);

      // 隐藏头布局
      // 获取测量高度
      header.measure(0, 0);
      headerMeasureHeight = header.getMeasuredHeight();
      header.setPadding(0, -headerMeasureHeight, 0, 0);
      this.addHeaderView(header);
   }

   @Override
   public boolean onTouchEvent(MotionEvent ev) {
      if (mListener==null) {
         try {
            throw new IllegalAccessException("the RefreshListView's MyrefreshLinstener hasn't been created yet! ");
         } catch (IllegalAccessException e) {
            e.printStackTrace();
         }
         return false;
      }

      switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
         downY = (int) ev.getY();
         break;
      case MotionEvent.ACTION_MOVE:
         // 当按到轮播图控件时,获取不到down事件,在move事件给他赋值
         if (downY == -1) {
            downY = (int) ev.getY();
         }

         int moveY = (int) ev.getY();

         int diffY = moveY - downY;
         //有轮播图时,当滑动到轮播图完全展示时,才显示下拉刷新条目,0位置就是刷新条目
         if (getFirstVisiblePosition() != 0) {
            break;
         }

         // 如果已经处于正在刷新,不能再刷新了
         if (CURREN_STATE == REFRESHING_STATE) {
            break;
         }

         // 只处理手指下拉滑动,防止数据过少,上拉滑动时也出现刷新情况
         if (diffY > 0) {
            // 计算头布局的toppadding值
            // topadding = 手指移动的距离 - 头布局的高度
            int topadding = diffY - headerMeasureHeight;
            // 根据topadding值是否大于0判断状态切换
            if (topadding < 0 && CURREN_STATE != PULLDOWN_STATE) {// 头布局没有完全显示,切换到下拉刷新状态
               CURREN_STATE = PULLDOWN_STATE;
               switchState(CURREN_STATE);
            } else if (topadding > 0 && CURREN_STATE != RELEASE_STATE) {// 头布局已经完全显示,切换到松开刷新状态
               CURREN_STATE = RELEASE_STATE;
               switchState(CURREN_STATE);
            }

            // 设置头布局的padding,达到移动的效果
            header.setPadding(0, topadding > headerMeasureHeight? headerMeasureHeight:topadding, 0, 0);
            return true;// 自己消费掉事件
         }

         break;
      case MotionEvent.ACTION_UP:
         // 为了不影响下一次down,downY初始化为-1
         downY = -1;

         // 手指抬起时,根据当前状态判断是否切换到正在刷新状态
         if (CURREN_STATE == PULLDOWN_STATE) {
            // 当前处于下拉状态,不切到正在刷新,需要把头布局隐藏
            header.setPadding(0, -headerMeasureHeight, 0, 0);
         } else if (CURREN_STATE == RELEASE_STATE) {
            // 当前处于松开状态,需要切到正在刷新,把头布局设置成刚好完全展示
            CURREN_STATE = REFRESHING_STATE;
            switchState(CURREN_STATE);
            header.setPadding(0, 0, 0, 0);
            // 当处于下拉刷新状态时,调用外界传进来的监听器的真正的业务
            if (mListener != null) {
               mListener.onRefreshing();
            }
         }
         break;

      default:
         break;
      }
      return super.onTouchEvent(ev);
   }

   // 根据状态改变头布局控件
   private void switchState(int state) {
      switch (state) {
      case PULLDOWN_STATE:
         tv_refresh_state.setText("下拉刷新");
         iv_refresh_arrow.setVisibility(View.VISIBLE);
         pb_refresh_progress.setVisibility(View.INVISIBLE);// 不能用gone
         iv_refresh_arrow.startAnimation(up);
         break;
      case RELEASE_STATE:
         tv_refresh_state.setText("松开刷新");
         iv_refresh_arrow.startAnimation(down);
         break;
      case REFRESHING_STATE:
         // 清除箭头的动画
         iv_refresh_arrow.clearAnimation();
         tv_refresh_state.setText("正在刷新");
         iv_refresh_arrow.setVisibility(View.INVISIBLE);
         pb_refresh_progress.setVisibility(View.VISIBLE);
         break;

      default:
         break;
      }
   }

   // 恢复下拉刷新控件的状态,什么时候刷新完成,由外部请求数据情况决定的,调用方法,完成刷新
   public void refreshFinished(boolean success) {
      tv_refresh_state.setText("下拉刷新");
      iv_refresh_arrow.setVisibility(View.VISIBLE);
      pb_refresh_progress.setVisibility(View.INVISIBLE);// 不能用gone
      CURREN_STATE = PULLDOWN_STATE;
      header.setPadding(0, -headerMeasureHeight, 0, 0);
      if (success) {
         SimpleDateFormat format = new SimpleDateFormat(
               "yyyy-MM-dd HH:mm:ss");
         String time = format.format(new Date());
         tv_refresh_time.setText("最后刷新时间:" + time);
      } else {
         Toast.makeText(getContext(), "亲,网络出问题了", Toast.LENGTH_SHORT).show();
      }
   }
   
   // 恢复加载更多状态
   public void loadMoreFinished(){
      footer.setPadding(0, -footerMeasuredHeight, 0, 0);
      isLoadMore = false;
      this.setSelection(itermAccount);
   }

   // 对外暴露接口
   public interface MyrefreshLinstener {
      // 下拉刷新方法
      void onRefreshing();
      // 加载更多方法
      void onLoadMore();
   }

   // 让外界传递监听器,这是让外界来补全方法,用于本来调用的,而不是由
   //外界调用本类方法,来完成它类的功能的,二者功能要区分开
   public void setMyrefreshLinstener(MyrefreshLinstener linstener) {
      this.mListener = linstener;
   }
   private boolean isLoadMore = false;// 当前是否处于加载更多中

   class MyOnScrollListener implements OnScrollListener {

      @Override
      public void onScrollStateChanged(AbsListView view, int scrollState) {
         // 当处于停止或惯性停止状态时,且Listview展示的条目最后一条数据,显示加载更多布局
         if (OnScrollListener.SCROLL_STATE_IDLE == scrollState
               || OnScrollListener.SCROLL_STATE_FLING == scrollState) {
            itermAccount = getCount() - 1;
            if(getLastVisiblePosition()== itermAccount && !isLoadMore){
               isLoadMore = true;
               footer.setPadding(0, 0, 0, 0);
               // 当处于加载更多时,调用外界的监听器的业务
               if(mListener!=null){
                  mListener.onLoadMore();
               }
            }
         }
      }

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

再让我们看下SwipeRefreshLayout实现上拉加载和下拉刷新,但只能内部只能用ListView.

/**
 * 该类现只适合于SwipRefreshLayout中放ListView的情况,实现上下拉刷新操作
 */
public class MySwipRefreshListLayout extends SwipeRefreshLayout {
    private int mScaledTouchSlop;
    private View mFooterView;
    private ListView mListView;
    private OnLoadListener mOnLoadListener;

    public MySwipRefreshListLayout(Context context) {
        super(context);
    }

    public MySwipRefreshListLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 填充底部加载布局
        mFooterView = View.inflate(context, R.layout.view_footer, null);
        // 表示控件移动的最小距离,手移动的距离大于这个距离才能拖动控件
        mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    /**
     * 正在加载状态
     */
    private boolean isLoading;

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        // 获取ListView,设置ListView的布局位置
        if (mListView == null) {
            // 判断容器有多少个孩子
            if (getChildCount() > 0) {
                // 判断第一个孩子是不是ListView
                if (getChildAt(0) instanceof ListView) {
                    // 创建ListView对象
                    mListView = (ListView) getChildAt(0);
                    // 设置ListView的滑动监听
                    setListViewOnScroll();
                }
            }
        }
    }

    /**
     * 在分发事件的时候处理子控件的触摸事件
     *
     * @param ev
     * @return
     */
    private float mDownY, mUpY;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 移动的起点
                mDownY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                // 移动过程中判断时候能下拉加载更多
                if (canLoadMore()) {
                    // 加载数据
                    loadData();
                }

                break;
            case MotionEvent.ACTION_UP:
                // 移动的终点
                mUpY = getY();
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 判断是否满足加载更多条件
     *
     * @return
     */
    private boolean canLoadMore() {
        // 1. 是上拉状态
        boolean condition1 = (mDownY - mUpY) >= mScaledTouchSlop;
        // 2. 当前页面可见的item是最后一个条目
        boolean condition2 = false;
        if (mListView != null && mListView.getAdapter() != null) {
            condition2 = mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1);
        }
        // 3. 正在加载状态
        boolean condition3 = !isLoading;
        return condition1 && condition2 && condition3;
    }

    /**
     * 处理加载数据的逻辑
     */
    private void loadData() {
        if (mOnLoadListener != null) {
            // 设置加载状态,让布局显示出来
            setLoading(true);
            mOnLoadListener.onLoad();
        }
    }

    /**
     * 设置加载状态,是否加载传入boolean值进行判断
     *
     * @param loading
     */
    public void setLoading(boolean loading) {
        // 修改当前的状态
        isLoading = loading;
        if (isLoading) {
            // 显示布局
            mListView.addFooterView(mFooterView);
        } else {
            // 隐藏布局
            mListView.removeFooterView(mFooterView);
            // 重置滑动的坐标
            mDownY = 0;
            mUpY = 0;
        }
    }


    /**
     * 设置ListView的滑动监听
     */
    private void setListViewOnScroll() {

        mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // 移动过程中判断时候能上拉加载更多
                if (canLoadMore()) {
                    // 加载数据
                    loadData();
                }
            }

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

    public void loadMoreFinish(){
        setLoading(false);
    }
    /**
     * 上拉加载的接口回调
     */

    public interface OnLoadListener {
        void onLoad();
    }

    public void setOnLoadMoreListener(OnLoadListener listener) {
        this.mOnLoadListener = listener;
    }
}

布局代码:




    
    

        

    


关键的解释在注释中都有,有感兴趣的童鞋可以自己调整升级一下。

相关文章
最新文章
热点推荐