下拉刷新ScrollView

来源:互联网 发布:淘宝关键词重复 编辑:程序博客网 时间:2024/05/22 06:13

需求

昨天在网上找了好久都没有找到一个合适的可以下拉刷新的scrollView代码,不过github上有一个开源项目(Android-PullToRefresh)写得很不错,但好像必须是AbsListView的子类才能使用,当然自己写的这个和该项目的实现原理是一样的。想想自己学Android已经快一年了,总得努力努力自己搞搞,所以今天上午就查阅资料看博客开始写,下面就贴贴自己的思路和代码和下载地址。如果喜欢请star,如果觉得有纰漏或是有更好的点子请及时评论。

实现

咋们还是按思路一步一步往下走..

1.需要编写头部文件和布局,这个人们都知道,继承RelativeLayout,然后提供三个设置状态的方法,这里就不粘代码了,有需要的可以下载我的源码,下面开始干重要活…

2.extends ScrollView,将头部View,加到容器布局,设置头部的magrin值将头部headView隐藏

    public class RefreshScrollView extends ScrollView {    private Context mContext;    // 容器布局,因为scroll只允许嵌套一个子布局    private LinearLayout mScrollContainer = null;    // 头部刷新的View    private ScrollViewHeader mRefreshHeaderView;    // 头部的高度    private int mHeaderViewHeight;    private final static float OFFSET_RADIO = 2.2f; // support iOS like pull    public CopyOfRefreshScrollView(Context context) {        this(context, null);    }    public CopyOfRefreshScrollView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public CopyOfRefreshScrollView(Context context, AttributeSet attrs,            int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.mContext = context;        initView();      }    /**     * 初始化view     */    private void initView() {        // 添加头部布局到容器        mRefreshHeaderView = new ScrollViewHeader(mContext);        LinearLayout.LayoutParams headerViewParams = new LinearLayout.LayoutParams(                LinearLayout.LayoutParams.MATCH_PARENT,                LinearLayout.LayoutParams.WRAP_CONTENT);        mScrollContainer = new LinearLayout(mContext);        mScrollContainer.addView(mRefreshHeaderView, headerViewParams);        mScrollContainer.setOrientation(LinearLayout.VERTICAL);        addView(mScrollContainer);        // 必须要测量一下头部view,要不然getMeasuredHeight()是0,        // 当然mRefreshHeaderView.getViewTreeObserver().addOnGlobalLayoutListener也可以        measureView(mRefreshHeaderView);        // 获取头部刷新headView的高度,设置margin让头部隐藏        mHeaderViewHeight = mRefreshHeaderView.getMeasuredHeight();        mRefreshHeaderView                .updateMargin(-mRefreshHeaderView.getMeasuredHeight());    }    /**     * 通知父布局,占用的宽,高     *      * @param view     */    private void measureView(View view) {        ViewGroup.LayoutParams p = view.getLayoutParams();        if (p == null) {            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                    ViewGroup.LayoutParams.WRAP_CONTENT);        }        int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);        int height;        int tempHeight = p.height;        if (tempHeight > 0) {            height = MeasureSpec.makeMeasureSpec(tempHeight,                    MeasureSpec.EXACTLY);        } else {            height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);        }        view.measure(width, height);    }

3.这个时候我们可以新建一个activity试试看效果了,但一运行起来果断报异常了:java.lang.IllegalStateException:ScrollView can host only one direct child,这个没办法了总得解决一下吧。那么就新建一个方法setContainerView(View child) 吧?那如果非要在布局文件里面添加呢怎么办呢?所以只能够阅读源码了,发现scrollView在加载仅有的一个childView的时候会调用addView(View child, android.view.ViewGroup.LayoutParams params)方法,这下好了只要override这个方法就可以了,再次运行爽了。

    /**     * 其他的addView 的方法也要override,暂且放一边     */    @Override    public void addView(View child, android.view.ViewGroup.LayoutParams params) {        // 2.重载addView(View child, android.view.ViewGroup.LayoutParams params)方法        // 解决 java.lang.IllegalStateException        // 因为scrollView只许添加一个子布局,如果在xml中添加子布局,那么肯定会throw        // java.lang.IllegalStateException:ScrollView can host only one direct child        this.removeAllViews();        mScrollContainer.addView(child, params);        super.addView(mScrollContainer, mScrollContainer.getLayoutParams());    }

4.处理最重要的方法onTouchEvent的触摸事件

    // 4.处理触摸事件,override onTouchEvent    @Override    public boolean onTouchEvent(MotionEvent ev) {        if (mLastY == -1) {            mLastY = ev.getRawY();        }        switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            mLastY = ev.getRawY();            break;        case MotionEvent.ACTION_MOVE:            final float deltaY = ev.getRawY() - mLastY;            mLastY = ev.getRawY();            if(deltaY < 0 && mRefreshing){                // 如果往上滑并且是刷新的状态就不除阻力,要不然当头部出来的时候向上滑动怪怪的                updateHeader(deltaY);            }else if (getScrollY() == 0                && (deltaY > 0 || mRefreshHeaderView.getTopMargin() > -mHeaderViewHeight)) {                // 更新headerView的高度,同时更改状态                 updateHeader(deltaY/OFFSET_RADIO);                return true;            }            break;        default:              //这里没有使用action_up的原因是,可能会受到viewpager的影响接收到action_cacel事件              if (getScrollY() == 0) {                  if (mRefreshHeaderView.getTopMargin() > 0 && mEnableRefresh && !mRefreshing)                 {                      mRefreshing = true;                      mRefreshHeaderView.setState(ScrollViewHeader.STATE_REFRESHING);                    // 刷新加载中,给调用者监听                      if(mListener!=null){                        mListener.onRefresh();                    }                }                 //重置RefreshHeaderView的高度                 resetHeaderView();              }              break;        }        return super.onTouchEvent(ev);    }    /**      * 更新headerview的高度,同时更改状态      * @param deltY      */      public void updateHeader(float deltY) {          int currentMargin = (int) (mRefreshHeaderView.getTopMargin() + deltY);          mRefreshHeaderView.updateMargin(currentMargin);          if(mEnableRefresh && !mRefreshing) {            if (currentMargin > mHeaderViewHeight/5) {                 // 头部全部出来了,就显示松开刷新                mRefreshHeaderView.setState(ScrollViewHeader.STATE_READY);              } else {                 // 否则显示下拉加载更多                mRefreshHeaderView.setState(ScrollViewHeader.STATE_NORMAL);              }          }      }      /**      * 重置RefreshHeaderView的高度      */      public void resetHeaderView() {        int margin = mRefreshHeaderView.getTopMargin();         if(margin == -mHeaderViewHeight) {              return ;          }          if(margin < 0 && mRefreshing) {              // 当前已经在刷新,又重新进行拖动,但未拖满,不进行操作              return ;          }          int finalMargin = 0;          if(margin <= 0 && !mRefreshing) {              finalMargin = mHeaderViewHeight;          }          // 松开刷新,或者下拉刷新,又松手,没有触发刷新          // mAssistScroller辅助滚动 ,得要Override computeScroll()        // 如果头部的状态已经是隐藏的就没必要再去滚动了        if(this.getScrollY()<mHeaderViewHeight){            mAssistScroller.startScroll(0, -margin, 0, finalMargin + margin, SCROLL_DURATION);              invalidate();          }     }    @Override      public void computeScroll() {          if(mAssistScroller.computeScrollOffset()) {              mRefreshHeaderView.updateMargin(-mAssistScroller.getCurrY());              //继续重绘              postInvalidate();          }          super.computeScroll();      }

5.到第四步为止就基本大功告成了,最后就差几个提供给调用者的公共方法和刷新的监听。

     /**     * 设置刷新的监听     * @param listener     */    public void setOnRefreshScrollViewListener(OnRefreshScrollViewListener listener){        this.mListener = listener;    }    public interface OnRefreshScrollViewListener {          public void onRefresh();      }    /**     * 加载完成     */    public void onLoadComplete(){        if(mRefreshing) {              mRefreshing = false;              resetHeaderView();          }     }    /**      * 设置scroll是否可以刷新      *       * @param enableRefresh      */      public void setEnableRefresh(boolean enableRefresh) {          this.mEnableRefresh = enableRefresh;      }

源码地址:http://pan.baidu.com/s/1bnhqHI3

0 0