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