下拉刷新控件很常见,Android官方也推出了自己的下拉组件SwipeRefreshLayout。之前项目中有用到下拉刷新控件,要求根据手势播放动画效果,由于没有现成的,就参考第三方下拉控件,改造成自己项目所需的。改造过程还算比较顺利,现分享出来,仅供参考。
效果图
布局pull_to_refresh_header.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#dadada"> <ImageView android:id="@+id/cart" android:layout_width="39dp" android:layout_height="27dp" android:layout_marginTop="50dp" android:layout_alignParentLeft="true" android:visibility="visible" android:src="@drawable/loading_car"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="10sp" android:drawableBottom="@drawable/loading_wenan"/> <TextView android:id="@+id/loading_middle" android:layout_width="120dp" android:layout_height="55dp" android:visibility="invisible" android:background="@drawable/refresh_anim"/> <TextView android:id="@+id/refresh_text" android:layout_width="wrap_content" android:layout_height="20dp" android:gravity="center" android:text="正在刷新..." android:textSize="10sp" android:textColor="#999"/> </LinearLayout> </RelativeLayout>
package com.miloisbadboy.view; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import com.miloisbadboy.R; public class PullToRefreshView extends LinearLayout { private static final String TAG = "PullToRefreshView"; // refresh states private static final int PULL_TO_REFRESH = 2; private static final int RELEASE_TO_REFRESH = 3; private static final int REFRESHING = 4; // pull state private static final int PULL_UP_STATE = 0; private static final int PULL_DOWN_STATE = 1; private boolean enablePullTorefresh = true; private boolean enablePullLoadMoreDataStatus = true; /** * last y */ private int mLastMotionY; /** * lock */ private boolean mLock; /** * header view */ private View mHeaderView; /** * list or grid */ private AdapterView<?> mAdapterView; /** * scrollview */ private ScrollView mScrollView; /** * header view height */ private int mHeaderViewHeight; /** * 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; /** * header refresh listener */ private OnHeaderRefreshListener mOnHeaderRefreshListener; /** * 正在刷新动画 */ protected AnimationDrawable animationMiddle; /** * 刷新提示语 */ protected TextView mLoddingTextView; /** * 敞篷车 */ protected ImageView mCart; /** * 刷新提示 */ protected TextView refreshText; /** * 记录第一次按下的y坐标 */ protected int mYDown; /** * y方向移动的距离 */ protected int moveDistance; /** * 最小的有效滑动距离 */ protected int mTouchSlop; /** * 屏幕宽度 */ protected int mScreenWidth; /** * 屏幕高度 */ protected int mScreenHeight; public PullToRefreshView(Context context, AttributeSet attrs) { this(context, attrs,0); } public PullToRefreshView(Context context) { this(context,null); } public PullToRefreshView(Context context, AttributeSet attributeSet, int defStyle) { super(context,attributeSet,defStyle); init(context); } private void init(Context context) { mInflater = LayoutInflater.from(getContext()); setOrientation(VERTICAL); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mScreenWidth = context.getResources().getDisplayMetrics().widthPixels; mScreenHeight = context.getResources().getDisplayMetrics().heightPixels; // header view 在此添加,保证是第一个添加到linearlayout的最上端 addHeaderView(); } private void addHeaderView() { // header view mHeaderView = mInflater.inflate(R.layout.pull_to_refresh_header, this, false); mCart = (ImageView) mHeaderView.findViewById(R.id.cart); mLoddingTextView = (TextView) mHeaderView.findViewById(R.id.loading_middle); animationMiddle = (AnimationDrawable) mLoddingTextView.getBackground(); refreshText = (TextView) mHeaderView.findViewById(R.id.refresh_text); // header layout measureView(mHeaderView); mHeaderViewHeight = mHeaderView.getMeasuredHeight(); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mHeaderViewHeight); // 设置topMargin的值为负的header View高度,即将其隐藏在最上方 params.topMargin = -(mHeaderViewHeight); // mHeaderView.setLayoutParams(params1); addView(mHeaderView, params); } /** * init AdapterView like ListView,GridView and so on;or init ScrollView */ private void initContentAdapterView() { int count = getChildCount(); if (count < 2) { throw new IllegalArgumentException("this layout must contain 2 child views,and AdapterView or ScrollView must in the second position!"); } View view = null; for (int i = 0; i < count; ++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!"); } } 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); } @Override public boolean onInterceptTouchEvent(MotionEvent e) { int y = (int) e.getRawY(); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: // 首先拦截down事件,记录y坐标 mLastMotionY = y; mYDown = 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自己来处理) */ @Override public 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; moveDistance = (int) event.getRawY() - mYDown; if(Math.abs(moveDistance) > mTouchSlop) { if (mPullState == PULL_DOWN_STATE) { // PullToRefreshView执行下拉 Log.i(TAG, " pull down!parent view move!"); headerPrepareToRefresh(deltaY); // setHeaderPadding(-mHeaderViewHeight); } else if (mPullState == PULL_UP_STATE) { // PullToRefreshView执行上拉 Log.i(TAG, "pull up!parent view move!"); // footerPrepareToRefresh(deltaY); } mLastMotionY = y; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: int topMargin = getHeaderTopMargin(); if (mPullState == PULL_DOWN_STATE) { if (mHeaderState == RELEASE_TO_REFRESH) { // 开始刷新 headerRefreshing(); } else { // 还没有执行刷新,重新隐藏 setHeaderTopMargin(-mHeaderViewHeight); } } else if (mPullState == PULL_UP_STATE) { if (Math.abs(topMargin) >= mHeaderViewHeight) { } else { // 还没有执行刷新,重新隐藏 setHeaderTopMargin(-mHeaderViewHeight); } } break; } return super.onTouchEvent(event); } /** * 是否应该到了父View,即PullToRefreshView滑动 * @param deltaY > 0 是向下运动,< 0是向上运动 */ private boolean isRefreshViewScroll(int deltaY) { if (mHeaderState == REFRESHING || mFooterState == REFRESHING) { return false; } // 对于ListView和GridView if (mAdapterView != null) { // 子view(ListView or GridView)滑动到最顶端 if (deltaY > 0) { // 判断是否禁用下拉刷新操作 if (!enablePullTorefresh) { return false; } 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) <= 11) {// 这里之前用3可以判断,但现在不行,还没找到原因 mPullState = PULL_DOWN_STATE; return true; } } else if (deltaY < 0) { // 判断是否禁用上拉加载更多操作 if (!enablePullLoadMoreDataStatus) { return false; } 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; } } } // 对于ScrollView if (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 distance = (int)((moveDistance / (float)mScreenHeight) * mScreenWidth); if(distance > mScreenWidth/2 - mCart.getMeasuredWidth()) { distance = mScreenWidth/2 - mCart.getMeasuredWidth(); } // 设置车的x轴偏移量 mCart.setTranslationX(distance); int newTopMargin = changingHeaderViewTopMargin(deltaY); // 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态 if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) { refreshText.setText(R.string.pull_to_refresh_release_label); mHeaderState = RELEASE_TO_REFRESH; } else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放 refreshText.setText(R.string.pull_to_refresh_pull_label); mHeaderState = PULL_TO_REFRESH; } } /** * 修改Header view top margin的值 */ private int changingHeaderViewTopMargin(int deltaY) { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); float newTopMargin = params.topMargin + deltaY * 0.7f; params.topMargin = (int) newTopMargin; mHeaderView.setLayoutParams(params); invalidate(); return params.topMargin; } /** * header refreshing */ public void headerRefreshing() { mHeaderState = REFRESHING; setHeaderTopMargin(0); mCart.setVisibility(GONE); mLoddingTextView.setVisibility(VISIBLE); animationMiddle.start(); refreshText.setText(R.string.pull_to_refresh_refreshing_label); if (mOnHeaderRefreshListener != null) { mOnHeaderRefreshListener.onHeaderRefresh(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() { mCart.setVisibility(VISIBLE); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mCart,"translationX",mScreenWidth/2,mScreenWidth); objectAnimator.setDuration(300); objectAnimator.start(); mLoddingTextView.setVisibility(INVISIBLE); animationMiddle.stop(); refreshText.setText(""); mHeaderState = PULL_TO_REFRESH; postDelayed(new Runnable() { @Override public void run() { setHeaderTopMargin(-mHeaderViewHeight); } },200); } @Override protected void onFinishInflate() { super.onFinishInflate(); initContentAdapterView(); } /** * Resets the list to a normal state after a refresh. */ public void onHeaderRefreshComplete(CharSequence lastUpdated) { onHeaderRefreshComplete(); } /** * 获取当前header view 的topMargin */ private int getHeaderTopMargin() { LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams(); return params.topMargin; } /** * set headerRefreshListener */ public void setOnHeaderRefreshListener(OnHeaderRefreshListener headerRefreshListener) { mOnHeaderRefreshListener = headerRefreshListener; } /** * Interface definition for a callback to be invoked when list/grid header * view should be refreshed. */ public interface OnHeaderRefreshListener { public void onHeaderRefresh(PullToRefreshView view); } public boolean isEnablePullTorefresh() { return enablePullTorefresh; } public void setEnablePullTorefresh(boolean enablePullTorefresh) { this.enablePullTorefresh = enablePullTorefresh; } }
作者:風中赏雪