1010import android .graphics .Path ;
1111import android .graphics .Point ;
1212import android .os .Build ;
13+ import android .support .annotation .FloatRange ;
1314import android .support .annotation .IntDef ;
14- import android .support .annotation .RequiresApi ;
15+ import android .support .annotation .NonNull ;
16+ import android .support .annotation .Nullable ;
1517import android .util .AttributeSet ;
1618import android .view .View ;
1719import android .view .animation .DecelerateInterpolator ;
20+
1821import java .lang .annotation .Retention ;
1922import java .lang .annotation .RetentionPolicy ;
2023
@@ -34,42 +37,87 @@ public class ExpandIconView extends View {
3437 LESS ,
3538 INTERMEDIATE
3639 })
37-
3840 @ Retention (RetentionPolicy .SOURCE )
39-
4041 public @interface State {
4142 }
4243
4344 public static final int MORE = 0 ;
4445 public static final int LESS = 1 ;
45- private static final int INTERMEDIATE = 2 ;
46+ public static final int INTERMEDIATE = 2 ;
4647
4748 @ State
4849 private int state ;
49- private int width ;
50- private int height ;
51- private int arrowWidth ;
5250 private float alpha = MORE_STATE_ALPHA ;
5351 private float centerTranslation = 0f ;
52+ @ FloatRange (from = 0.f , to = 1.f )
5453 private float fraction = 0f ;
55- private float animationSpeed ;
56- private boolean useDefaultPadding ;
54+ private final float animationSpeed ;
5755
58- private boolean roundedCorners = false ;
5956 private boolean switchColor = false ;
60- private long animationDuration = DEFAULT_ANIMATION_DURATION ;
6157 private int color = Color .BLACK ;
62- private int colorMore = Color .BLACK ;
63- private int colorLess = Color .RED ;
58+ private final int colorMore ;
59+ private final int colorLess ;
60+
61+ @ NonNull
62+ private final Paint paint ;
63+ private final Point left = new Point ();
64+ private final Point right = new Point ();
65+ private final Point center = new Point ();
66+ private final Point tempLeft = new Point ();
67+ private final Point tempRight = new Point ();
68+
69+ private final boolean useDefaultPadding ;
6470 private int padding ;
6571
66- private Paint paint ;
67- private Point left ;
68- private Point right ;
69- private Point center ;
7072 private final Path path = new Path ();
73+ @ Nullable
7174 private ValueAnimator arrowAnimator ;
7275
76+ public ExpandIconView (@ NonNull Context context ) {
77+ this (context , null );
78+ }
79+
80+ public ExpandIconView (@ NonNull Context context , @ Nullable AttributeSet attrs ) {
81+ this (context , attrs , 0 );
82+ }
83+
84+ public ExpandIconView (@ NonNull Context context , @ Nullable AttributeSet attrs , int defStyleAttr ) {
85+ super (context , attrs , defStyleAttr );
86+
87+ TypedArray array = getContext ().getTheme ().obtainStyledAttributes (attrs ,
88+ R .styleable .ExpandIconView ,
89+ 0 , 0 );
90+
91+ final boolean roundedCorners ;
92+ final long animationDuration ;
93+ try {
94+ roundedCorners = array .getBoolean (R .styleable .ExpandIconView_roundedCorners , false );
95+ switchColor = array .getBoolean (R .styleable .ExpandIconView_switchColor , false );
96+ color = array .getColor (R .styleable .ExpandIconView_color , Color .BLACK );
97+ colorMore = array .getColor (R .styleable .ExpandIconView_colorMore , Color .BLACK );
98+ colorLess = array .getColor (R .styleable .ExpandIconView_colorLess , Color .BLACK );
99+ animationDuration = array .getInteger (R .styleable .ExpandIconView_animationDuration , (int ) DEFAULT_ANIMATION_DURATION );
100+ padding = array .getDimensionPixelSize (R .styleable .ExpandIconView_eiv_padding , -1 );
101+ useDefaultPadding = (padding == -1 );
102+ } finally {
103+ array .recycle ();
104+ }
105+
106+ {
107+ paint = new Paint (ANTI_ALIAS_FLAG );
108+ paint .setColor (color );
109+ paint .setStyle (Paint .Style .STROKE );
110+ paint .setDither (true );
111+ if (roundedCorners ) {
112+ paint .setStrokeJoin (Paint .Join .ROUND );
113+ paint .setStrokeCap (Paint .Cap .ROUND );
114+ }
115+ }
116+
117+ animationSpeed = DELTA_ALPHA / animationDuration ;
118+ setState (MORE , false );
119+ }
120+
73121 public void switchState () {
74122 switchState (true );
75123 }
@@ -80,13 +128,21 @@ public void switchState() {
80128 * @param animate Indicates thaw state will be changed with animation or not
81129 */
82130 public void switchState (boolean animate ) {
83- if (state == MORE ) {
84- setState (LESS , animate );
85- } else if (state == LESS ) {
86- setState (MORE , animate );
87- } else {
88- setState (getFinalStateByFraction (), animate );
131+ final int newState ;
132+ switch (state ) {
133+ case MORE :
134+ newState = LESS ;
135+ break ;
136+ case LESS :
137+ newState = MORE ;
138+ break ;
139+ case INTERMEDIATE :
140+ newState = getFinalStateByFraction ();
141+ break ;
142+ default :
143+ throw new IllegalArgumentException ("Unknown state [" + state + "]" );
89144 }
145+ setState (newState , animate );
90146 }
91147
92148 /**
@@ -115,11 +171,15 @@ public void setState(@State int state, boolean animate) {
115171 * state value is 1f
116172 * @throws IllegalArgumentException if fraction is less than 0f or more than 1f
117173 */
118- public void setFraction (float fraction , boolean animate ) {
174+ public void setFraction (@ FloatRange ( from = 0.f , to = 1.f ) float fraction , boolean animate ) {
119175 if (fraction < 0f || fraction > 1f ) {
120176 throw new IllegalArgumentException ("Fraction value must be from 0 to 1f, fraction=" + fraction );
121177 }
122- if (this .fraction == fraction ) return ;
178+
179+ if (this .fraction == fraction ) {
180+ return ;
181+ }
182+
123183 this .fraction = fraction ;
124184 if (fraction == 0f ) {
125185 state = MORE ;
@@ -128,31 +188,8 @@ public void setFraction(float fraction, boolean animate) {
128188 } else {
129189 state = INTERMEDIATE ;
130190 }
131- updateArrow (animate );
132- }
133-
134- public ExpandIconView (Context context ) {
135- super (context );
136- init ();
137- }
138-
139- public ExpandIconView (Context context , AttributeSet attrs ) {
140- super (context , attrs );
141- readAttributes (attrs );
142- init ();
143- }
144-
145- public ExpandIconView (Context context , AttributeSet attrs , int defStyleAttr ) {
146- super (context , attrs , defStyleAttr );
147- readAttributes (attrs );
148- init ();
149- }
150191
151- @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
152- public ExpandIconView (Context context , AttributeSet attrs , int defStyleAttr , int defStyleRes ) {
153- super (context , attrs , defStyleAttr , defStyleRes );
154- readAttributes (attrs );
155- init ();
192+ updateArrow (animate );
156193 }
157194
158195 @ Override
@@ -165,35 +202,15 @@ protected void onDraw(Canvas canvas) {
165202 @ Override
166203 protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) {
167204 super .onMeasure (widthMeasureSpec , heightMeasureSpec );
168- width = getMeasuredWidth ();
169- height = getMeasuredHeight ();
170205 calculateArrowMetrics ();
171206 updateArrowPath ();
172207 }
173208
174- private void readAttributes (AttributeSet attrs ) {
175- TypedArray array = getContext ().getTheme ().obtainStyledAttributes (
176- attrs ,
177- R .styleable .ExpandIconView ,
178- 0 , 0 );
179-
180- try {
181- roundedCorners = array .getBoolean (R .styleable .ExpandIconView_eiv_roundedCorners , false );
182- switchColor = array .getBoolean (R .styleable .ExpandIconView_eiv_switchColor , false );
183- color = array .getColor (R .styleable .ExpandIconView_eiv_color , Color .BLACK );
184- colorMore = array .getColor (R .styleable .ExpandIconView_eiv_colorMore , Color .BLACK );
185- colorLess = array .getColor (R .styleable .ExpandIconView_eiv_colorLess , Color .BLACK );
186- animationDuration = array .getInteger (R .styleable .ExpandIconView_eiv_animationDuration , (int ) DEFAULT_ANIMATION_DURATION );
187- padding = array .getDimensionPixelSize (R .styleable .ExpandIconView_eiv_padding , -1 );
188- if (padding == -1 ) useDefaultPadding = true ;
189- } finally {
190- array .recycle ();
191- }
192- }
193-
194209 private void calculateArrowMetrics () {
195- int arrowMaxHeight = height - 2 * padding ;
196- arrowWidth = width - 2 * padding ;
210+ final int width = getMeasuredWidth ();
211+ final int height = getMeasuredHeight ();
212+ final int arrowMaxHeight = height - 2 * padding ;
213+ int arrowWidth = width - 2 * padding ;
197214 arrowWidth = arrowMaxHeight >= arrowWidth ? arrowWidth : arrowMaxHeight ;
198215
199216 if (useDefaultPadding ) {
@@ -208,25 +225,6 @@ private void calculateArrowMetrics() {
208225 right .set (center .x + arrowWidth / 2 , center .y );
209226 }
210227
211- private void init () {
212- paint = new Paint (ANTI_ALIAS_FLAG );
213- paint .setColor (color );
214- paint .setStyle (Paint .Style .STROKE );
215- paint .setDither (true );
216- if (roundedCorners ) {
217- paint .setStrokeJoin (Paint .Join .ROUND );
218- paint .setStrokeCap (Paint .Cap .ROUND );
219- }
220-
221- left = new Point ();
222- right = new Point ();
223- center = new Point ();
224-
225- animationSpeed = DELTA_ALPHA / animationDuration ;
226-
227- setState (MORE , false );
228- }
229-
230228 private void updateArrow (boolean animate ) {
231229 float toAlpha = MORE_STATE_ALPHA + (fraction * DELTA_ALPHA );
232230 if (animate ) {
@@ -245,21 +243,22 @@ private void updateArrow(boolean animate) {
245243 private void updateArrowPath () {
246244 path .reset ();
247245 if (left != null && right != null ) {
248- Point currLeft = rotate (left , -alpha );
249- Point currRight = rotate (right , alpha );
250- centerTranslation = (center .y - currLeft .y ) / 2 ;
251- path .moveTo (currLeft .x , currLeft .y );
246+ rotate (left , -alpha , tempLeft );
247+ rotate (right , alpha , tempRight );
248+ centerTranslation = (center .y - tempLeft .y ) / 2 ;
249+ path .moveTo (tempLeft .x , tempLeft .y );
252250 path .lineTo (center .x , center .y );
253- path .lineTo (currRight .x , currRight .y );
251+ path .lineTo (tempRight .x , tempRight .y );
254252 }
255253 }
256254
257255 private void animateArrow (float toAlpha ) {
258256 cancelAnimation ();
259- final ArgbEvaluator colorEvaluator = new ArgbEvaluator ();
260257
261- arrowAnimator = ValueAnimator .ofFloat (alpha , toAlpha );
262- arrowAnimator .addUpdateListener (new ValueAnimator .AnimatorUpdateListener () {
258+ final ValueAnimator valueAnimator = ValueAnimator .ofFloat (alpha , toAlpha );
259+ valueAnimator .addUpdateListener (new ValueAnimator .AnimatorUpdateListener () {
260+ private final ArgbEvaluator colorEvaluator = new ArgbEvaluator ();
261+
263262 @ Override
264263 public void onAnimationUpdate (ValueAnimator valueAnimator ) {
265264 alpha = (float ) valueAnimator .getAnimatedValue ();
@@ -270,9 +269,11 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) {
270269 postInvalidateOnAnimationCompat ();
271270 }
272271 });
273- arrowAnimator .setInterpolator (new DecelerateInterpolator ());
274- arrowAnimator .setDuration (calculateAnimationDuration (toAlpha ));
275- arrowAnimator .start ();
272+ valueAnimator .setInterpolator (new DecelerateInterpolator ());
273+ valueAnimator .setDuration (calculateAnimationDuration (toAlpha ));
274+ valueAnimator .start ();
275+
276+ arrowAnimator = valueAnimator ;
276277 }
277278
278279 private void cancelAnimation () {
@@ -281,7 +282,7 @@ private void cancelAnimation() {
281282 }
282283 }
283284
284- private void updateColor (ArgbEvaluator colorEvaluator ) {
285+ private void updateColor (@ NonNull ArgbEvaluator colorEvaluator ) {
285286 color = (int ) colorEvaluator .evaluate ((alpha + 45f ) / 90f , colorMore , colorLess );
286287 paint .setColor (color );
287288 }
@@ -290,14 +291,15 @@ private long calculateAnimationDuration(float toAlpha) {
290291 return (long ) (Math .abs (toAlpha - alpha ) / animationSpeed );
291292 }
292293
293- private Point rotate (Point startPosition , double degrees ) {
294+ private void rotate (@ NonNull Point startPosition , double degrees , @ NonNull Point target ) {
294295 double angle = Math .toRadians (degrees );
295296 int x = (int ) (center .x + (startPosition .x - center .x ) * Math .cos (angle ) -
296297 (startPosition .y - center .y ) * Math .sin (angle ));
297298
298299 int y = (int ) (center .y + (startPosition .x - center .x ) * Math .sin (angle ) +
299300 (startPosition .y - center .y ) * Math .cos (angle ));
300- return new Point (x , y );
301+
302+ target .set (x , y );
301303 }
302304
303305 @ State
0 commit comments