概述
PullToRefresh是一个很好的框架,但是功能多了,对于仅需求某一些功能的开发者来说,确实大了点。
因为项目中仅仅需要用到下拉刷新,所以手动把下拉方法抽出来了,简化了一些代码。
当然,代码比较多,还有问题的话可以下载我的demo,地址是:http://download.csdn.net/detail/qq_28767927/9918846
有这么几个类
HeaderLoadingLayout类:
/**ILoadingLayout类:* 这个类封装了下拉刷新的布局 * * @author Li Hong * @since 2013-7-30 */ public class HeaderLoadingLayout extends LoadingLayout { /** 旋转动画时间 */ private static final int ROTATE_ANIM_DURATION = 150; /**Header的容器*/ private RelativeLayout mHeaderContainer; /**箭头图片*/ private ImageView mArrowImageView; /**进度条*/ private ProgressBar mProgressBar; /**状态提示TextView*/ private TextView mHintTextView; /**最后更新时间的TextView*/ private TextView mHeaderTimeView; /**最后更新时间的标题*/ private TextView mHeaderTimeViewTitle; /**向上的动画*/ private Animation mRotateUpAnim; /**向下的动画*/ private Animation mRotateDownAnim; /** * 构造方法 * * @param context context */ public HeaderLoadingLayout(Context context) { super(context); init(); } /** * 构造方法 * * @param context context * @param attrs attrs */ public HeaderLoadingLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { mHeaderContainer = (RelativeLayout) findViewById(R.id.pull_to_refresh_header_content); mArrowImageView = (ImageView) findViewById(R.id.pull_to_refresh_header_arrow); mHintTextView = (TextView) findViewById(R.id.pull_to_refresh_header_hint_textview); mProgressBar = (ProgressBar) findViewById(R.id.pull_to_refresh_header_progressbar); mHeaderTimeView = (TextView) findViewById(R.id.pull_to_refresh_header_time); mHeaderTimeViewTitle = (TextView) findViewById(R.id.pull_to_refresh_last_update_time_text); float pivotValue = 0.5f; // SUPPRESS CHECKSTYLE float toDegree = -180f; // SUPPRESS CHECKSTYLE // 初始化旋转动画 mRotateUpAnim = new RotateAnimation(0.0f, toDegree, Animation.RELATIVE_TO_SELF, pivotValue, Animation.RELATIVE_TO_SELF, pivotValue); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(toDegree, 0.0f, Animation.RELATIVE_TO_SELF, pivotValue, Animation.RELATIVE_TO_SELF, pivotValue); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } @Override public void setLastUpdatedLabel(CharSequence label) { // 如果最后更新的时间的文本是空的话,隐藏前面的标题 mHeaderTimeViewTitle.setVisibility(TextUtils.isEmpty(label) ? View.INVISIBLE : View.VISIBLE); mHeaderTimeView.setText(label); } @Override public int getContentSize() { if (null != mHeaderContainer) { return mHeaderContainer.getHeight(); } return (int) (getResources().getDisplayMetrics().density * 60); } @Override protected View createLoadingView(Context context, AttributeSet attrs) { return LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, null); } @Override protected void onStateChanged(State curState, State oldState) { mArrowImageView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.INVISIBLE); super.onStateChanged(curState, oldState); } @Override protected void onReset() { mArrowImageView.clearAnimation(); mHintTextView.setText(R.string.pull_to_refresh_header_hint_normal); } @Override protected void onPullToRefresh() { if (State.RELEASE_TO_REFRESH == getPreState()) { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateDownAnim); } mHintTextView.setText(R.string.pull_to_refresh_header_hint_normal); } @Override protected void onReleaseToRefresh() { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mHintTextView.setText(R.string.pull_to_refresh_header_hint_ready); } @Override protected void onRefreshing() { mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.VISIBLE); mHintTextView.setText(R.string.pull_to_refresh_header_hint_loading); } }
/** * 下拉刷新界面接口 * * @author Li Hong * @since 2013-7-29 */ public interface ILoadingLayout { /** * 当前的状态 */ enum State { /** * Initial state */ NONE, /** * When the UI is in a state which means that user is not interacting * with the Pull-to-Refresh function. */ RESET, /** * When the UI is being pulled by the user, but has not been pulled far * enough so that it refreshes when released. */ PULL_TO_REFRESH, /** * When the UI is being pulled by the user, and <strong>has</strong> * been pulled far enough so that it will refresh when released. */ RELEASE_TO_REFRESH, /** * When the UI is currently refreshing, caused by a pull gesture. */ REFRESHING, /** * No more data */ NO_MORE_DATA, } /** * 设置当前状态,派生类应该根据这个状态的变化来改变View的变化 * * @param state 状态 */ void setState(State state); /** * 得到当前的状态 * * @return 状态 */ State getState(); /** * 得到当前Layout的内容大小,它将作为一个刷新的临界点 * * @return 高度 */ int getContentSize(); /** * 在拉动时调用 * * @param scale 拉动的比例 */ void onPull(float scale); }IPullToRefresh类:
/** * 定义了拉动刷新的接口 * @author Li Hong * @since 2013-8-22 * @param <T> */ public interface IPullToRefresh<T extends View> { /** * 判断当前下拉刷新是否可用 * * @return true如果可用,false不可用 */ boolean isPullRefreshEnabled(); /** * 设置刷新的监听器 * * @param refreshListener 监听器对象 */ void setOnRefreshListener(PullToRefreshBase.OnRefreshListener<T> refreshListener); /** * 结束下拉刷新 */ void onPullDownRefreshComplete(); /** * 得到可刷新的View对象 * * @return 返回调用{@link #createRefreshableView(Context, AttributeSet)} 方法返回的对象 */ T getRefreshableView(); void setLastUpdatedLabel(CharSequence label); }LoadingLayout类:
/** * 这个类定义了Header和Footer的共通行为 * * @author Li Hong * @since 2013-8-16 */ public abstract class LoadingLayout extends FrameLayout implements ILoadingLayout { /**容器布局*/ private View mContainer; /**当前的状态*/ private State mCurState = State.NONE; /**前一个状态*/ private State mPreState = State.NONE; /** * 构造方法 * * @param context context */ public LoadingLayout(Context context) { this(context, null); } /** * 构造方法 * * @param context context * @param attrs attrs */ public LoadingLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 构造方法 * * @param context context * @param attrs attrs * @param defStyle defStyle */ public LoadingLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } /** * 初始化 * * @param context context * @param attrs attrs */ protected void init(Context context, AttributeSet attrs) { mContainer = createLoadingView(context, attrs); if (null == mContainer) { throw new NullPointerException("Loading view can not be null."); } LayoutParams params = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); addView(mContainer, params); } /** * 显示或隐藏这个布局 * * @param show flag */ public void show(boolean show) { // If is showing, do nothing. if (show == (View.VISIBLE == getVisibility())) { return; } ViewGroup.LayoutParams params = mContainer.getLayoutParams(); if (null != params) { if (show) { params.height = ViewGroup.LayoutParams.WRAP_CONTENT; } else { params.height = 0; } setVisibility(show ? View.VISIBLE : View.INVISIBLE); } } /** * 设置最后更新的时间文本 * * @param label 文本 */ public void setLastUpdatedLabel(CharSequence label) { } @Override public void setState(State state) { if (mCurState != state) { mPreState = mCurState; mCurState = state; onStateChanged(state, mPreState); } } @Override public State getState() { return mCurState; } @Override public void onPull(float scale) { } /** * 得到前一个状态 * * @return 状态 */ protected State getPreState() { return mPreState; } /** * 当状态改变时调用 * * @param curState 当前状态 * @param oldState 老的状态 */ protected void onStateChanged(State curState, State oldState) { switch (curState) { case RESET: onReset(); break; case RELEASE_TO_REFRESH: onReleaseToRefresh(); break; case PULL_TO_REFRESH: onPullToRefresh(); break; case REFRESHING: onRefreshing(); break; case NO_MORE_DATA: onNoMoreData(); break; default: break; } } /** * 当状态设置为{@link State#RESET}时调用 */ protected void onReset() { } /** * 当状态设置为{@link State#PULL_TO_REFRESH}时调用 */ protected void onPullToRefresh() { } /** * 当状态设置为{@link State#RELEASE_TO_REFRESH}时调用 */ protected void onReleaseToRefresh() { } /** * 当状态设置为{@link State#REFRESHING}时调用 */ protected void onRefreshing() { } /** * 当状态设置为{@link State#NO_MORE_DATA}时调用 */ protected void onNoMoreData() { } /** * 得到当前Layout的内容大小,它将作为一个刷新的临界点 * * @return 高度 */ public abstract int getContentSize(); /** * 创建Loading的View * * @param context context * @param attrs attrs * @return Loading的View */ protected abstract View createLoadingView(Context context, AttributeSet attrs); }PullToRefreshBase类:
/** * 这个实现了下拉刷新 * * @author Li Hong * @since 2013-7-29 * @param <T> */ public abstract class PullToRefreshBase<T extends View> extends LinearLayout implements IPullToRefresh<T> { /** * 定义了下拉刷新的接口。 * * @author Li Hong * @since 2013-7-29 */ public interface OnRefreshListener<V extends View> { /** * 下拉松手后会被调用 * * @param refreshView 刷新的View */ void onPullDownToRefresh(final PullToRefreshBase<V> refreshView); } /**回滚的时间*/ private static final int SCROLL_DURATION = 150; /**阻尼系数*/ private static final float OFFSET_RADIO = 2.5f; /**上一次移动的点 */ private float mLastMotionY = -1; /**下拉刷新的监听器 */ private OnRefreshListener<T> mRefreshListener; /**下拉刷新的布局 */ private LoadingLayout mHeaderLayout; /**上拉加载更多的布局*/ // private LoadingLayout mFooterLayout; /**HeaderView的高度*/ private int mHeaderHeight; /**下拉刷新是否可用*/ private boolean mPullRefreshEnabled = true; /**是否截断touch事件*/ private boolean mInterceptEventEnable = true; /**表示是否消费了touch事件,如果是,则不调用父类的onTouchEvent方法*/ private boolean mIsHandledTouchEvent = false; /**移动点的保护范围值*/ private int mTouchSlop; /**下拉的状态*/ private State mPullDownState = State.NONE; /**可以下拉刷新的View*/ T mRefreshableView; /**平滑滚动的Runnable*/ private SmoothScrollRunnable mSmoothScrollRunnable; /**可刷新View的包装布局*/ private FrameLayout mRefreshableViewWrapper; /** * 构造方法 * * @param context context */ public PullToRefreshBase(Context context) { super(context); init(context, null); } /** * 构造方法 * * @param context context * @param attrs attrs */ public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } /** * 构造方法 * * @param context context * @param attrs attrs * @param defStyle defStyle */ public PullToRefreshBase(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } /** * 初始化 * * @param context context */ private void init(Context context, AttributeSet attrs) { setOrientation(LinearLayout.VERTICAL); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mHeaderLayout = createHeaderLoadingLayout(context, attrs); mRefreshableView = createRefreshableView(context, attrs); if (null == mRefreshableView) { throw new NullPointerException("Refreshable view can not be null."); } addRefreshableView(context, mRefreshableView); addHeaderAndFooter(context); // 得到Header的高度,这个高度需要用这种方式得到,在onLayout方法里面得到的高度始终是0 getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { refreshLoadingViewsSize(); getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * 初始化padding,我们根据header和footer的高度来设置top padding和bottom padding */ private void refreshLoadingViewsSize() { // 得到header和footer的内容高度,它将会作为拖动刷新的一个临界值,如果拖动距离大于这个高度 // 然后再松开手,就会触发刷新操作 int headerHeight = (null != mHeaderLayout) ? mHeaderLayout.getContentSize() : 0; if (headerHeight < 0) { headerHeight = 0; } mHeaderHeight = headerHeight; // 这里得到Header和Footer的高度,设置的padding的top和bottom就应该是header和footer的高度 // 因为header和footer是完全看不见的 headerHeight = (null != mHeaderLayout) ? mHeaderLayout.getMeasuredHeight() : 0; int pLeft = getPaddingLeft(); int pTop = -headerHeight; int pRight = getPaddingRight(); int pBottom = getPaddingBottom(); setPadding(pLeft, pTop, pRight, pBottom); } @Override protected final void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // We need to update the header/footer when our size changes refreshLoadingViewsSize(); // 设置刷新View的大小 refreshRefreshableViewSize(w, h); /** * As we're currently in a Layout Pass, we need to schedule another one * to layout any changes we've made here */ post(new Runnable() { @Override public void run() { requestLayout(); } }); } @Override public void setOrientation(int orientation) { if (LinearLayout.VERTICAL != orientation) { throw new IllegalArgumentException("This class only supports VERTICAL orientation."); } // Only support vertical orientation super.setOrientation(orientation); } @Override public final boolean onInterceptTouchEvent(MotionEvent event) { if (!isInterceptTouchEventEnabled()) { return false; } final int action = event.getAction(); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mIsHandledTouchEvent = false; return false; } if (action != MotionEvent.ACTION_DOWN && mIsHandledTouchEvent) { return true; } switch (action) { case MotionEvent.ACTION_DOWN: mLastMotionY = event.getY(); mIsHandledTouchEvent = false; break; case MotionEvent.ACTION_MOVE: final float deltaY = event.getY() - mLastMotionY; final float absDiff = Math.abs(deltaY); // 这里有三个条件: // 1,位移差大于mTouchSlop,这是为了防止快速拖动引发刷新 // 2,isPullRefreshing(),如果当前正在下拉刷新的话,是允许向上滑动,并把刷新的HeaderView挤上去 // 3,isPullLoading(),理由与第2条相同 if (absDiff > mTouchSlop || isPullRefreshing() ) { mLastMotionY = event.getY(); // 第一个显示出来,Header已经显示或拉下 if (isPullRefreshEnabled() && isReadyForPullDown()) { // 1,Math.abs(getScrollY()) > 0:表示当前滑动的偏移量的绝对值大于0,表示当前HeaderView滑出来了或完全 // 不可见,存在这样一种case,当正在刷新时并且RefreshableView已经滑到顶部,向上滑动,那么我们期望的结果是 // 依然能向上滑动,直到HeaderView完全不可见 // 2,deltaY > 0.5f:表示下拉的值大于0.5f mIsHandledTouchEvent = (Math.abs(getScrollYValue()) > 0 || deltaY > 0.5f); // 如果截断事件,我们则仍然把这个事件交给刷新View去处理,典型的情况是让ListView/GridView将按下 // Child的Selector隐藏 if (mIsHandledTouchEvent) { mRefreshableView.onTouchEvent(event); } } } break; default: break; } return mIsHandledTouchEvent; } @Override public final boolean onTouchEvent(MotionEvent ev) { boolean handled = false; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastMotionY = ev.getY(); mIsHandledTouchEvent = false; break; case MotionEvent.ACTION_MOVE: final float deltaY = ev.getY() - mLastMotionY; mLastMotionY = ev.getY(); if (isPullRefreshEnabled() && isReadyForPullDown()) { pullHeaderLayout(deltaY / OFFSET_RADIO); handled = true; } else { mIsHandledTouchEvent = false; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (mIsHandledTouchEvent) { mIsHandledTouchEvent = false; // 当第一个显示出来时 if (isReadyForPullDown()) { // 调用刷新 if (mPullRefreshEnabled && (mPullDownState == State.RELEASE_TO_REFRESH)) { startRefreshing(); handled = true; } resetHeaderLayout(); } } break; default: break; } return handled; } @Override public boolean isPullRefreshEnabled() { return mPullRefreshEnabled && (null != mHeaderLayout); } @Override public void setOnRefreshListener(OnRefreshListener<T> refreshListener) { mRefreshListener = refreshListener; } @Override public void onPullDownRefreshComplete() { if (isPullRefreshing()) { mPullDownState = State.RESET; onStateChanged(State.RESET, true); // 回滚动有一个时间,我们在回滚完成后再设置状态为normal // 在将LoadingLayout的状态设置为normal之前,我们应该禁止 // 截断Touch事件,因为设里有一个post状态,如果有post的Runnable // 未被执行时,用户再一次发起下拉刷新,如果正在刷新时,这个Runnable // 再次被执行到,那么就会把正在刷新的状态改为正常状态,这就不符合期望 postDelayed(new Runnable() { @Override public void run() { setInterceptTouchEventEnabled(true); mHeaderLayout.setState(State.RESET); } }, getSmoothScrollDuration()); resetHeaderLayout(); setInterceptTouchEventEnabled(false); } } @Override public T getRefreshableView() { return mRefreshableView; } @Override public void setLastUpdatedLabel(CharSequence label){ if (null != mHeaderLayout) { mHeaderLayout.setLastUpdatedLabel(label); } } /** * 开始刷新,通常用于调用者主动刷新,典型的情况是进入界面,开始主动刷新,这个刷新并不是由用户拉动引起的 * * @param smoothScroll 表示是否有平滑滚动,true表示平滑滚动,false表示无平滑滚动 * @param delayMillis 延迟时间 */ public void doPullRefreshing(final boolean smoothScroll, final long delayMillis) { postDelayed(new Runnable() { @Override public void run() { int newScrollValue = -mHeaderHeight; int duration = smoothScroll ? SCROLL_DURATION : 0; startRefreshing(); smoothScrollTo(newScrollValue, duration, 0); } }, delayMillis); } /** * 创建可以刷新的View * * @param context context * @param attrs 属性 * @return View */ protected abstract T createRefreshableView(Context context, AttributeSet attrs); /** * 判断刷新的View是否滑动到顶部 * * @return true表示已经滑动到顶部,否则false */ protected abstract boolean isReadyForPullDown(); /** * 创建Header的布局 * * @param context context * @param attrs 属性 * @return LoadingLayout对象 */ protected LoadingLayout createHeaderLoadingLayout(Context context, AttributeSet attrs) { return new HeaderLoadingLayout(context); } /** * 得到平滑滚动的时间,派生类可以重写这个方法来控件滚动时间 * * @return 返回值时间为毫秒 */ protected long getSmoothScrollDuration() { return SCROLL_DURATION; } /** * 计算刷新View的大小 * * @param width 当前容器的宽度 * @param height 当前容器的宽度 */ protected void refreshRefreshableViewSize(int width, int height) { if (null != mRefreshableViewWrapper) { LayoutParams lp = (LayoutParams) mRefreshableViewWrapper.getLayoutParams(); if (lp.height != height) { lp.height = height; mRefreshableViewWrapper.requestLayout(); } } } /** * 将刷新View添加到当前容器中 * * @param context context * @param refreshableView 可以刷新的View */ protected void addRefreshableView(Context context, T refreshableView) { int width = ViewGroup.LayoutParams.MATCH_PARENT; int height = ViewGroup.LayoutParams.MATCH_PARENT; // 创建一个包装容器 mRefreshableViewWrapper = new FrameLayout(context); mRefreshableViewWrapper.addView(refreshableView, width, height); // 这里把Refresh view的高度设置为一个很小的值,它的高度最终会在onSizeChanged()方法中设置为MATCH_PARENT // 这样做的原因是,如果此是它的height是MATCH_PARENT,那么footer得到的高度就是0,所以,我们先设置高度很小 // 我们就可以得到header和footer的正常高度,当onSizeChanged后,Refresh view的高度又会变为正常。 height = 10; addView(mRefreshableViewWrapper, new LayoutParams(width, height)); } /** * 添加Header和Footer * * @param context context */ protected void addHeaderAndFooter(Context context) { LayoutParams params = new LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); final LoadingLayout headerLayout = mHeaderLayout; // final LoadingLayout footerLayout = mFooterLayout; if (null != headerLayout) { if (this == headerLayout.getParent()) { removeView(headerLayout); } addView(headerLayout, 0, params); } } /** * 拉动Header Layout时调用 * * @param delta 移动的距离 */ protected void pullHeaderLayout(float delta) { // 向上滑动,并且当前scrollY为0时,不滑动 int oldScrollY = getScrollYValue(); if (delta < 0 && (oldScrollY - delta) >= 0) { setScrollTo(0, 0); return; } // 向下滑动布局 setScrollBy(0, -(int)delta); if (null != mHeaderLayout && 0 != mHeaderHeight) { float scale = Math.abs(getScrollYValue()) / (float) mHeaderHeight; mHeaderLayout.onPull(scale); } // 未处于刷新状态,更新箭头 int scrollY = Math.abs(getScrollYValue()); if (isPullRefreshEnabled() && !isPullRefreshing()) { if (scrollY > mHeaderHeight) { mPullDownState = State.RELEASE_TO_REFRESH; } else { mPullDownState = State.PULL_TO_REFRESH; } mHeaderLayout.setState(mPullDownState); onStateChanged(mPullDownState, true); } } /** * 得置header */ protected void resetHeaderLayout() { final int scrollY = Math.abs(getScrollYValue()); final boolean refreshing = isPullRefreshing(); if (refreshing && scrollY <= mHeaderHeight) { smoothScrollTo(0); return; } if (refreshing) { smoothScrollTo(-mHeaderHeight); } else { smoothScrollTo(0); } } /** * 判断是否正在下拉刷新 * * @return true正在刷新,否则false */ protected boolean isPullRefreshing() { return (mPullDownState == State.REFRESHING); } /** * 开始刷新,当下拉松开后被调用 */ protected void startRefreshing() { // 如果正在刷新 if (isPullRefreshing()) { return; } mPullDownState = State.REFRESHING; onStateChanged(State.REFRESHING, true); if (null != mHeaderLayout) { mHeaderLayout.setState(State.REFRESHING); } if (null != mRefreshListener) { // 因为滚动回原始位置的时间是200,我们需要等回滚完后才执行刷新回调 postDelayed(new Runnable() { @Override public void run() { mRefreshListener.onPullDownToRefresh(PullToRefreshBase.this); } }, getSmoothScrollDuration()); } } /** * 当状态发生变化时调用 * * @param state 状态 * @param isPullDown 是否向下 */ protected void onStateChanged(State state, boolean isPullDown) { } /** * 设置滚动位置 * * @param x 滚动到的x位置 * @param y 滚动到的y位置 */ private void setScrollTo(int x, int y) { scrollTo(x, y); } /** * 设置滚动的偏移 * * @param x 滚动x位置 * @param y 滚动y位置 */ private void setScrollBy(int x, int y) { scrollBy(x, y); } /** * 得到当前Y的滚动值 * * @return 滚动值 */ private int getScrollYValue() { return getScrollY(); } /** * 平滑滚动 * * @param newScrollValue 滚动的值 */ private void smoothScrollTo(int newScrollValue) { smoothScrollTo(newScrollValue, getSmoothScrollDuration(), 0); } /** * 平滑滚动 * * @param newScrollValue 滚动的值 * @param duration 滚动时候 * @param delayMillis 延迟时间,0代表不延迟 */ private void smoothScrollTo(int newScrollValue, long duration, long delayMillis) { if (null != mSmoothScrollRunnable) { mSmoothScrollRunnable.stop(); } int oldScrollValue = this.getScrollYValue(); boolean post = (oldScrollValue != newScrollValue); if (post) { mSmoothScrollRunnable = new SmoothScrollRunnable(oldScrollValue, newScrollValue, duration); } if (post) { if (delayMillis > 0) { postDelayed(mSmoothScrollRunnable, delayMillis); } else { post(mSmoothScrollRunnable); } } } /** * 设置是否截断touch事件 * * @param enabled true截断,false不截断 */ private void setInterceptTouchEventEnabled(boolean enabled) { mInterceptEventEnable = enabled; } /** * 标志是否截断touch事件 * * @return true截断,false不截断 */ private boolean isInterceptTouchEventEnabled() { return mInterceptEventEnable; } /** * 实现了平滑滚动的Runnable * * @author Li Hong * @since 2013-8-22 */ final class SmoothScrollRunnable implements Runnable { /**动画效果*/ private final Interpolator mInterpolator; /**结束Y*/ private final int mScrollToY; /**开始Y*/ private final int mScrollFromY; /**滑动时间*/ private final long mDuration; /**是否继续运行*/ private boolean mContinueRunning = true; /**开始时刻*/ private long mStartTime = -1; /**当前Y*/ private int mCurrentY = -1; /** * 构造方法 * * @param fromY 开始Y * @param toY 结束Y * @param duration 动画时间 */ public SmoothScrollRunnable(int fromY, int toY, long duration) { mScrollFromY = fromY; mScrollToY = toY; mDuration = duration; mInterpolator = new DecelerateInterpolator(); } @Override public void run() { /** * If the duration is 0, we scroll the view to target y directly. */ if (mDuration <= 0) { setScrollTo(0, mScrollToY); return; } /** * Only set mStartTime if this is the first time we're starting, * else actually calculate the Y delta */ if (mStartTime == -1) { mStartTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce software float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ final long oneSecond = 1000; // SUPPRESS CHECKSTYLE long normalizedTime = (oneSecond * (System.currentTimeMillis() - mStartTime)) / mDuration; normalizedTime = Math.max(Math.min(normalizedTime, oneSecond), 0); final int deltaY = Math.round((mScrollFromY - mScrollToY) * mInterpolator.getInterpolation(normalizedTime / (float) oneSecond)); mCurrentY = mScrollFromY - deltaY; setScrollTo(0, mCurrentY); } // If we're not at the target Y, keep going... if (mContinueRunning && mScrollToY != mCurrentY) { PullToRefreshBase.this.postDelayed(this, 16);// SUPPRESS CHECKSTYLE } } /** * 停止滑动 */ public void stop() { mContinueRunning = false; removeCallbacks(this); } } }PullToRefreshListView类:
/** * 这个类实现了ListView下拉刷新 * * @author Li Hong * @since 2013-8-15 */ public class PullToRefreshListView extends PullToRefreshBase<ListView> { /**ListView*/ private ListView mListView; /** * 构造方法 * * @param context context */ public PullToRefreshListView(Context context) { this(context, null); } /** * 构造方法 * * @param context context * @param attrs attrs */ public PullToRefreshListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 构造方法 * * @param context context * @param attrs attrs * @param defStyle defStyle */ public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected ListView createRefreshableView(Context context, AttributeSet attrs) { ListView listView = new ListView(context); mListView = listView; return listView; } @Override protected boolean isReadyForPullDown() { return isFirstItemVisible(); } @Override protected LoadingLayout createHeaderLoadingLayout(Context context, AttributeSet attrs) { return new RotateLoadingLayout(context); } /** * 判断第一个child是否完全显示出来 * * @return true完全显示出来,否则false */ private boolean isFirstItemVisible() { final Adapter adapter = mListView.getAdapter(); if (null == adapter || adapter.isEmpty()) { return true; } int mostTop = (mListView.getChildCount() > 0) ? mListView.getChildAt(0).getTop() : 0; if (mostTop >= 0) { return true; } return false; } }
RotateLoadingLayout类:
/** * 这个类封装了下拉刷新的布局 * * @author Li Hong * @since 2013-7-30 */ public class RotateLoadingLayout extends LoadingLayout { /**旋转动画的时间*/ static final int ROTATION_ANIMATION_DURATION = 1200; /**动画插值*/ static final Interpolator ANIMATION_INTERPOLATOR = new LinearInterpolator(); /**Header的容器*/ private RelativeLayout mHeaderContainer; /**箭头图片*/ private ImageView mArrowImageView; /**状态提示TextView*/ private TextView mHintTextView; /**最后更新时间的TextView*/ private TextView mHeaderTimeView; /**最后更新时间的标题*/ private TextView mHeaderTimeViewTitle; /**旋转的动画*/ private Animation mRotateAnimation; /** * 构造方法 * * @param context context */ public RotateLoadingLayout(Context context) { super(context); init(); } /** * 构造方法 * * @param context context * @param attrs attrs */ public RotateLoadingLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { mHeaderContainer = (RelativeLayout) findViewById(R.id.pull_to_refresh_header_content); mArrowImageView = (ImageView) findViewById(R.id.pull_to_refresh_header_arrow); mHintTextView = (TextView) findViewById(R.id.pull_to_refresh_header_hint_textview); mHeaderTimeView = (TextView) findViewById(R.id.pull_to_refresh_header_time); mHeaderTimeViewTitle = (TextView) findViewById(R.id.pull_to_refresh_last_update_time_text); mArrowImageView.setScaleType(ScaleType.CENTER); mArrowImageView.setImageResource(R.mipmap.default_ptr_rotate); float pivotValue = 0.5f; // SUPPRESS CHECKSTYLE float toDegree = 720.0f; // SUPPRESS CHECKSTYLE mRotateAnimation = new RotateAnimation(0.0f, toDegree, Animation.RELATIVE_TO_SELF, pivotValue, Animation.RELATIVE_TO_SELF, pivotValue); mRotateAnimation.setFillAfter(true); mRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR); mRotateAnimation.setDuration(ROTATION_ANIMATION_DURATION); mRotateAnimation.setRepeatCount(Animation.INFINITE); mRotateAnimation.setRepeatMode(Animation.RESTART); } @Override protected View createLoadingView(Context context, AttributeSet attrs) { return LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header2, null); } @Override public void setLastUpdatedLabel(CharSequence label) { // 如果最后更新的时间的文本是空的话,隐藏前面的标题 mHeaderTimeViewTitle.setVisibility(TextUtils.isEmpty(label) ? View.INVISIBLE : View.VISIBLE); mHeaderTimeView.setText(label); } @Override public int getContentSize() { if (null != mHeaderContainer) { return mHeaderContainer.getHeight(); } return (int) (getResources().getDisplayMetrics().density * 60); } @Override protected void onStateChanged(State curState, State oldState) { super.onStateChanged(curState, oldState); } @Override protected void onReset() { resetRotation(); mHintTextView.setText(R.string.pull_to_refresh_header_hint_normal); } @Override protected void onReleaseToRefresh() { mHintTextView.setText(R.string.pull_to_refresh_header_hint_ready); } @Override protected void onPullToRefresh() { mHintTextView.setText(R.string.pull_to_refresh_header_hint_normal); } @Override protected void onRefreshing() { resetRotation(); mArrowImageView.startAnimation(mRotateAnimation); mHintTextView.setText(R.string.pull_to_refresh_header_hint_loading); } @Override public void onPull(float scale) { float angle = scale * 180f; // SUPPRESS CHECKSTYLE mArrowImageView.setRotation(angle); } /** * 重置动画 */ private void resetRotation() { mArrowImageView.clearAnimation(); mArrowImageView.setRotation(0); } }
最后
以上就是温婉唇彩为你收集整理的PullToRefresh的下拉刷新的全部内容,希望文章能够帮你解决PullToRefresh的下拉刷新所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复