• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.hvac.ui;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorSet;
20 import android.animation.ObjectAnimator;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.graphics.drawable.GradientDrawable;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.View;
28 import android.widget.FrameLayout;
29 import android.widget.ImageView;
30 import android.widget.TextView;
31 import com.android.car.hvac.R;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /**
37  * An expandable temperature control bar. Note this UI is meant to only support Fahrenheit.
38  */
39 public class TemperatureBarOverlay extends FrameLayout {
40 
41     /**
42      * A listener that observes clicks on the temperature bar.
43      */
44     public interface TemperatureAdjustClickListener {
onTemperatureChanged(int temperature)45         void onTemperatureChanged(int temperature);
46     }
47 
48     private static final int EXPAND_ANIMATION_TIME_MS = 500;
49     private static final int COLLAPSE_ANIMATION_TIME_MS = 200;
50 
51     private static final int TEXT_ALPHA_ANIMATION_TIME_DELAY_MS = 400;
52     private static final int TEXT_ALPHA_FADE_OUT_ANIMATION_TIME_MS = 100;
53     private static final int TEXT_ALPHA_FADE_IN_ANIMATION_TIME_MS = 300;
54 
55     private static final int COLOR_CHANGE_ANIMATION_TIME_MS = 200;
56 
57     private static final float BUTTON_ALPHA_COLLAPSED = 0f;
58     private static final float BUTTON_ALPHA_EXPANDED = 1.0f;
59 
60     private static final int DEFAULT_TEMPERATURE = 32;
61     private static final int MAX_TEMPERATURE = 256;
62     private static final int MIN_TEMPERATURE = 0;
63 
64     private String mInvalidTemperature;
65 
66     private int mTempColor1;
67     private int mTempColor2;
68     private int mTempColor3;
69     private int mTempColor4;
70     private int mTempColor5;
71 
72     private int mOffColor;
73 
74     private ImageView mIncreaseButton;
75     private ImageView mDecreaseButton;
76     private TextView mText;
77     private TextView mFloatingText;
78     private TextView mOffText;
79     private View mTemperatureBar;
80     private View mCloseButton;
81 
82     private int mTemperature = DEFAULT_TEMPERATURE;
83 
84     private int mCollapsedWidth;
85     private int mExpandedWidth;
86     private int mCollapsedHeight;
87     private int mExpandedHeight;
88     private int mCollapsedYShift;
89     private int mExpandedYShift;
90 
91     private boolean mIsOpen;
92     private boolean mIsOn = true;
93 
94     private TemperatureAdjustClickListener mListener;
95 
TemperatureBarOverlay(Context context)96     public TemperatureBarOverlay(Context context) {
97         super(context);
98     }
99 
TemperatureBarOverlay(Context context, AttributeSet attrs)100     public TemperatureBarOverlay(Context context, AttributeSet attrs) {
101         super(context, attrs);
102     }
103 
TemperatureBarOverlay(Context context, AttributeSet attrs, int defStyleAttr)104     public TemperatureBarOverlay(Context context, AttributeSet attrs, int defStyleAttr) {
105         super(context, attrs, defStyleAttr);
106     }
107 
108     @Override
onFinishInflate()109     protected void onFinishInflate() {
110         super.onFinishInflate();
111         Resources res = getResources();
112 
113         mCollapsedHeight = res.getDimensionPixelSize(R.dimen.temperature_bar_collapsed_height);
114         mExpandedHeight = res.getDimensionPixelSize(R.dimen.temperature_bar_expanded_height);
115         // Push the collapsed circle all the way down to the bottom of the screen and leave
116         // half of it visible.
117         mCollapsedYShift = (mCollapsedHeight / 2);
118         // center of expanded panel. The extra nudge of the mCollapsedYShift is to make up for
119         // the fact that the gravity is set to bottom.
120         mExpandedYShift = mCollapsedYShift - ((mExpandedHeight - mCollapsedHeight)/ 2);
121 
122         mCollapsedWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_collapsed);
123         mExpandedWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_expanded);
124 
125         mInvalidTemperature = getContext().getString(R.string.hvac_invalid_temperature);
126 
127         mTempColor1 = res.getColor(R.color.temperature_1);
128         mTempColor2 = res.getColor(R.color.temperature_2);
129         mTempColor3 = res.getColor(R.color.temperature_3);
130         mTempColor4 = res.getColor(R.color.temperature_4);
131         mTempColor5 = res.getColor(R.color.temperature_5);
132 
133         mOffColor = res.getColor(R.color.hvac_temperature_off_text_bg_color);
134 
135         mIncreaseButton = findViewById(R.id.increase_button);
136         mDecreaseButton = findViewById(R.id.decrease_button);
137 
138         mFloatingText = findViewById(R.id.floating_temperature_text);
139         mText = findViewById(R.id.temperature_text);
140         mOffText = findViewById(R.id.temperature_off_text);
141 
142         mTemperatureBar = findViewById(R.id.temperature_bar);
143         mTemperatureBar.setTranslationY(mCollapsedYShift);
144 
145         mCloseButton = findViewById(R.id.close_button);
146 
147         mText.setText(getContext().getString(R.string.hvac_temperature_template,
148                 mInvalidTemperature));
149         mFloatingText.setText(getContext()
150                 .getString(R.string.hvac_temperature_template,
151                         mInvalidTemperature));
152 
153         mIncreaseButton.setOnTouchListener(new PressAndHoldTouchListener(temperatureClickListener));
154         mDecreaseButton.setOnTouchListener(new PressAndHoldTouchListener(temperatureClickListener));
155 
156         if (!mIsOpen) {
157             mIncreaseButton.setAlpha(BUTTON_ALPHA_COLLAPSED);
158             mDecreaseButton.setAlpha(BUTTON_ALPHA_COLLAPSED);
159             mText.setAlpha(BUTTON_ALPHA_COLLAPSED);
160 
161             mDecreaseButton.setVisibility(GONE);
162             mIncreaseButton.setVisibility(GONE);
163             mText.setVisibility(GONE);
164         }
165     }
166 
setTemperatureChangeListener(TemperatureAdjustClickListener listener)167     public void setTemperatureChangeListener(TemperatureAdjustClickListener listener) {
168         mListener =  listener;
169     }
170 
setBarOnClickListener(OnClickListener l)171     public void setBarOnClickListener(OnClickListener l) {
172         mFloatingText.setOnClickListener(l);
173         mTemperatureBar.setOnClickListener(l);
174         setOnClickListener(l);
175     }
176 
setCloseButtonOnClickListener(OnClickListener l)177     public void setCloseButtonOnClickListener(OnClickListener l) {
178         mCloseButton.setOnClickListener(l);
179     }
180 
getExpandAnimatons()181     public AnimatorSet getExpandAnimatons() {
182         List<Animator> list = new ArrayList<>();
183         AnimatorSet animation = new AnimatorSet();
184         if (mIsOpen) {
185             return animation;
186         }
187 
188         list.add(getAlphaAnimator(mIncreaseButton, false /* fade */, EXPAND_ANIMATION_TIME_MS));
189         list.add(getAlphaAnimator(mDecreaseButton, false /* fade */, EXPAND_ANIMATION_TIME_MS));
190         list.add(getAlphaAnimator(mText, false /* fade */, EXPAND_ANIMATION_TIME_MS));
191         list.add(getAlphaAnimator(mFloatingText, true /* fade */,
192                 TEXT_ALPHA_FADE_OUT_ANIMATION_TIME_MS));
193 
194         ValueAnimator widthAnimator = ValueAnimator.ofInt(mCollapsedWidth, mExpandedWidth)
195                 .setDuration(EXPAND_ANIMATION_TIME_MS);
196         widthAnimator.addUpdateListener(mWidthUpdateListener);
197         list.add(widthAnimator);
198 
199         ValueAnimator heightAnimator = ValueAnimator.ofInt(mCollapsedHeight,
200                 mExpandedHeight)
201                 .setDuration(EXPAND_ANIMATION_TIME_MS);
202         heightAnimator.addUpdateListener(mHeightUpdateListener);
203         list.add(heightAnimator);
204 
205 
206         ValueAnimator translationYAnimator
207                 = ValueAnimator.ofFloat(mCollapsedYShift, mExpandedYShift);
208         translationYAnimator.addUpdateListener(mTranslationYListener);
209         list.add(translationYAnimator);
210 
211         animation.playTogether(list);
212         animation.addListener(mStateListener);
213 
214         return animation;
215     }
216 
getCollapseAnimations()217     public AnimatorSet getCollapseAnimations() {
218 
219         List<Animator> list = new ArrayList<>();
220         AnimatorSet animation = new AnimatorSet();
221 
222         if (!mIsOpen) {
223             return animation;
224         }
225         list.add(getAlphaAnimator(mIncreaseButton, true /* fade */, COLLAPSE_ANIMATION_TIME_MS));
226         list.add(getAlphaAnimator(mDecreaseButton, true /* fade */, COLLAPSE_ANIMATION_TIME_MS));
227         list.add(getAlphaAnimator(mText, true /* fade */, COLLAPSE_ANIMATION_TIME_MS));
228 
229         ObjectAnimator floatingTextAnimator = getAlphaAnimator(mFloatingText,
230                 false /* fade */, TEXT_ALPHA_FADE_IN_ANIMATION_TIME_MS);
231         floatingTextAnimator.setStartDelay(TEXT_ALPHA_ANIMATION_TIME_DELAY_MS);
232 
233         list.add(floatingTextAnimator);
234 
235         ValueAnimator widthAnimator = ValueAnimator.ofInt(mExpandedWidth, mCollapsedWidth)
236                 .setDuration(COLLAPSE_ANIMATION_TIME_MS);
237         widthAnimator.addUpdateListener(mWidthUpdateListener);
238         list.add(widthAnimator);
239 
240         ValueAnimator heightAnimator = ValueAnimator.ofInt(mExpandedHeight, mCollapsedHeight)
241                 .setDuration(COLLAPSE_ANIMATION_TIME_MS);
242         heightAnimator.addUpdateListener(mHeightUpdateListener);
243         list.add(heightAnimator);
244 
245         ValueAnimator translationYAnimator
246                 = ValueAnimator.ofFloat(mExpandedYShift, mCollapsedYShift);
247         translationYAnimator.addUpdateListener(mTranslationYListener);
248         list.add(translationYAnimator);
249 
250         animation.playTogether(list);
251         animation.addListener(mStateListener);
252 
253         return animation;
254     }
255 
256     private ValueAnimator.AnimatorListener mStateListener = new ValueAnimator.AnimatorListener() {
257         @Override
258         public void onAnimationStart(Animator animation) {
259             if (!mIsOpen) {
260                 mDecreaseButton.setVisibility(VISIBLE);
261                 mIncreaseButton.setVisibility(VISIBLE);
262                 mText.setVisibility(VISIBLE);
263                 mCloseButton.setVisibility(VISIBLE);
264             } else {
265                 mCloseButton.setVisibility(GONE);
266             }
267         }
268 
269         @Override
270         public void onAnimationEnd(Animator animation) {
271             if (mIsOpen) {
272                 //Finished closing, make sure the buttons are now gone,
273                 //so they are no longer touchable
274                 mDecreaseButton.setVisibility(GONE);
275                 mIncreaseButton.setVisibility(GONE);
276                 mText.setVisibility(GONE);
277                 mIsOpen = false;
278             } else {
279                 //Finished opening
280                 mIsOpen = true;
281             }
282         }
283 
284         @Override
285         public void onAnimationCancel(Animator animation) {
286         }
287 
288         @Override
289         public void onAnimationRepeat(Animator animation) {
290         }
291     };
292 
293 
changeTemperatureColor(int startColor, int endColor)294     private void changeTemperatureColor(int startColor, int endColor) {
295         if (endColor != startColor) {
296             ValueAnimator animator = ValueAnimator.ofArgb(startColor, endColor);
297             animator.addUpdateListener(mTemperatureColorListener);
298             animator.setDuration(COLOR_CHANGE_ANIMATION_TIME_MS);
299             animator.start();
300         } else {
301             ((GradientDrawable) mTemperatureBar.getBackground()).setColor(endColor);
302         }
303     }
304 
305     private final View.OnClickListener temperatureClickListener = new View.OnClickListener() {
306         @Override
307         public void onClick(View v) {
308             synchronized (this) {
309                 if (!mIsOn) {
310                     Log.d("HvacTempBar", "setting temperature not available");
311                     return;
312                 }
313                 int startColor = getTemperatureColor(mTemperature);
314 
315                 if (v == mIncreaseButton && mTemperature < MAX_TEMPERATURE) {
316                     mTemperature++;
317                     Log.d("HvacTempBar", "increased temperature to " + mTemperature);
318                 } else if (v == mDecreaseButton && mTemperature > MIN_TEMPERATURE) {
319                     mTemperature--;
320                     Log.d("HvacTempBar", "decreased temperature to " + mTemperature);
321                 } else {
322                     Log.d("HvacTempBar", "key not recognized");
323                 }
324                 int endColor = getTemperatureColor(mTemperature);
325                 changeTemperatureColor(startColor, endColor);
326 
327                 mText.setText(
328                     getContext().getString(R.string.hvac_temperature_template, mTemperature));
329                 mFloatingText.setText(getContext()
330                     .getString(R.string.hvac_temperature_template, mTemperature));
331                 mListener.onTemperatureChanged(mTemperature);
332             }
333         }
334     };
335 
setAvailable(boolean available)336     public void setAvailable(boolean available) {
337         Log.d("HvacTempBar", "setAvailable(" + available + ")");
338         setIsOn(available);
339     }
340 
setTemperature(int temperature)341     public void setTemperature(int temperature) {
342         Log.d("HvacTempBar", "setTemperature(" + temperature + ")");
343         int startColor = getTemperatureColor(mTemperature);
344         int endColor = getTemperatureColor(temperature);
345         mTemperature = temperature;
346         String temperatureString;
347 
348         if (mTemperature < MIN_TEMPERATURE || mTemperature > MAX_TEMPERATURE) {
349             temperatureString = mInvalidTemperature;
350         } else {
351             temperatureString = String.valueOf(mTemperature);
352         }
353 
354         synchronized (this) {
355             mText.setText(getContext().getString(R.string.hvac_temperature_template,
356                 temperatureString));
357             mFloatingText.setText(getContext()
358                 .getString(R.string.hvac_temperature_template, temperatureString));
359 
360             // Only animate the color if the button is currently enabled.
361             if (mIsOn) {
362                 changeTemperatureColor(startColor, endColor);
363             }
364         }
365     }
366 
367     /**
368      * Sets whether or not the temperature bar is on. If it is off, it should show "off" instead
369      * of the temperature.
370      */
setIsOn(boolean isOn)371     public void setIsOn(boolean isOn) {
372         synchronized (this) {
373             mIsOn = isOn;
374 
375             GradientDrawable temperatureBall
376                 = (GradientDrawable) mTemperatureBar.getBackground();
377             if (mIsOn) {
378                 mFloatingText.setVisibility(VISIBLE);
379                 mOffText.setVisibility(GONE);
380                 temperatureBall.setColor(getTemperatureColor(mTemperature));
381                 setAlpha(1.0f);
382             } else {
383                 mOffText.setVisibility(VISIBLE);
384                 mFloatingText.setVisibility(GONE);
385                 temperatureBall.setColor(mOffColor);
386                 setAlpha(.2f);
387             }
388         }
389     }
390 
getTemperatureColor(int temperature)391     private int getTemperatureColor(int temperature) {
392         if (temperature >= 78) {
393             return mTempColor1;
394         } else if (temperature >= 74 && temperature < 78) {
395             return mTempColor2;
396         } else if (temperature >= 70 && temperature < 74) {
397             return mTempColor3;
398         } else if (temperature >= 66 && temperature < 70) {
399             return mTempColor4;
400         } else {
401             return mTempColor5;
402         }
403     }
404 
405     private final ValueAnimator.AnimatorUpdateListener mTranslationYListener
406             = new ValueAnimator.AnimatorUpdateListener() {
407         @Override
408         public void onAnimationUpdate(ValueAnimator animation) {
409             float translation = (float) animation.getAnimatedValue();
410             mTemperatureBar.setTranslationY(translation);
411         }
412     };
413 
414     private final ValueAnimator.AnimatorUpdateListener mWidthUpdateListener
415             = new ValueAnimator.AnimatorUpdateListener() {
416         @Override
417         public void onAnimationUpdate(ValueAnimator animation) {
418             int width = (Integer) animation.getAnimatedValue();
419             mTemperatureBar.getLayoutParams().width = width;
420             mTemperatureBar.requestLayout();
421         }
422     };
423 
424     private final ValueAnimator.AnimatorUpdateListener mHeightUpdateListener
425             = new ValueAnimator.AnimatorUpdateListener() {
426         @Override
427         public void onAnimationUpdate(ValueAnimator animation) {
428             int height = (Integer) animation.getAnimatedValue();
429             int currentHeight = mTemperatureBar.getLayoutParams().height;
430             mTemperatureBar.getLayoutParams().height = height;
431             mTemperatureBar.setTop(mTemperatureBar.getTop() + height - currentHeight);
432             mTemperatureBar.requestLayout();
433         }
434     };
435 
436     private final ValueAnimator.AnimatorUpdateListener mTemperatureColorListener
437             = new ValueAnimator.AnimatorUpdateListener() {
438         @Override
439         public void onAnimationUpdate(ValueAnimator animation) {
440             int color = (Integer) animation.getAnimatedValue();
441             ((GradientDrawable) mTemperatureBar.getBackground()).setColor(color);
442         }
443     };
444 
getAlphaAnimator(View view, boolean fade, int duration)445     private ObjectAnimator getAlphaAnimator(View view, boolean fade, int duration) {
446 
447         float startingAlpha = BUTTON_ALPHA_COLLAPSED;
448         float endingAlpha = BUTTON_ALPHA_EXPANDED;
449 
450         if (fade) {
451             startingAlpha = BUTTON_ALPHA_EXPANDED;
452             endingAlpha = BUTTON_ALPHA_COLLAPSED;
453         }
454 
455         return ObjectAnimator.ofFloat(view, View.ALPHA,
456                 startingAlpha, endingAlpha).setDuration(duration);
457     }
458 }
459