/* * Copyright (c) 2016, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.hvac.ui; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.GradientDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import com.android.car.hvac.R; import java.util.ArrayList; import java.util.List; /** * An expandable temperature control bar. Note this UI is meant to only support Fahrenheit. */ public class TemperatureBarOverlay extends FrameLayout { /** * A listener that observes clicks on the temperature bar. */ public interface TemperatureAdjustClickListener { void onTemperatureChanged(int temperature); } private static final int EXPAND_ANIMATION_TIME_MS = 500; private static final int COLLAPSE_ANIMATION_TIME_MS = 200; private static final int TEXT_ALPHA_ANIMATION_TIME_DELAY_MS = 400; private static final int TEXT_ALPHA_FADE_OUT_ANIMATION_TIME_MS = 100; private static final int TEXT_ALPHA_FADE_IN_ANIMATION_TIME_MS = 300; private static final int COLOR_CHANGE_ANIMATION_TIME_MS = 200; private static final float BUTTON_ALPHA_COLLAPSED = 0f; private static final float BUTTON_ALPHA_EXPANDED = 1.0f; private static final int DEFAULT_TEMPERATURE = 32; private static final int MAX_TEMPERATURE = 256; private static final int MIN_TEMPERATURE = 0; private String mInvalidTemperature; private int mTempColor1; private int mTempColor2; private int mTempColor3; private int mTempColor4; private int mTempColor5; private int mOffColor; private ImageView mIncreaseButton; private ImageView mDecreaseButton; private TextView mText; private TextView mFloatingText; private TextView mOffText; private View mTemperatureBar; private View mCloseButton; private int mTemperature = DEFAULT_TEMPERATURE; private int mCollapsedWidth; private int mExpandedWidth; private int mCollapsedHeight; private int mExpandedHeight; private int mCollapsedYShift; private int mExpandedYShift; private boolean mIsOpen; private boolean mIsOn = true; private TemperatureAdjustClickListener mListener; public TemperatureBarOverlay(Context context) { super(context); } public TemperatureBarOverlay(Context context, AttributeSet attrs) { super(context, attrs); } public TemperatureBarOverlay(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onFinishInflate() { super.onFinishInflate(); Resources res = getResources(); mCollapsedHeight = res.getDimensionPixelSize(R.dimen.temperature_bar_collapsed_height); mExpandedHeight = res.getDimensionPixelSize(R.dimen.temperature_bar_expanded_height); // Push the collapsed circle all the way down to the bottom of the screen and leave // half of it visible. mCollapsedYShift = (mCollapsedHeight / 2); // center of expanded panel. The extra nudge of the mCollapsedYShift is to make up for // the fact that the gravity is set to bottom. mExpandedYShift = mCollapsedYShift - ((mExpandedHeight - mCollapsedHeight)/ 2); mCollapsedWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_collapsed); mExpandedWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_expanded); mInvalidTemperature = getContext().getString(R.string.hvac_invalid_temperature); mTempColor1 = res.getColor(R.color.temperature_1); mTempColor2 = res.getColor(R.color.temperature_2); mTempColor3 = res.getColor(R.color.temperature_3); mTempColor4 = res.getColor(R.color.temperature_4); mTempColor5 = res.getColor(R.color.temperature_5); mOffColor = res.getColor(R.color.hvac_temperature_off_text_bg_color); mIncreaseButton = findViewById(R.id.increase_button); mDecreaseButton = findViewById(R.id.decrease_button); mFloatingText = findViewById(R.id.floating_temperature_text); mText = findViewById(R.id.temperature_text); mOffText = findViewById(R.id.temperature_off_text); mTemperatureBar = findViewById(R.id.temperature_bar); mTemperatureBar.setTranslationY(mCollapsedYShift); mCloseButton = findViewById(R.id.close_button); mText.setText(getContext().getString(R.string.hvac_temperature_template, mInvalidTemperature)); mFloatingText.setText(getContext() .getString(R.string.hvac_temperature_template, mInvalidTemperature)); mIncreaseButton.setOnTouchListener(new PressAndHoldTouchListener(temperatureClickListener)); mDecreaseButton.setOnTouchListener(new PressAndHoldTouchListener(temperatureClickListener)); if (!mIsOpen) { mIncreaseButton.setAlpha(BUTTON_ALPHA_COLLAPSED); mDecreaseButton.setAlpha(BUTTON_ALPHA_COLLAPSED); mText.setAlpha(BUTTON_ALPHA_COLLAPSED); mDecreaseButton.setVisibility(GONE); mIncreaseButton.setVisibility(GONE); mText.setVisibility(GONE); } } public void setTemperatureChangeListener(TemperatureAdjustClickListener listener) { mListener = listener; } public void setBarOnClickListener(OnClickListener l) { mFloatingText.setOnClickListener(l); mTemperatureBar.setOnClickListener(l); setOnClickListener(l); } public void setCloseButtonOnClickListener(OnClickListener l) { mCloseButton.setOnClickListener(l); } public AnimatorSet getExpandAnimatons() { List list = new ArrayList<>(); AnimatorSet animation = new AnimatorSet(); if (mIsOpen) { return animation; } list.add(getAlphaAnimator(mIncreaseButton, false /* fade */, EXPAND_ANIMATION_TIME_MS)); list.add(getAlphaAnimator(mDecreaseButton, false /* fade */, EXPAND_ANIMATION_TIME_MS)); list.add(getAlphaAnimator(mText, false /* fade */, EXPAND_ANIMATION_TIME_MS)); list.add(getAlphaAnimator(mFloatingText, true /* fade */, TEXT_ALPHA_FADE_OUT_ANIMATION_TIME_MS)); ValueAnimator widthAnimator = ValueAnimator.ofInt(mCollapsedWidth, mExpandedWidth) .setDuration(EXPAND_ANIMATION_TIME_MS); widthAnimator.addUpdateListener(mWidthUpdateListener); list.add(widthAnimator); ValueAnimator heightAnimator = ValueAnimator.ofInt(mCollapsedHeight, mExpandedHeight) .setDuration(EXPAND_ANIMATION_TIME_MS); heightAnimator.addUpdateListener(mHeightUpdateListener); list.add(heightAnimator); ValueAnimator translationYAnimator = ValueAnimator.ofFloat(mCollapsedYShift, mExpandedYShift); translationYAnimator.addUpdateListener(mTranslationYListener); list.add(translationYAnimator); animation.playTogether(list); animation.addListener(mStateListener); return animation; } public AnimatorSet getCollapseAnimations() { List list = new ArrayList<>(); AnimatorSet animation = new AnimatorSet(); if (!mIsOpen) { return animation; } list.add(getAlphaAnimator(mIncreaseButton, true /* fade */, COLLAPSE_ANIMATION_TIME_MS)); list.add(getAlphaAnimator(mDecreaseButton, true /* fade */, COLLAPSE_ANIMATION_TIME_MS)); list.add(getAlphaAnimator(mText, true /* fade */, COLLAPSE_ANIMATION_TIME_MS)); ObjectAnimator floatingTextAnimator = getAlphaAnimator(mFloatingText, false /* fade */, TEXT_ALPHA_FADE_IN_ANIMATION_TIME_MS); floatingTextAnimator.setStartDelay(TEXT_ALPHA_ANIMATION_TIME_DELAY_MS); list.add(floatingTextAnimator); ValueAnimator widthAnimator = ValueAnimator.ofInt(mExpandedWidth, mCollapsedWidth) .setDuration(COLLAPSE_ANIMATION_TIME_MS); widthAnimator.addUpdateListener(mWidthUpdateListener); list.add(widthAnimator); ValueAnimator heightAnimator = ValueAnimator.ofInt(mExpandedHeight, mCollapsedHeight) .setDuration(COLLAPSE_ANIMATION_TIME_MS); heightAnimator.addUpdateListener(mHeightUpdateListener); list.add(heightAnimator); ValueAnimator translationYAnimator = ValueAnimator.ofFloat(mExpandedYShift, mCollapsedYShift); translationYAnimator.addUpdateListener(mTranslationYListener); list.add(translationYAnimator); animation.playTogether(list); animation.addListener(mStateListener); return animation; } private ValueAnimator.AnimatorListener mStateListener = new ValueAnimator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { if (!mIsOpen) { mDecreaseButton.setVisibility(VISIBLE); mIncreaseButton.setVisibility(VISIBLE); mText.setVisibility(VISIBLE); mCloseButton.setVisibility(VISIBLE); } else { mCloseButton.setVisibility(GONE); } } @Override public void onAnimationEnd(Animator animation) { if (mIsOpen) { //Finished closing, make sure the buttons are now gone, //so they are no longer touchable mDecreaseButton.setVisibility(GONE); mIncreaseButton.setVisibility(GONE); mText.setVisibility(GONE); mIsOpen = false; } else { //Finished opening mIsOpen = true; } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }; private void changeTemperatureColor(int startColor, int endColor) { if (endColor != startColor) { ValueAnimator animator = ValueAnimator.ofArgb(startColor, endColor); animator.addUpdateListener(mTemperatureColorListener); animator.setDuration(COLOR_CHANGE_ANIMATION_TIME_MS); animator.start(); } else { ((GradientDrawable) mTemperatureBar.getBackground()).setColor(endColor); } } private final View.OnClickListener temperatureClickListener = new View.OnClickListener() { @Override public void onClick(View v) { synchronized (this) { if (!mIsOn) { Log.d("HvacTempBar", "setting temperature not available"); return; } int startColor = getTemperatureColor(mTemperature); if (v == mIncreaseButton && mTemperature < MAX_TEMPERATURE) { mTemperature++; Log.d("HvacTempBar", "increased temperature to " + mTemperature); } else if (v == mDecreaseButton && mTemperature > MIN_TEMPERATURE) { mTemperature--; Log.d("HvacTempBar", "decreased temperature to " + mTemperature); } else { Log.d("HvacTempBar", "key not recognized"); } int endColor = getTemperatureColor(mTemperature); changeTemperatureColor(startColor, endColor); mText.setText( getContext().getString(R.string.hvac_temperature_template, mTemperature)); mFloatingText.setText(getContext() .getString(R.string.hvac_temperature_template, mTemperature)); mListener.onTemperatureChanged(mTemperature); } } }; public void setAvailable(boolean available) { Log.d("HvacTempBar", "setAvailable(" + available + ")"); setIsOn(available); } public void setTemperature(int temperature) { Log.d("HvacTempBar", "setTemperature(" + temperature + ")"); int startColor = getTemperatureColor(mTemperature); int endColor = getTemperatureColor(temperature); mTemperature = temperature; String temperatureString; if (mTemperature < MIN_TEMPERATURE || mTemperature > MAX_TEMPERATURE) { temperatureString = mInvalidTemperature; } else { temperatureString = String.valueOf(mTemperature); } synchronized (this) { mText.setText(getContext().getString(R.string.hvac_temperature_template, temperatureString)); mFloatingText.setText(getContext() .getString(R.string.hvac_temperature_template, temperatureString)); // Only animate the color if the button is currently enabled. if (mIsOn) { changeTemperatureColor(startColor, endColor); } } } /** * Sets whether or not the temperature bar is on. If it is off, it should show "off" instead * of the temperature. */ public void setIsOn(boolean isOn) { synchronized (this) { mIsOn = isOn; GradientDrawable temperatureBall = (GradientDrawable) mTemperatureBar.getBackground(); if (mIsOn) { mFloatingText.setVisibility(VISIBLE); mOffText.setVisibility(GONE); temperatureBall.setColor(getTemperatureColor(mTemperature)); setAlpha(1.0f); } else { mOffText.setVisibility(VISIBLE); mFloatingText.setVisibility(GONE); temperatureBall.setColor(mOffColor); setAlpha(.2f); } } } private int getTemperatureColor(int temperature) { if (temperature >= 78) { return mTempColor1; } else if (temperature >= 74 && temperature < 78) { return mTempColor2; } else if (temperature >= 70 && temperature < 74) { return mTempColor3; } else if (temperature >= 66 && temperature < 70) { return mTempColor4; } else { return mTempColor5; } } private final ValueAnimator.AnimatorUpdateListener mTranslationYListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float translation = (float) animation.getAnimatedValue(); mTemperatureBar.setTranslationY(translation); } }; private final ValueAnimator.AnimatorUpdateListener mWidthUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int width = (Integer) animation.getAnimatedValue(); mTemperatureBar.getLayoutParams().width = width; mTemperatureBar.requestLayout(); } }; private final ValueAnimator.AnimatorUpdateListener mHeightUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (Integer) animation.getAnimatedValue(); int currentHeight = mTemperatureBar.getLayoutParams().height; mTemperatureBar.getLayoutParams().height = height; mTemperatureBar.setTop(mTemperatureBar.getTop() + height - currentHeight); mTemperatureBar.requestLayout(); } }; private final ValueAnimator.AnimatorUpdateListener mTemperatureColorListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int color = (Integer) animation.getAnimatedValue(); ((GradientDrawable) mTemperatureBar.getBackground()).setColor(color); } }; private ObjectAnimator getAlphaAnimator(View view, boolean fade, int duration) { float startingAlpha = BUTTON_ALPHA_COLLAPSED; float endingAlpha = BUTTON_ALPHA_EXPANDED; if (fade) { startingAlpha = BUTTON_ALPHA_EXPANDED; endingAlpha = BUTTON_ALPHA_COLLAPSED; } return ObjectAnimator.ofFloat(view, View.ALPHA, startingAlpha, endingAlpha).setDuration(duration); } }