PullToRefreshView 上啦下拉刷新

来源:互联网 发布:idea算法 编辑:程序博客网 时间:2024/06/05 10:10
public class PullToRefreshView extends LinearLayout {//private static final String TAG = "PullToRefreshView";// refresh statesprivate static final int PULL_TO_REFRESH = 2;private static final int RELEASE_TO_REFRESH = 3;private static final int REFRESHING = 4;// pull stateprivate static final int PULL_UP_STATE = 0;private static final int PULL_DOWN_STATE = 1;/** * last y */private int mLastMotionY;/** * lock *///private boolean mLock;/** * header view */private View mHeaderView;/** * footer view */private View mFooterView;/** * list or grid */private AdapterView<?> mAdapterView;/** * scrollview */private ScrollView mScrollView;/** * header view height */private int mHeaderViewHeight;/** * footer view height */private int mFooterViewHeight;/** * header tip text */private TextView mHeaderTextView;/** * footer tip text */private TextView mFooterTextView;/** * header refresh time */private TextView mHeaderUpdateTextView;/** * footer refresh time */// private TextView mFooterUpdateTextView;/** * header progress bar */private ProgressBar mHeaderProgressBar;/** * footer progress bar */private ProgressBar mFooterProgressBar;/** * layout inflater */private LayoutInflater mInflater;/** * header view current state */private int mHeaderState;/** * footer view current state */private int mFooterState;/** * pull state,pull up or pull down;PULL_UP_STATE or PULL_DOWN_STATE */private int mPullState;/** * 变为向下的箭头,改变箭头方向 */private RotateAnimation mFlipAnimation;/** * 变为逆向的箭头,旋转 */private RotateAnimation mReverseFlipAnimation;/** * footer refresh listener */private OnFooterRefreshListener mOnFooterRefreshListener;/** * footer refresh listener */private OnHeaderRefreshListener mOnHeaderRefreshListener;/** * last update time *///private String mLastUpdateTime;private boolean isEnd = false;public PullToRefreshView(Context context, AttributeSet attrs) {super(context, attrs);init();}public PullToRefreshView(Context context) {super(context);init();}/** * init *  * @param context */private void init() {//需要设置成verticalsetOrientation(LinearLayout.VERTICAL);// Load all of the animations we need in code rather than through XMLmFlipAnimation = new RotateAnimation(0, -180,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);mFlipAnimation.setInterpolator(new LinearInterpolator());mFlipAnimation.setDuration(250);mFlipAnimation.setFillAfter(true);mReverseFlipAnimation = new RotateAnimation(-180, 0,RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);mReverseFlipAnimation.setInterpolator(new LinearInterpolator());mReverseFlipAnimation.setDuration(250);mReverseFlipAnimation.setFillAfter(true);mInflater = LayoutInflater.from(getContext());// header view 在此添加,保证是第一个添加到linearlayout的最上端addHeaderView();}private void addHeaderView() {// header viewmHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);mHeaderUpdateTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_updated_at);mHeaderProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);// header layoutmeasureView(mHeaderView);mHeaderViewHeight = mHeaderView.getMeasuredHeight();LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,mHeaderViewHeight);// 设置topMargin的值为负的header View高度,即将其隐藏在最上方params.topMargin = -(mHeaderViewHeight);// mHeaderView.setLayoutParams(params1);addView(mHeaderView, params);}private void addFooterView() {// footer viewmFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pull_to_load_text);mFooterProgressBar = (ProgressBar) mFooterView.findViewById(R.id.pull_to_load_progress);// footer layoutmeasureView(mFooterView);mFooterViewHeight = mFooterView.getMeasuredHeight();LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,mFooterViewHeight);// int top = getHeight();// params.topMargin// =getHeight();//在这里getHeight()==0,但在onInterceptTouchEvent()方法里getHeight()已经有值了,不再是0;// getHeight()什么时候会赋值,稍候再研究一下// 由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏addView(mFooterView, params);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();// footer view 在此添加保证添加到linearlayout中的最后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 latermScrollView = (ScrollView) view;}}if (mAdapterView == null && mScrollView == null) {throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!");}}private void measureView(View child) {ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);int lpHeight = p.height;int childHeightSpec;if (lpHeight > 0) {childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,MeasureSpec.EXACTLY);} else {childHeightSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);}child.measure(childWidthSpec, childHeightSpec);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {int y = (int) e.getRawY();switch (e.getAction()) {case MotionEvent.ACTION_DOWN:// 首先拦截down事件,记录y坐标mLastMotionY = y;break;case MotionEvent.ACTION_MOVE:// deltaY > 0 是向下运动,< 0是向上运动int deltaY = y - mLastMotionY;if (isRefreshViewScroll(deltaY)) {return true;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:break;}return false;}/* * 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return * false)则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理) */@Overridepublic boolean onTouchEvent(MotionEvent event) {//if (mLock) {//return true;//}int y = (int) event.getRawY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// onInterceptTouchEvent已经记录// mLastMotionY = y;break;case MotionEvent.ACTION_MOVE:int deltaY = y - mLastMotionY;if (mPullState == PULL_DOWN_STATE) {//执行下拉headerPrepareToRefresh(deltaY);// setHeaderPadding(-mHeaderViewHeight);} else if (mPullState == PULL_UP_STATE) {//执行上拉footerPrepareToRefresh(deltaY);}mLastMotionY = y;break;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 刷新footerRefreshing();} else {// 还没有执行刷新,重新隐藏setHeaderTopMargin(-mHeaderViewHeight);}}break;}return super.onTouchEvent(event);}/** * 是否应该到了父View,即PullToRefreshView滑动 *  * @param deltaY *            , deltaY > 0 是向下运动,< 0是向上运动 * @return */private boolean isRefreshViewScroll(int deltaY) {if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {return false;}//对于ListView和GridViewif (mAdapterView != null) {// 子view(ListView or GridView)滑动到最顶端if (deltaY > 0) {View child = mAdapterView.getChildAt(0);if (child == null) {// 如果mAdapterView中没有数据,不拦截return false;}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) <= 8) {//这里之前用3可以判断,但现在不行,还没找到原因mPullState = PULL_DOWN_STATE;return true;}} else if (deltaY < 0) {View lastChild = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);if (lastChild == null) {// 如果mAdapterView中没有数据,不拦截return false;}// 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view,// 等于父View的高度说明mAdapterView已经滑动到最后if (lastChild.getBottom() <= getHeight()&& mAdapterView.getLastVisiblePosition() == mAdapterView.getCount() - 1) {mPullState = PULL_UP_STATE;return true;}}}// 对于ScrollViewif (mScrollView != null) {// 子scroll view滑动到最顶端View child = mScrollView.getChildAt(0);if (deltaY > 0 && mScrollView.getScrollY() == 0) {mPullState = PULL_DOWN_STATE;return true;} else if (deltaY < 0&& child.getMeasuredHeight() <= getHeight()+ mScrollView.getScrollY()) {mPullState = PULL_UP_STATE;return true;}}return false;}/** * header 准备刷新,手指移动过程,还没有释放 *  * @param deltaY *            ,手指滑动的距离 */private void headerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);// 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {mHeaderTextView.setText(R.string.pull_to_refresh_release_label);mHeaderUpdateTextView.setVisibility(View.VISIBLE);mHeaderState = RELEASE_TO_REFRESH;} else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放// mHeaderImageView.mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);mHeaderState = PULL_TO_REFRESH;}}/** * footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view * 高度是一样,都是通过修改header view的topmargin的值来达到 *  * @param deltaY *            ,手指滑动的距离 */private void footerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);// 如果header view topMargin 的绝对值大于或等于header + footer 的高度// 说明footer view 完全显示出来了,修改footer view 的提示状态if (Math.abs(newTopMargin) >= (mHeaderViewHeight + mFooterViewHeight)&& mFooterState != RELEASE_TO_REFRESH) {if(!isEnd){mFooterTextView.setText(R.string.pull_to_refresh_footer_release_label);}mFooterState = RELEASE_TO_REFRESH;} else if (Math.abs(newTopMargin) < (mHeaderViewHeight + mFooterViewHeight)) {if(!isEnd){mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);}mFooterState = PULL_TO_REFRESH;}}/** * 修改Header view top margin的值 *  * @param deltaY */private int changingHeaderViewTopMargin(int deltaY) {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();float newTopMargin = params.topMargin + deltaY * 0.3f;//这里对上拉做一下限制,因为当前上拉后然后不释放手指直接下拉,会把下拉刷新给触发了,感谢网友yufengzungzhe的指出//表示如果是在上拉后一段距离,然后直接下拉if(deltaY>0&&mPullState == PULL_UP_STATE&&Math.abs(params.topMargin) <= mHeaderViewHeight){return params.topMargin;}//同样地,对下拉做一下限制,避免出现跟上拉操作时一样的bugif(deltaY<0&&mPullState == PULL_DOWN_STATE&&Math.abs(params.topMargin)>=mHeaderViewHeight){return params.topMargin;}params.topMargin = (int) newTopMargin;mHeaderView.setLayoutParams(params);invalidate();return params.topMargin;}/** * header refreshing *  */private void headerRefreshing() {mHeaderState = REFRESHING;setHeaderTopMargin(0);mHeaderProgressBar.setVisibility(View.VISIBLE);mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);if (mOnHeaderRefreshListener != null) {mOnHeaderRefreshListener.onHeaderRefresh(this);}}/** * footer refreshing *  */private void footerRefreshing() {mFooterState = REFRESHING;int top = mHeaderViewHeight + mFooterViewHeight;setHeaderTopMargin(-top);if(!isEnd){mFooterProgressBar.setVisibility(View.VISIBLE);mFooterTextView.setText(R.string.pull_to_refresh_footer_refreshing_label);}if (mOnFooterRefreshListener != null) {mOnFooterRefreshListener.onFooterRefresh(this);}}/** * 设置header view 的topMargin的值 *  * @param topMargin *            ,为0时,说明header view 刚好完全显示出来; 为-mHeaderViewHeight时,说明完全隐藏了 */private void setHeaderTopMargin(int topMargin) {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();params.topMargin = topMargin;mHeaderView.setLayoutParams(params);invalidate();}/** * header view 完成更新后恢复初始状态 *  */public void onHeaderRefreshComplete() {setHeaderTopMargin(-mHeaderViewHeight);mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);mHeaderProgressBar.setVisibility(View.GONE);// mHeaderUpdateTextView.setText("");mHeaderState = PULL_TO_REFRESH;}/** * Resets the list to a normal state after a refresh. *  * @param lastUpdated *            Last updated at. */public void onHeaderRefreshComplete(final CharSequence lastUpdated) {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {setLastUpdated(lastUpdated);onHeaderRefreshComplete();}}, 500);}/** * footer view 完成更新后恢复初始状态 */public void onFooterRefreshComplete() {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {setHeaderTopMargin(-mHeaderViewHeight);if(!isEnd){mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);}mFooterProgressBar.setVisibility(View.GONE);// mHeaderUpdateTextView.setText("");mFooterState = PULL_TO_REFRESH;if (mScrollView != null) {int y = mScrollView.getScrollY()+mFooterViewHeight;mScrollView.scrollTo(0, y);}}}, 500);}/** * Set a text to represent when the list was last updated. *  * @param lastUpdated *            Last updated at. */public void setLastUpdated(CharSequence lastUpdated) {if (lastUpdated != null) {mHeaderUpdateTextView.setVisibility(View.VISIBLE);mHeaderUpdateTextView.setText(lastUpdated);} else {mHeaderUpdateTextView.setVisibility(View.GONE);}}/** * 获取当前header view 的topMargin *  */private int getHeaderTopMargin() {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();return params.topMargin;}///**// * lock// * // *///private void lock() {//mLock = true;//}/////**// * unlock// * // *///private void unlock() {//mLock = false;//}/** * set headerRefreshListener *  * @param headerRefreshListener */public void setOnHeaderRefreshListener(OnHeaderRefreshListener headerRefreshListener) {mOnHeaderRefreshListener = headerRefreshListener;}public void setOnFooterRefreshListener(OnFooterRefreshListener footerRefreshListener) {mOnFooterRefreshListener = footerRefreshListener;}/** * Interface definition for a callback to be invoked when list/grid footer * view should be refreshed. */public interface OnFooterRefreshListener {public void onFooterRefresh(PullToRefreshView view);}/** * Interface definition for a callback to be invoked when list/grid header * view should be refreshed. */public interface OnHeaderRefreshListener {public void onHeaderRefresh(PullToRefreshView view);}/** * 设置是否还有新加载数据 * @param b */public void setNoMoreData(Boolean b) {isEnd = b;if(b){mFooterTextView.setText("没有更多数据了");}}

0 0