Home | History | Annotate | Download | only in ui
      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 {
     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 
     94     public TemperatureBarOverlay(Context context) {
     95         super(context);
     96     }
     97 
     98     public TemperatureBarOverlay(Context context, AttributeSet attrs) {
     99         super(context, attrs);
    100     }
    101 
    102     public TemperatureBarOverlay(Context context, AttributeSet attrs, int defStyleAttr) {
    103         super(context, attrs, defStyleAttr);
    104     }
    105 
    106     @Override
    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 
    166     public void setTemperatureChangeListener(TemperatureAdjustClickListener listener) {
    167         mListener =  listener;
    168     }
    169 
    170     public void setBarOnClickListener(OnClickListener l) {
    171         mFloatingText.setOnClickListener(l);
    172         mTemperatureBar.setOnClickListener(l);
    173     }
    174 
    175     public void setCloseButtonOnClickListener(OnClickListener l) {
    176         mCloseButton.setOnClickListener(l);
    177     }
    178 
    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 
    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 
    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 
    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      */
    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 
    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 
    420     private ObjectAnimator getAlphaAnimator(View view, boolean fade) {
    421         return getAlphaAnimator(view, fade, EXPAND_ANIMATION_TIME_MS);
    422     }
    423 
    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