null
.
586 | */
587 | public void setColors(@NonNull int[] colors) {
588 | mColors = colors;
589 | // if colors are reset, make sure to reset the color index as well
590 | setColorIndex(0);
591 | }
592 |
593 | /**
594 | * Set the absolute color of the progress spinner. This is should only
595 | * be used when animating between current and next color when the
596 | * spinner is rotating.
597 | *
598 | * @param color int describing the color.
599 | */
600 | public void setColor(int color) {
601 | mCurrentColor = color;
602 | }
603 |
604 | /**
605 | * @param index Index into the color array of the color to display in
606 | * the progress spinner.
607 | */
608 | public void setColorIndex(int index) {
609 | mColorIndex = index;
610 | mCurrentColor = mColors[mColorIndex];
611 | }
612 |
613 | /**
614 | * @return int describing the next color the progress spinner should use when drawing.
615 | */
616 | public int getNextColor() {
617 | return mColors[getNextColorIndex()];
618 | }
619 |
620 | private int getNextColorIndex() {
621 | return (mColorIndex + 1) % (mColors.length);
622 | }
623 |
624 | /**
625 | * Proceed to the next available ring color. This will automatically
626 | * wrap back to the beginning of colors.
627 | */
628 | public void goToNextColor() {
629 | setColorIndex(getNextColorIndex());
630 | }
631 |
632 | public void setColorFilter(ColorFilter filter) {
633 | mPaint.setColorFilter(filter);
634 | invalidateSelf();
635 | }
636 |
637 | /**
638 | * @param alpha Set the alpha of the progress spinner and associated arrowhead.
639 | */
640 | public void setAlpha(int alpha) {
641 | mAlpha = alpha;
642 | }
643 |
644 | /**
645 | * @return Current alpha of the progress spinner and arrowhead.
646 | */
647 | public int getAlpha() {
648 | return mAlpha;
649 | }
650 |
651 | /**
652 | * @param strokeWidth Set the stroke width of the progress spinner in pixels.
653 | */
654 | public void setStrokeWidth(float strokeWidth) {
655 | mStrokeWidth = strokeWidth;
656 | mPaint.setStrokeWidth(strokeWidth);
657 | invalidateSelf();
658 | }
659 |
660 | @SuppressWarnings("unused")
661 | public float getStrokeWidth() {
662 | return mStrokeWidth;
663 | }
664 |
665 | @SuppressWarnings("unused")
666 | public void setStartTrim(float startTrim) {
667 | mStartTrim = startTrim;
668 | invalidateSelf();
669 | }
670 |
671 | @SuppressWarnings("unused")
672 | public float getStartTrim() {
673 | return mStartTrim;
674 | }
675 |
676 | public float getStartingStartTrim() {
677 | return mStartingStartTrim;
678 | }
679 |
680 | public float getStartingEndTrim() {
681 | return mStartingEndTrim;
682 | }
683 |
684 | public int getStartingColor() {
685 | return mColors[mColorIndex];
686 | }
687 |
688 | @SuppressWarnings("unused")
689 | public void setEndTrim(float endTrim) {
690 | mEndTrim = endTrim;
691 | invalidateSelf();
692 | }
693 |
694 | @SuppressWarnings("unused")
695 | public float getEndTrim() {
696 | return mEndTrim;
697 | }
698 |
699 | @SuppressWarnings("unused")
700 | public void setRotation(float rotation) {
701 | mRotation = rotation;
702 | invalidateSelf();
703 | }
704 |
705 | @SuppressWarnings("unused")
706 | public float getRotation() {
707 | return mRotation;
708 | }
709 |
710 | public void setInsets(int width, int height) {
711 | final float minEdge = (float) Math.min(width, height);
712 | float insets;
713 | if (mRingCenterRadius <= 0 || minEdge < 0) {
714 | insets = (float) Math.ceil(mStrokeWidth / 2.0f);
715 | } else {
716 | insets = (float) (minEdge / 2.0f - mRingCenterRadius);
717 | }
718 | mStrokeInset = insets;
719 | }
720 |
721 | @SuppressWarnings("unused")
722 | public float getInsets() {
723 | return mStrokeInset;
724 | }
725 |
726 | /**
727 | * @param centerRadius Inner radius in px of the circle the progress
728 | * spinner arc traces.
729 | */
730 | public void setCenterRadius(double centerRadius) {
731 | mRingCenterRadius = centerRadius;
732 | }
733 |
734 | public double getCenterRadius() {
735 | return mRingCenterRadius;
736 | }
737 |
738 | /**
739 | * @param show Set to true to show the arrow head on the progress spinner.
740 | */
741 | public void setShowArrow(boolean show) {
742 | if (mShowArrow != show) {
743 | mShowArrow = show;
744 | invalidateSelf();
745 | }
746 | }
747 |
748 | /**
749 | * @param scale Set the scale of the arrowhead for the spinner.
750 | */
751 | public void setArrowScale(float scale) {
752 | if (scale != mArrowScale) {
753 | mArrowScale = scale;
754 | invalidateSelf();
755 | }
756 | }
757 |
758 | /**
759 | * @return The amount the progress spinner is currently rotated, between [0..1].
760 | */
761 | public float getStartingRotation() {
762 | return mStartingRotation;
763 | }
764 |
765 | /**
766 | * If the start / end trim are offset to begin with, store them so that
767 | * animation starts from that offset.
768 | */
769 | public void storeOriginals() {
770 | mStartingStartTrim = mStartTrim;
771 | mStartingEndTrim = mEndTrim;
772 | mStartingRotation = mRotation;
773 | }
774 |
775 | /**
776 | * Reset the progress spinner to default rotation, start and end angles.
777 | */
778 | public void resetOriginals() {
779 | mStartingStartTrim = 0;
780 | mStartingEndTrim = 0;
781 | mStartingRotation = 0;
782 | setStartTrim(0);
783 | setEndTrim(0);
784 | setRotation(0);
785 | }
786 |
787 | private void invalidateSelf() {
788 | mCallback.invalidateDrawable(null);
789 | }
790 | }
791 | }
792 |
--------------------------------------------------------------------------------
/springheader/src/main/java/com/loopeer/springheader/RefreshHeader.java:
--------------------------------------------------------------------------------
1 | package com.loopeer.springheader;
2 |
3 | import android.content.Context;
4 | import android.support.design.widget.CoordinatorLayout;
5 | import android.util.AttributeSet;
6 | import android.view.ViewGroup;
7 | import android.widget.FrameLayout;
8 |
9 | @CoordinatorLayout.DefaultBehavior(SpringHeaderBehavior.class)
10 | public class RefreshHeader extends FrameLayout implements SpringHeaderBehavior.SpringHeaderCallback {
11 |
12 | private SpringHeaderBehavior mBehavior;
13 |
14 | private OnRefreshListener mOnRefreshListener;
15 |
16 | public RefreshHeader(Context context) {
17 | this(context, null);
18 | }
19 |
20 | public RefreshHeader(Context context, AttributeSet attrs) {
21 | this(context, attrs, 0);
22 | }
23 |
24 | public RefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
25 | super(context, attrs, defStyleAttr);
26 | }
27 |
28 | @Override
29 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
30 | super.onLayout(changed, left, top, right, bottom);
31 | ViewGroup.LayoutParams lp = getLayoutParams();
32 | if (lp instanceof CoordinatorLayout.LayoutParams) {
33 | CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) lp).getBehavior();
34 | if (behavior instanceof SpringHeaderBehavior) {
35 | mBehavior = (SpringHeaderBehavior) behavior;
36 | mBehavior.setSpringHeaderCallback(this);
37 | }
38 | }
39 | }
40 |
41 | @Override
42 | public void onScroll(int offset, float fraction) {
43 | }
44 |
45 | @Override
46 | public void onStateChanged(int newState) {
47 | if (newState == SpringHeaderBehavior.STATE_HOVERING) {
48 | if (mOnRefreshListener != null) {
49 | mOnRefreshListener.onRefresh();
50 | }
51 | }
52 | }
53 |
54 | public void setRefreshing(boolean refreshing) {
55 | if (mBehavior != null) {
56 | mBehavior.setState(refreshing ? SpringHeaderBehavior.STATE_HOVERING
57 | : SpringHeaderBehavior.STATE_COLLAPSED);
58 | }
59 | }
60 |
61 | public void setOnRefreshListener(OnRefreshListener listener) {
62 | mOnRefreshListener = listener;
63 | }
64 |
65 | public interface OnRefreshListener {
66 | void onRefresh();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/springheader/src/main/java/com/loopeer/springheader/SimpleRefreshHeader.java:
--------------------------------------------------------------------------------
1 | package com.loopeer.springheader;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.support.annotation.ColorInt;
6 | import android.support.annotation.ColorRes;
7 | import android.support.annotation.StringRes;
8 | import android.support.v4.content.ContextCompat;
9 | import android.util.AttributeSet;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | public class SimpleRefreshHeader extends RefreshHeader {
14 |
15 | private TextView mText;
16 | private MaterialProgressDrawable mProgress;
17 |
18 | private CharSequence mTextBelowThreshold;
19 | private CharSequence mTextAboveThreshold;
20 | private CharSequence mTextRefreshing;
21 |
22 | private int mOldState;
23 |
24 | private boolean mBelowThreshold;
25 |
26 | public SimpleRefreshHeader(Context context) {
27 | this(context, null);
28 | }
29 |
30 | public SimpleRefreshHeader(Context context, AttributeSet attrs) {
31 | this(context, attrs, 0);
32 | }
33 |
34 | public SimpleRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
35 | super(context, attrs, defStyleAttr);
36 |
37 | inflate(context, R.layout.refresh_header_simple, this);
38 |
39 | ImageView icon = (ImageView) findViewById(android.R.id.icon);
40 | mText = (TextView) findViewById(android.R.id.text1);
41 |
42 | mProgress = new MaterialProgressDrawable(getContext(), this);
43 | mProgress.setAlpha(255);
44 | icon.setImageDrawable(mProgress);
45 |
46 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleRefreshHeader, defStyleAttr, 0);
47 | CharSequence text = a.getText(R.styleable.SimpleRefreshHeader_textBelowThreshold);
48 | if (text != null) {
49 | setTextBelowThreshold(text);
50 | } else {
51 | setTextBelowThreshold(R.string.simple_refresh_header_text_below_threshold);
52 | }
53 | text = a.getText(R.styleable.SimpleRefreshHeader_textAboveThreshold);
54 | if (text != null) {
55 | setTextAboveThreshold(text);
56 | } else {
57 | setTextAboveThreshold(R.string.simple_refresh_header_text_above_threshold);
58 | }
59 | text = a.getText(R.styleable.SimpleRefreshHeader_textRefreshing);
60 | if (text != null) {
61 | setTextRefreshing(text);
62 | } else {
63 | setTextRefreshing(R.string.simple_refresh_header_text_refreshing);
64 | }
65 | a.recycle();
66 | }
67 |
68 | public void setColorSchemeResources(@ColorRes int... colorResIds) {
69 | int[] colorRes = new int[colorResIds.length];
70 | for (int i = 0; i < colorResIds.length; i++) {
71 | colorRes[i] = ContextCompat.getColor(getContext(), colorResIds[i]);
72 | }
73 | setColorSchemeColors(colorRes);
74 | }
75 |
76 | public void setColorSchemeColors(@ColorInt int... colors) {
77 | mProgress.setColorSchemeColors(colors);
78 | }
79 |
80 | public void setTextBelowThreshold(CharSequence textBelowThreshold) {
81 | mTextBelowThreshold = textBelowThreshold;
82 | }
83 |
84 | public void setTextBelowThreshold(@StringRes int textBelowThreshold) {
85 | mTextBelowThreshold = getResources().getText(textBelowThreshold);
86 | }
87 |
88 | public void setTextAboveThreshold(CharSequence textAboveThreshold) {
89 | mTextAboveThreshold = textAboveThreshold;
90 | }
91 |
92 | public void setTextAboveThreshold(@StringRes int textAboveThreshold) {
93 | mTextAboveThreshold = getResources().getText(textAboveThreshold);
94 | }
95 |
96 | public void setTextRefreshing(CharSequence textRefreshing) {
97 | mTextRefreshing = textRefreshing;
98 | }
99 |
100 | public void setTextRefreshing(@StringRes int textRefreshing) {
101 | mTextRefreshing = getResources().getText(textRefreshing);
102 | }
103 |
104 | @Override
105 | public void onScroll(int offset, float fraction) {
106 | super.onScroll(offset, fraction);
107 |
108 | boolean belowThreshold = fraction < 1;
109 | if (belowThreshold != mBelowThreshold) {
110 | mBelowThreshold = belowThreshold;
111 | mText.setText(belowThreshold ? mTextBelowThreshold : mTextAboveThreshold);
112 | }
113 |
114 | mProgress.showArrow(true);
115 | float clampedFraction = Math.min(1, fraction);
116 | mProgress.setStartEndTrim(0, 0.8f * clampedFraction);
117 | mProgress.setArrowScale(clampedFraction);
118 | mProgress.setProgressRotation(fraction + 0.1f);
119 | }
120 |
121 | @Override
122 | public void onStateChanged(int newState) {
123 | super.onStateChanged(newState);
124 | if (newState == SpringHeaderBehavior.STATE_HOVERING) {
125 | mText.setText(mTextRefreshing);
126 | mProgress.start();
127 | } else if (mOldState == SpringHeaderBehavior.STATE_HOVERING) {
128 | mProgress.stop();
129 | }
130 | mOldState = newState;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/springheader/src/main/java/com/loopeer/springheader/SpringHeaderBehavior.java:
--------------------------------------------------------------------------------
1 | package com.loopeer.springheader;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ValueAnimator;
6 | import android.content.Context;
7 | import android.content.res.TypedArray;
8 | import android.support.design.widget.CoordinatorLayout;
9 | import android.support.v4.view.ViewCompat;
10 | import android.util.AttributeSet;
11 | import android.view.View;
12 | import android.view.animation.DecelerateInterpolator;
13 |
14 | public class SpringHeaderBehavior extends ViewOffsetBehavior
29 | * Also the setting of absolute offsets (similar to translationX/Y), rather than additive
30 | * offsets.
31 | */
32 | class ViewOffsetHelper {
33 |
34 | private final View mView;
35 |
36 | private int mLayoutTop;
37 | private int mLayoutLeft;
38 | private int mOffsetTop;
39 | private int mOffsetLeft;
40 |
41 | public ViewOffsetHelper(View view) {
42 | mView = view;
43 | }
44 |
45 | public void onViewLayout() {
46 | // Now grab the intended top
47 | mLayoutTop = mView.getTop();
48 | mLayoutLeft = mView.getLeft();
49 |
50 | // And offset it as needed
51 | updateOffsets();
52 | }
53 |
54 | private void updateOffsets() {
55 | ViewCompat.offsetTopAndBottom(mView, mOffsetTop - (mView.getTop() - mLayoutTop));
56 | ViewCompat.offsetLeftAndRight(mView, mOffsetLeft - (mView.getLeft() - mLayoutLeft));
57 |
58 | // Manually invalidate the view and parent to make sure we get drawn pre-M
59 | if (Build.VERSION.SDK_INT < 23) {
60 | tickleInvalidationFlag(mView);
61 | final ViewParent vp = mView.getParent();
62 | if (vp instanceof View) {
63 | tickleInvalidationFlag((View) vp);
64 | }
65 | }
66 | }
67 |
68 | private static void tickleInvalidationFlag(View view) {
69 | final float x = ViewCompat.getTranslationX(view);
70 | ViewCompat.setTranslationY(view, x + 1);
71 | ViewCompat.setTranslationY(view, x);
72 | }
73 |
74 | /**
75 | * Set the top and bottom offset for this {@link android.support.design.widget.ViewOffsetHelper}'s view.
76 | *
77 | * @param offset the offset in px.
78 | * @return true if the offset has changed
79 | */
80 | public boolean setTopAndBottomOffset(int offset) {
81 | if (mOffsetTop != offset) {
82 | mOffsetTop = offset;
83 | updateOffsets();
84 | return true;
85 | }
86 | return false;
87 | }
88 |
89 | /**
90 | * Set the left and right offset for this {@link android.support.design.widget.ViewOffsetHelper}'s view.
91 | *
92 | * @param offset the offset in px.
93 | * @return true if the offset has changed
94 | */
95 | public boolean setLeftAndRightOffset(int offset) {
96 | if (mOffsetLeft != offset) {
97 | mOffsetLeft = offset;
98 | updateOffsets();
99 | return true;
100 | }
101 | return false;
102 | }
103 |
104 | public int getTopAndBottomOffset() {
105 | return mOffsetTop;
106 | }
107 |
108 | public int getLeftAndRightOffset() {
109 | return mOffsetLeft;
110 | }
111 | }
--------------------------------------------------------------------------------
/springheader/src/main/res/layout/refresh_header_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |