/* * Copyright (C) 2012 www.amsoft.cn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ab.view.pullview; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.widget.AdapterView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.ScrollView; // TODO: Auto-generated Javadoc /** * © 2012 amsoft.cn * 名称:AbPullToRefreshView.java * 描述:下拉刷新和加载更多的View. * * @author 还如一梦中 * @version v1.0 * @date:2014-06-04 下午11:52:13 */ public class AbPullToRefreshView extends LinearLayout { /** 上下文. */ private Context mContext = null; /** 下拉刷新的开关. */ private boolean mEnablePullRefresh = true; /** 加载更多的开关. */ private boolean mEnableLoadMore = true; /** x上一次保存的. */ private int mLastMotionX; /** y上一次保存的. */ private int mLastMotionY; /** header view. */ private AbListViewHeader mHeaderView; /** footer view. */ private AbListViewFooter mFooterView; /** list or grid. */ private AdapterView<?> mAdapterView; /** Scrollview. */ private ScrollView mScrollView; /** header view 高度. */ private int mHeaderViewHeight; /** footer view 高度. */ private int mFooterViewHeight; /** 滑动状态. */ private int mPullState; /** 上滑动作. */ private static final int PULL_UP_STATE = 0; /** 下拉动作. */ private static final int PULL_DOWN_STATE = 1; /** 上一次的数量. */ private int mCount = 0; /** 正在下拉刷新. */ private boolean mPullRefreshing = false; /** 正在加载更多. */ private boolean mPullLoading = false; /** Footer加载更多监听器. */ private OnFooterLoadListener mOnFooterLoadListener; /** Header下拉刷新监听器. */ private OnHeaderRefreshListener mOnHeaderRefreshListener; /** * 构造. * @param context the context * @param attrs the attrs */ public AbPullToRefreshView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** * 构造. * @param context the context */ public AbPullToRefreshView(Context context) { super(context); init(context); } /** * 初始化View. * @param context the context */ private void init(Context context) { mContext = context; this.setOrientation(LinearLayout.VERTICAL); // 增加HeaderView addHeaderView(); } /** * add HeaderView. */ private void addHeaderView() { mHeaderView = new AbListViewHeader(mContext); mHeaderViewHeight = mHeaderView.getHeaderHeight(); mHeaderView.setGravity(Gravity.BOTTOM); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mHeaderViewHeight); // 设置topMargin的值为负的header View高度,即将其隐藏在最上方 params.topMargin = -(mHeaderViewHeight); addView(mHeaderView, params); } /** * add FooterView. */ private void addFooterView() { mFooterView = new AbListViewFooter(mContext); mFooterViewHeight= mFooterView.getFooterHeight(); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mFooterViewHeight); addView(mFooterView, params); } /** * 在此添加footer view保证添加到linearlayout中的最后. */ @Override protected void onFinishInflate() { super.onFinishInflate(); addFooterView(); initContentAdapterView(); } /** * init AdapterView like ListView, * GridView and so on; * or init ScrollView. */ private void initContentAdapterView() { int count = getChildCount(); if (count < 3) { throw new IllegalArgumentException("this layout must contain 3 child views,and AdapterView or ScrollView must in the second position!"); } View view = null; for (int i = 0; i < count - 1; ++i) { view = getChildAt(i); if (view instanceof AdapterView<?>) { mAdapterView = (AdapterView<?>) view; } if (view instanceof ScrollView) { // finish later mScrollView = (ScrollView) view; } } if (mAdapterView == null && mScrollView == null) { throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!"); } } /* (non-Javadoc) * @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) */ @Override public boolean onInterceptTouchEvent(MotionEvent e) { int x = (int) e.getX(); int y = (int) e.getY(); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: // 首先拦截down事件,记录y坐标 mLastMotionX = x; mLastMotionY = y; break; case MotionEvent.ACTION_MOVE: // deltaY > 0 是向下运动,< 0是向上运动 int deltaX = x - mLastMotionX; int deltaY = y - mLastMotionY; //解决点击与移动的冲突 if(Math.abs(deltaX)<Math.abs(deltaY) && Math.abs(deltaY) > 10){ if (isRefreshViewScroll(deltaY)) { return true; } } break; } return false; } /* * 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return * false)则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理) */ /* (non-Javadoc) * @see android.view.View#onTouchEvent(android.view.MotionEvent) */ @Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: int deltaY = y - mLastMotionY; if (mPullState == PULL_DOWN_STATE) { // 执行下拉 headerPrepareToRefresh(deltaY); } else if (mPullState == PULL_UP_STATE) { // 执行上拉 footerPrepareToRefresh(deltaY); } mLastMotionY = y; break; //UP和CANCEL执行相同的方法 case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: int topMargin = getHeaderTopMargin(); if (mPullState == PULL_DOWN_STATE) { if (topMargin >= 0) { // 开始刷新 headerRefreshing(); } else { // 还没有执行刷新,重新隐藏 setHeaderTopMargin(-mHeaderViewHeight); } } else if (mPullState == PULL_UP_STATE) { //控制在什么时候加载更多 if (Math.abs(topMargin) >= mHeaderViewHeight + mFooterViewHeight) { // 开始执行footer 刷新 footerLoading(); } else { // 还没有执行刷新,重新隐藏 setHeaderTopMargin(-mHeaderViewHeight); } } break; } return super.onTouchEvent(event); } /** * 判断滑动方向,和是否响应事件. * * @param deltaY deltaY > 0 是向下运动,< 0是向上运动 * @return true, if is refresh view scroll */ private boolean isRefreshViewScroll(int deltaY) { if (mPullRefreshing || mPullLoading) { return false; } // 对于ListView和GridView if (mAdapterView != null) { // 子view(ListView or GridView)滑动到最顶端 if (deltaY > 0) { // 判断是否禁用下拉刷新操作 if (!mEnablePullRefresh) { return false; } View child = mAdapterView.getChildAt(0); if (child == null) { // 如果mAdapterView中没有数据,不拦截 //return false; mPullState = PULL_DOWN_STATE; return true; } if (mAdapterView.getFirstVisiblePosition() == 0 && child.getTop() == 0) { mPullState = PULL_DOWN_STATE; return true; } int top = child.getTop(); int padding = mAdapterView.getPaddingTop(); if (mAdapterView.getFirstVisiblePosition() == 0 && Math.abs(top - padding) <= 11) { mPullState = PULL_DOWN_STATE; return true; } } else if (deltaY < 0) { // 判断是否禁用上拉加载更多操作 if (!mEnableLoadMore) { return false; } View lastChild = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1); if (lastChild == null) { //如果mAdapterView中没有数据,不拦截 //return false; mPullState = PULL_UP_STATE; return true; } // 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view, // 等于父View的高度说明mAdapterView已经滑动到最后 if (lastChild.getBottom() <= getHeight() && mAdapterView.getLastVisiblePosition() == mAdapterView.getCount() - 1) { mPullState = PULL_UP_STATE; return true; } } } // 对于ScrollView if (mScrollView != null) { // 子scroll view滑动到最顶端 View child = mScrollView.getChildAt(0); if (deltaY > 0 && mScrollView.getScrollY() == 0) { // 判断是否禁用下拉刷新操作 if (!mEnablePullRefresh) { return false; } mPullState = PULL_DOWN_STATE; return true; } else if (deltaY < 0 && child.getMeasuredHeight() <= getHeight() + mScrollView.getScrollY()) { // 判断是否禁用上拉加载更多操作 if (!mEnableLoadMore) { return false; } mPullState = PULL_UP_STATE; return true; } } return false; } /** * header 准备刷新,手指移动过程,还没有释放. * * @param deltaY 手指滑动的距离 */ private void headerPrepareToRefresh(int deltaY) { if (mPullRefreshing || mPullLoading) { return; } int newTopMargin = updateHeaderViewTopMargin(deltaY); // 当header view的topMargin>=0时,说明header view完全显示出来了 ,修改header view 的提示状态 if (newTopMargin >= 0 && mHeaderView.getState() != AbListViewHeader.STATE_REFRESHING) { //提示松开刷新 mHeaderView.setState(AbListViewHeader.STATE_READY); } else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) { //提示下拉刷新 mHeaderView.setState(AbListViewHeader.STATE_NORMAL); } } /** * footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view * 高度是一样,都是通过修改header view的topmargin的值来达到. * * @param deltaY 手指滑动的距离 */ private void footerPrepareToRefresh(int deltaY) { if (mPullRefreshing || mPullLoading) { return; } int newTopMargin = updateHeaderViewTopMargin(deltaY); // 如果header view topMargin 的绝对值大于或等于header + footer 的高度 // 说明footer view 完全显示出来了,修改footer view 的提示状态 if (Math.abs(newTopMargin) >= (mHeaderViewHeight + mFooterViewHeight) && mFooterView.getState() != AbListViewFooter.STATE_LOADING) { mFooterView.setState(AbListViewFooter.STATE_READY); } else if (Math.abs(newTopMargin) < (mHeaderViewHeight + mFooterViewHeight)) { mFooterView.setState(AbListViewFooter.STATE_LOADING); } } /** * 修改Header view top margin的值. * * @param deltaY the delta y * @return the int */ private int updateHeaderViewTopMargin(int deltaY) { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); float newTopMargin = params.topMargin + deltaY * 0.3f; // 这里对上拉做一下限制,因为当前上拉后然后不释放手指直接下拉,会把下拉刷新给触发了 // 表示如果是在上拉后一段距离,然后直接下拉 if (deltaY > 0 && mPullState == PULL_UP_STATE && Math.abs(params.topMargin) <= mHeaderViewHeight) { return params.topMargin; } // 同样地,对下拉做一下限制,避免出现跟上拉操作时一样的bug if (deltaY < 0 && mPullState == PULL_DOWN_STATE && Math.abs(params.topMargin) >= mHeaderViewHeight) { return params.topMargin; } params.topMargin = (int) newTopMargin; mHeaderView.setLayoutParams(params); return params.topMargin; } /** * 下拉刷新. */ public void headerRefreshing() { mPullRefreshing = true; mHeaderView.setState(AbListViewHeader.STATE_REFRESHING); setHeaderTopMargin(0); if (mOnHeaderRefreshListener != null) { mOnHeaderRefreshListener.onHeaderRefresh(this); } } /** * 加载更多. */ private void footerLoading() { mPullLoading = true; int top = mHeaderViewHeight + mFooterViewHeight; setHeaderTopMargin(-top); if (mOnFooterLoadListener != null) { mOnFooterLoadListener.onFooterLoad(this); } } /** * 设置header view 的topMargin的值. * * @param topMargin the new header top margin */ private void setHeaderTopMargin(int topMargin) { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); params.topMargin = topMargin; mHeaderView.setLayoutParams(params); } /** * header view 完成更新后恢复初始状态. */ public void onHeaderRefreshFinish() { setHeaderTopMargin(-mHeaderViewHeight); mHeaderView.setState(AbListViewHeader.STATE_NORMAL); if(mAdapterView!=null){ mCount = mAdapterView.getCount(); //判断有没有数据 if(mCount > 0){ mFooterView.setState(AbListViewFooter.STATE_READY); }else{ mFooterView.setState(AbListViewFooter.STATE_EMPTY); } }else{ mFooterView.setState(AbListViewFooter.STATE_READY); } mPullRefreshing = false; } /** * footer view 完成更新后恢复初始状态. */ public void onFooterLoadFinish() { setHeaderTopMargin(-mHeaderViewHeight); mHeaderView.setState(AbListViewHeader.STATE_NORMAL); if(mAdapterView!=null){ int countNew = mAdapterView.getCount(); //判断有没有更多数据了 if(countNew > mCount){ mFooterView.setState(AbListViewFooter.STATE_READY); }else{ mFooterView.setState(AbListViewFooter.STATE_NO); } }else{ mFooterView.setState(AbListViewFooter.STATE_READY); } mPullLoading = false; } /** * 获取当前header view 的topMargin. * * @return the header top margin */ private int getHeaderTopMargin() { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); return params.topMargin; } /** * 设置下拉刷新的监听器. * * @param headerRefreshListener the new on header refresh listener */ public void setOnHeaderRefreshListener(OnHeaderRefreshListener headerRefreshListener) { mOnHeaderRefreshListener = headerRefreshListener; } /** * 设置加载更多的监听器. * * @param footerLoadListener the new on footer load listener */ public void setOnFooterLoadListener(OnFooterLoadListener footerLoadListener) { mOnFooterLoadListener = footerLoadListener; } /** * 打开或者关闭下拉刷新功能. * @param enable 开关标记 */ public void setPullRefreshEnable(boolean enable) { mEnablePullRefresh = enable; } /** * 打开或者关闭加载更多功能. * @param enable 开关标记 */ public void setLoadMoreEnable(boolean enable) { mEnableLoadMore = enable; } /** * 下拉刷新是打开的吗. * * @return true, if is enable pull refresh */ public boolean isEnablePullRefresh(){ return mEnablePullRefresh; } /** * 加载更多是打开的吗. * * @return true, if is enable load more */ public boolean isEnableLoadMore(){ return mEnableLoadMore; } /** * 描述:获取Header View. * * @return the header view */ public AbListViewHeader getHeaderView() { return mHeaderView; } /** * 描述:获取Footer View. * * @return the footer view */ public AbListViewFooter getFooterView() { return mFooterView; } /** * 描述:获取Header ProgressBar,用于设置自定义样式. * * @return the header progress bar */ public ProgressBar getHeaderProgressBar() { return mHeaderView.getHeaderProgressBar(); } /** * 描述:获取Footer ProgressBar,用于设置自定义样式. * * @return the footer progress bar */ public ProgressBar getFooterProgressBar() { return mFooterView.getFooterProgressBar(); } /** * Interface definition for a callback to be invoked when list/grid footer * view should be refreshed. * * @see OnFooterLoadEvent */ public interface OnFooterLoadListener { /** * On footer load. * * @param view the view */ public void onFooterLoad(AbPullToRefreshView view); } /** * Interface definition for a callback to be invoked when list/grid header * view should be refreshed. * * @see OnHeaderRefreshEvent */ public interface OnHeaderRefreshListener { /** * On header refresh. * * @param view the view */ public void onHeaderRefresh(AbPullToRefreshView view); } }