首页>代码>Android下拉刷新,上拉加载>/PullToRefresh/src/com/stay/pull/lib/PullToRefreshBase.java
001package com.stay.pull.lib;
002 
003import android.content.Context;
004import android.content.res.TypedArray;
005import android.graphics.Color;
006import android.os.Handler;
007import android.util.AttributeSet;
008import android.view.MotionEvent;
009import android.view.View;
010import android.view.ViewConfiguration;
011import android.view.ViewGroup;
012import android.view.animation.AccelerateDecelerateInterpolator;
013import android.view.animation.Interpolator;
014import android.widget.LinearLayout;
015 
016import com.stay.pull.R;
017import com.stay.pull.lib.internal.LoadingLayout;
018 
019public abstract class PullToRefreshBase<T extends View> extends LinearLayout {
020 
021    final class SmoothScrollRunnable implements Runnable {
022 
023        static final int ANIMATION_DURATION_MS = 190;
024        static final int ANIMATION_FPS = 1000 / 60;
025 
026        private final Interpolator interpolator;
027        private final int scrollToY;
028        private final int scrollFromY;
029        private final Handler handler;
030 
031        private boolean continueRunning = true;
032        private long startTime = -1;
033        private int currentY = -1;
034 
035        public SmoothScrollRunnable(Handler handler, int fromY, int toY) {
036            this.handler = handler;
037            this.scrollFromY = fromY;
038            this.scrollToY = toY;
039            this.interpolator = new AccelerateDecelerateInterpolator();
040        }
041 
042        @Override
043        public void run() {
044 
045            /**
046             * Only set startTime if this is the first time we're starting, else
047             * actually calculate the Y delta
048             */
049            if (startTime == -1) {
050                startTime = System.currentTimeMillis();
051            } else {
052 
053                /**
054                 * We do do all calculations in long to reduce software float
055                 * calculations. We use 1000 as it gives us good accuracy and
056                 * small rounding errors
057                 */
058                long normalizedTime = (1000 * (System.currentTimeMillis() - startTime)) / ANIMATION_DURATION_MS;
059                normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0);
060 
061                final int deltaY = Math.round((scrollFromY - scrollToY)
062                        * interpolator.getInterpolation(normalizedTime / 1000f));
063                this.currentY = scrollFromY - deltaY;
064                setHeaderScroll(currentY);
065            }
066 
067            // If we're not at the target Y, keep going...
068            if (continueRunning && scrollToY != currentY) {
069                handler.postDelayed(this, ANIMATION_FPS);
070            }
071        }
072 
073        public void stop() {
074            this.continueRunning = false;
075            this.handler.removeCallbacks(this);
076        }
077    };
078 
079    // ===========================================================
080    // Constants
081    // ===========================================================
082 
083    static final float FRICTION = 2.0f;
084 
085    static final int PULL_TO_REFRESH = 0x0;
086    static final int RELEASE_TO_REFRESH = 0x1;
087    static final int REFRESHING = 0x2;
088    static final int MANUAL_REFRESHING = 0x3;
089 
090    public static final int MODE_PULL_DOWN_TO_REFRESH = 0x1;
091    public static final int MODE_PULL_UP_TO_REFRESH = 0x2;
092    public static final int MODE_BOTH = 0x3;
093 
094    // ===========================================================
095    // Fields
096    // ===========================================================
097 
098    private int touchSlop;
099 
100    private float initialMotionY;
101    private float lastMotionX;
102    private float lastMotionY;
103    private boolean isBeingDragged = false;
104 
105    private int state = PULL_TO_REFRESH;
106    private int mode = MODE_PULL_DOWN_TO_REFRESH;
107    private int currentMode;
108 
109    private boolean disableScrollingWhileRefreshing = true;
110 
111    T refreshableView;
112    private boolean isPullToRefreshEnabled = true;
113 
114    private LoadingLayout headerLayout;
115    private LoadingLayout footerLayout;
116    private int headerHeight;
117 
118    private final Handler handler = new Handler();
119 
120    private OnRefreshListener onRefreshListener;
121 
122    private SmoothScrollRunnable currentSmoothScrollRunnable;
123 
124    // ===========================================================
125    // Constructors
126    // ===========================================================
127 
128    public PullToRefreshBase(Context context) {
129        super(context);
130        init(context, null);
131    }
132 
133    public PullToRefreshBase(Context context, int mode) {
134        super(context);
135        this.mode = mode;
136        init(context, null);
137    }
138 
139    public PullToRefreshBase(Context context, AttributeSet attrs) {
140        super(context, attrs);
141        init(context, attrs);
142    }
143 
144    // ===========================================================
145    // Getter & Setter
146    // ===========================================================
147 
148    /**
149     * Deprecated. Use {@link #getRefreshableView()} from now on.
150     *
151     * @deprecated
152     * @return The Refreshable View which is currently wrapped
153     */
154    public final T getAdapterView() {
155        return refreshableView;
156    }
157 
158    /**
159     * Get the Wrapped Refreshable View. Anything returned here has already been
160     * added to the content view.
161     *
162     * @return The View which is currently wrapped
163     */
164    public final T getRefreshableView() {
165        return refreshableView;
166    }
167 
168    /**
169     * Whether Pull-to-Refresh is enabled
170     *
171     * @return enabled
172     */
173    public final boolean isPullToRefreshEnabled() {
174        return isPullToRefreshEnabled;
175    }
176 
177    /**
178     * Returns whether the widget has disabled scrolling on the Refreshable View
179     * while refreshing.
180     *
181     * @param true if the widget has disabled scrolling while refreshing
182     */
183    public final boolean isDisableScrollingWhileRefreshing() {
184        return disableScrollingWhileRefreshing;
185    }
186 
187    /**
188     * Returns whether the Widget is currently in the Refreshing state
189     *
190     * @return true if the Widget is currently refreshing
191     */
192    public final boolean isRefreshing() {
193        return state == REFRESHING || state == MANUAL_REFRESHING;
194    }
195 
196    /**
197     * By default the Widget disabled scrolling on the Refreshable View while
198     * refreshing. This method can change this behaviour.
199     *
200     * @param disableScrollingWhileRefreshing
201     *            - true if you want to disable scrolling while refreshing
202     */
203    public final void setDisableScrollingWhileRefreshing(boolean disableScrollingWhileRefreshing) {
204        this.disableScrollingWhileRefreshing = disableScrollingWhileRefreshing;
205    }
206 
207    /**
208     * Mark the current Refresh as complete. Will Reset the UI and hide the
209     * Refreshing View
210     */
211    public final void onRefreshComplete() {
212        if (state != PULL_TO_REFRESH) {
213            resetHeader();
214        }
215    }
216 
217    /**
218     * Set OnRefreshListener for the Widget
219     *
220     * @param listener
221     *            - Listener to be used when the Widget is set to Refresh
222     */
223    public final void setOnRefreshListener(OnRefreshListener listener) {
224        onRefreshListener = listener;
225    }
226 
227    /**
228     * A mutator to enable/disable Pull-to-Refresh for the current View
229     *
230     * @param enable
231     *            Whether Pull-To-Refresh should be used
232     */
233    public final void setPullToRefreshEnabled(boolean enable) {
234        this.isPullToRefreshEnabled = enable;
235    }
236 
237    /**
238     * Set Text to show when the Widget is being pulled, and will refresh when
239     * released
240     *
241     * @param releaseLabel
242     *            - String to display
243     */
244    public void setReleaseLabel(String releaseLabel) {
245        if (null != headerLayout) {
246            headerLayout.setReleaseLabel(releaseLabel);
247        }
248        if (null != footerLayout) {
249            footerLayout.setReleaseLabel(releaseLabel);
250        }
251    }
252 
253    /**
254     * Set Text to show when the Widget is being Pulled
255     *
256     * @param pullLabel
257     *            - String to display
258     */
259    public void setPullLabel(String pullLabel) {
260        if (null != headerLayout) {
261            headerLayout.setPullLabel(pullLabel);
262        }
263        if (null != footerLayout) {
264            footerLayout.setPullLabel(pullLabel);
265        }
266    }
267 
268    /**
269     * Set Text to show when the Widget is refreshing
270     *
271     * @param refreshingLabel
272     *            - String to display
273     */
274    public void setRefreshingLabel(String refreshingLabel) {
275        if (null != headerLayout) {
276            headerLayout.setRefreshingLabel(refreshingLabel);
277        }
278        if (null != footerLayout) {
279            footerLayout.setRefreshingLabel(refreshingLabel);
280        }
281    }
282 
283    public final void setRefreshing() {
284        this.setRefreshing(true);
285    }
286 
287    /**
288     * Sets the Widget to be in the refresh state. The UI will be updated to
289     * show the 'Refreshing' view.
290     *
291     * @param doScroll
292     *            - true if you want to force a scroll to the Refreshing view.
293     */
294    public final void setRefreshing(boolean doScroll) {
295        if (!isRefreshing()) {
296            setRefreshingInternal(doScroll);
297            state = MANUAL_REFRESHING;
298        }
299    }
300 
301    public final boolean hasPullFromTop() {
302        return currentMode != MODE_PULL_UP_TO_REFRESH;
303    }
304 
305    // ===========================================================
306    // Methods for/from SuperClass/Interfaces
307    // ===========================================================
308 
309    @Override
310    public final boolean onTouchEvent(MotionEvent event) {
311        if (!isPullToRefreshEnabled) {
312            return false;
313        }
314 
315        if (isRefreshing() && disableScrollingWhileRefreshing) {
316            return true;
317        }
318 
319        if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
320            return false;
321        }
322 
323        switch (event.getAction()) {
324 
325            case MotionEvent.ACTION_MOVE: {
326                if (isBeingDragged) {
327                    lastMotionY = event.getY();
328                    this.pullEvent();
329                    return true;
330                }
331                break;
332            }
333 
334            case MotionEvent.ACTION_DOWN: {
335                if (isReadyForPull()) {
336                    lastMotionY = initialMotionY = event.getY();
337                    return true;
338                }
339                break;
340            }
341 
342            case MotionEvent.ACTION_CANCEL:
343            case MotionEvent.ACTION_UP: {
344                if (isBeingDragged) {
345                    isBeingDragged = false;
346 
347                    if (state == RELEASE_TO_REFRESH && null != onRefreshListener) {
348                        setRefreshingInternal(true);
349                        onRefreshListener.onRefresh();
350                    } else {
351                        smoothScrollTo(0);
352                    }
353                    return true;
354                }
355                break;
356            }
357        }
358 
359        return false;
360    }
361 
362    @Override
363    public final boolean onInterceptTouchEvent(MotionEvent event) {
364 
365        if (!isPullToRefreshEnabled) {
366            return false;
367        }
368 
369        if (isRefreshing() && disableScrollingWhileRefreshing) {
370            return true;
371        }
372 
373        final int action = event.getAction();
374 
375        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
376            isBeingDragged = false;
377            return false;
378        }
379 
380        if (action != MotionEvent.ACTION_DOWN && isBeingDragged) {
381            return true;
382        }
383 
384        switch (action) {
385            case MotionEvent.ACTION_MOVE: {
386                if (isReadyForPull()) {
387 
388                    final float y = event.getY();
389                    final float dy = y - lastMotionY;
390                    final float yDiff = Math.abs(dy);
391                    final float xDiff = Math.abs(event.getX() - lastMotionX);
392 
393                    if (yDiff > touchSlop && yDiff > xDiff) {
394                        if ((mode == MODE_PULL_DOWN_TO_REFRESH || mode == MODE_BOTH) && dy >= 0.0001f
395                                && isReadyForPullDown()) {
396                            lastMotionY = y;
397                            isBeingDragged = true;
398                            if (mode == MODE_BOTH) {
399                                currentMode = MODE_PULL_DOWN_TO_REFRESH;
400                            }
401                        } else if ((mode == MODE_PULL_UP_TO_REFRESH || mode == MODE_BOTH) && dy <= 0.0001f
402                                && isReadyForPullUp()) {
403                            lastMotionY = y;
404                            isBeingDragged = true;
405                            if (mode == MODE_BOTH) {
406                                currentMode = MODE_PULL_UP_TO_REFRESH;
407                            }
408                        }
409                    }
410                }
411                break;
412            }
413            case MotionEvent.ACTION_DOWN: {
414                if (isReadyForPull()) {
415                    lastMotionY = initialMotionY = event.getY();
416                    lastMotionX = event.getX();
417                    isBeingDragged = false;
418                }
419                break;
420            }
421        }
422 
423        return isBeingDragged;
424    }
425 
426    protected void addRefreshableView(Context context, T refreshableView) {
427        addView(refreshableView, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, 0, 1.0f));
428    }
429 
430    /**
431     * This is implemented by derived classes to return the created View. If you
432     * need to use a custom View (such as a custom ListView), override this
433     * method and return an instance of your custom class.
434     *
435     * Be sure to set the ID of the view in this method, especially if you're
436     * using a ListActivity or ListFragment.
437     *
438     * @param context
439     * @param attrs
440     *            AttributeSet from wrapped class. Means that anything you
441     *            include in the XML layout declaration will be routed to the
442     *            created View
443     * @return New instance of the Refreshable View
444     */
445    protected abstract T createRefreshableView(Context context, AttributeSet attrs);
446 
447    protected final int getCurrentMode() {
448        return currentMode;
449    }
450 
451    protected final LoadingLayout getFooterLayout() {
452        return footerLayout;
453    }
454 
455    protected final LoadingLayout getHeaderLayout() {
456        return headerLayout;
457    }
458 
459    protected final int getHeaderHeight() {
460        return headerHeight;
461    }
462 
463    protected final int getMode() {
464        return mode;
465    }
466 
467    /**
468     * Implemented by derived class to return whether the View is in a state
469     * where the user can Pull to Refresh by scrolling down.
470     *
471     * @return true if the View is currently the correct state (for example, top
472     *         of a ListView)
473     */
474    protected abstract boolean isReadyForPullDown();
475 
476    /**
477     * Implemented by derived class to return whether the View is in a state
478     * where the user can Pull to Refresh by scrolling up.
479     *
480     * @return true if the View is currently in the correct state (for example,
481     *         bottom of a ListView)
482     */
483    protected abstract boolean isReadyForPullUp();
484 
485    // ===========================================================
486    // Methods
487    // ===========================================================
488 
489    protected void resetHeader() {
490        state = PULL_TO_REFRESH;
491        isBeingDragged = false;
492 
493        if (null != headerLayout) {
494            headerLayout.reset();
495        }
496        if (null != footerLayout) {
497            footerLayout.reset();
498        }
499 
500        smoothScrollTo(0);
501    }
502 
503    protected void setRefreshingInternal(boolean doScroll) {
504        state = REFRESHING;
505 
506        if (null != headerLayout) {
507            headerLayout.refreshing();
508        }
509        if (null != footerLayout) {
510            footerLayout.refreshing();
511        }
512 
513        if (doScroll) {
514            smoothScrollTo(currentMode == MODE_PULL_DOWN_TO_REFRESH ? -headerHeight : headerHeight);
515        }
516    }
517 
518    protected final void setHeaderScroll(int y) {
519        scrollTo(0, y);
520    }
521 
522    protected final void smoothScrollTo(int y) {
523        if (null != currentSmoothScrollRunnable) {
524            currentSmoothScrollRunnable.stop();
525        }
526 
527        if (this.getScrollY() != y) {
528            this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getScrollY(), y);
529            handler.post(currentSmoothScrollRunnable);
530        }
531    }
532 
533    private void init(Context context, AttributeSet attrs) {
534 
535        setOrientation(LinearLayout.VERTICAL);
536 
537        touchSlop = ViewConfiguration.getTouchSlop();
538 
539        // Styleables from XML
540        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullToRefresh);
541        if (a.hasValue(R.styleable.PullToRefresh_mode)) {
542            mode = a.getInteger(R.styleable.PullToRefresh_mode, MODE_PULL_DOWN_TO_REFRESH);
543        }
544 
545        // Refreshable View
546        // By passing the attrs, we can add ListView/GridView params via XML
547        refreshableView = this.createRefreshableView(context, attrs);
548        this.addRefreshableView(context, refreshableView);
549 
550        // Loading View Strings
551        String pullLabel = context.getString(R.string.pull_to_refresh_pull_label);
552        String refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label);
553        String releaseLabel = context.getString(R.string.pull_to_refresh_release_label);
554 
555        // Add Loading Views
556        if (mode == MODE_PULL_DOWN_TO_REFRESH || mode == MODE_BOTH) {
557            headerLayout = new LoadingLayout(context, MODE_PULL_DOWN_TO_REFRESH, releaseLabel, pullLabel,
558                    refreshingLabel);
559            addView(headerLayout, 0, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
560                    ViewGroup.LayoutParams.WRAP_CONTENT));
561            measureView(headerLayout);
562            headerHeight = headerLayout.getMeasuredHeight();
563        }
564        if (mode == MODE_PULL_UP_TO_REFRESH || mode == MODE_BOTH) {
565            footerLayout = new LoadingLayout(context, MODE_PULL_UP_TO_REFRESH, releaseLabel, pullLabel, refreshingLabel);
566            addView(footerLayout, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
567                    ViewGroup.LayoutParams.WRAP_CONTENT));
568            measureView(footerLayout);
569            headerHeight = footerLayout.getMeasuredHeight();
570        }
571 
572        // Styleables from XML
573        if (a.hasValue(R.styleable.PullToRefresh_headerTextColor)) {
574            final int color = a.getColor(R.styleable.PullToRefresh_headerTextColor, Color.BLACK);
575            if (null != headerLayout) {
576                headerLayout.setTextColor(color);
577            }
578            if (null != footerLayout) {
579                footerLayout.setTextColor(color);
580            }
581        }
582        if (a.hasValue(R.styleable.PullToRefresh_headerBackground)) {
583            this.setBackgroundResource(a.getResourceId(R.styleable.PullToRefresh_headerBackground, Color.WHITE));
584        }
585        if (a.hasValue(R.styleable.PullToRefresh_adapterViewBackground)) {
586            refreshableView.setBackgroundResource(a.getResourceId(R.styleable.PullToRefresh_adapterViewBackground,
587                    Color.WHITE));
588        }
589        a.recycle();
590 
591        // Hide Loading Views
592        switch (mode) {
593            case MODE_BOTH:
594                setPadding(0, -headerHeight, 0, -headerHeight);
595                break;
596            case MODE_PULL_UP_TO_REFRESH:
597                setPadding(0, 0, 0, -headerHeight);
598                break;
599            case MODE_PULL_DOWN_TO_REFRESH:
600            default:
601                setPadding(0, -headerHeight, 0, 0);
602                break;
603        }
604 
605        // If we're not using MODE_BOTH, then just set currentMode to current
606        // mode
607        if (mode != MODE_BOTH) {
608            currentMode = mode;
609        }
610    }
611 
612    private void measureView(View child) {
613        ViewGroup.LayoutParams p = child.getLayoutParams();
614        if (p == null) {
615            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
616        }
617 
618        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
619        int lpHeight = p.height;
620        int childHeightSpec;
621        if (lpHeight > 0) {
622            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
623        } else {
624            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
625        }
626        child.measure(childWidthSpec, childHeightSpec);
627    }
628 
629    /**
630     * Actions a Pull Event
631     *
632     * @return true if the Event has been handled, false if there has been no
633     *         change
634     */
635    private boolean pullEvent() {
636 
637        final int newHeight;
638        final int oldHeight = this.getScrollY();
639 
640        switch (currentMode) {
641            case MODE_PULL_UP_TO_REFRESH:
642                newHeight = Math.round(Math.max(initialMotionY - lastMotionY, 0) / FRICTION);
643//              newHeight = Math.round((initialMotionY - lastMotionY) / FRICTION);
644                break;
645            case MODE_PULL_DOWN_TO_REFRESH:
646            default:
647                newHeight = Math.round(Math.min(initialMotionY - lastMotionY, 0) / FRICTION);
648//              newHeight = Math.round((initialMotionY - lastMotionY) / FRICTION);
649                break;
650        }
651 
652        setHeaderScroll(newHeight);
653 
654        if (newHeight != 0) {
655            if (state == PULL_TO_REFRESH && headerHeight < Math.abs(newHeight)) {
656                state = RELEASE_TO_REFRESH;
657 
658                switch (currentMode) {
659                    case MODE_PULL_UP_TO_REFRESH:
660                        footerLayout.releaseToRefresh();
661                        break;
662                    case MODE_PULL_DOWN_TO_REFRESH:
663                        headerLayout.releaseToRefresh();
664                        break;
665                }
666 
667                return true;
668 
669            } else if (state == RELEASE_TO_REFRESH && headerHeight >= Math.abs(newHeight)) {
670                state = PULL_TO_REFRESH;
671 
672                switch (currentMode) {
673                    case MODE_PULL_UP_TO_REFRESH:
674                        footerLayout.pullToRefresh();
675                        break;
676                    case MODE_PULL_DOWN_TO_REFRESH:
677                        headerLayout.pullToRefresh();
678                        break;
679                }
680 
681                return true;
682            }
683        }
684 
685        return oldHeight != newHeight;
686    }
687 
688    private boolean isReadyForPull() {
689        switch (mode) {
690            case MODE_PULL_DOWN_TO_REFRESH:
691                return isReadyForPullDown();
692            case MODE_PULL_UP_TO_REFRESH:
693                return isReadyForPullUp();
694            case MODE_BOTH:
695                return isReadyForPullUp() || isReadyForPullDown();
696        }
697        return false;
698    }
699 
700    // ===========================================================
701    // Inner and Anonymous Classes
702    // ===========================================================
703 
704    public static interface OnRefreshListener {
705 
706        public void onRefresh();
707 
708    }
709 
710    public static interface OnLastItemVisibleListener {
711 
712        public void onLastItemVisible();
713 
714    }
715 
716    @Override
717    public void setLongClickable(boolean longClickable) {
718        getRefreshableView().setLongClickable(longClickable);
719    }
720}
最近下载更多
rockit  LV1 2021年10月13日
1234mama  LV19 2020年5月17日
1684302694  LV1 2020年2月7日
女方面  LV18 2019年11月5日
小肥羊  LV16 2019年7月24日
刘银莲莲莲  LV2 2019年5月20日
s1499371754  LV3 2019年5月15日
wangshihua  LV19 2019年5月7日
cyanno  LV1 2018年6月4日
bduntliuhui2008  LV7 2017年11月28日
最近浏览更多
萌了个乖乖  LV12 2022年5月20日
rockit  LV1 2021年10月13日
1670153475  LV4 2021年3月11日
a566566  LV9 2020年12月29日
一定要好好学习啊 2020年6月15日
暂无贡献等级
subject  LV6 2020年6月10日
aaa5849310  LV25 2020年5月25日
1234mama  LV19 2020年5月17日
tsslb1 2020年5月11日
暂无贡献等级
yangjiayangjia  LV3 2020年4月27日
顶部 客服 微信二维码 底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友