/*
* Copyright (C) 2013 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.google.android.glass.sample.stopwatch;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.TextView;
import java.lang.Long;
import java.util.concurrent.TimeUnit;
/**
* Animated countdown going from {@code mTimeSeconds} to 0.
*
* The current animation for each second is as follow:
* 1. From 0 to 500ms, move the TextView from {@code MAX_TRANSLATION_Y} to 0 and its alpha from
* {@code 0} to {@code ALPHA_DELIMITER}.
* 2. From 500ms to 1000ms, update the TextView's alpha from {@code ALPHA_DELIMITER} to {@code 1}.
* At each second change, update the TextView text.
*/
public class CountDownView extends FrameLayout {
private static final String TAG = "CountDownView";
/**
* Interface to listen for changes in the countdown.
*/
public interface CountDownListener {
/**
* Notified of a tick, indicating a layout change.
*/
public void onTick(long millisUntilFinish);
/**
* Notified when the countdown is finished.
*/
public void onFinish();
}
/** Time delimiter specifying when the second component is fully shown. */
public static final float ANIMATION_DURATION_IN_MILLIS = 850.0f;
// About 24 FPS.
private static final long DELAY_MILLIS = 41;
private static final int MAX_TRANSLATION_Y = 30;
private static final float ALPHA_DELIMITER = 0.95f;
private static final long SEC_TO_MILLIS = TimeUnit.SECONDS.toMillis(1);
private final TextView mSecondsView;
private long mTimeSeconds;
private long mStopTimeInFuture;
private CountDownListener mListener;
private boolean mStarted;
public CountDownView(Context context) {
this(context, null, 0);
}
public CountDownView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CountDownView(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
LayoutInflater.from(context).inflate(R.layout.card_countdown, this);
mSecondsView = (TextView) findViewById(R.id.seconds_view);
}
public void setCountDown(long timeSeconds) {
mTimeSeconds = timeSeconds;
}
public long getCountDown() {
return mTimeSeconds;
}
/**
* Set a {@link CountDownListener}.
*/
public void setListener(CountDownListener listener) {
mListener = listener;
}
private final Handler mHandler = new Handler();
private final Runnable mUpdateViewRunnable = new Runnable() {
@Override
public void run() {
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
// Count down is done.
if (millisLeft <= 0) {
mStarted = false;
if (mListener != null) {
mListener.onFinish();
}
} else {
updateView(millisLeft);
if (mListener != null) {
mListener.onTick(millisLeft);
}
mHandler.postDelayed(mUpdateViewRunnable, DELAY_MILLIS);
}
}
};
/**
* Starts the countdown animation if not yet started.
*/
public void start() {
if (!mStarted) {
mStopTimeInFuture =
TimeUnit.SECONDS.toMillis(mTimeSeconds) + SystemClock.elapsedRealtime();
mStarted = true;
mHandler.postDelayed(mUpdateViewRunnable, DELAY_MILLIS);
}
}
/**
* Updates the views to reflect the current state of animation.
*
* @params millisUntilFinish milliseconds until the countdown is done
*/
private void updateView(long millisUntilFinish) {
long currentTimeSeconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinish) + 1;
long frame = SEC_TO_MILLIS - (millisUntilFinish % SEC_TO_MILLIS);
mSecondsView.setText(Long.toString(currentTimeSeconds));
if (frame <= ANIMATION_DURATION_IN_MILLIS) {
float factor = frame / ANIMATION_DURATION_IN_MILLIS;
mSecondsView.setAlpha(factor * ALPHA_DELIMITER);
mSecondsView.setTranslationY(MAX_TRANSLATION_Y * (1 - factor));
} else {
float factor = (frame - ANIMATION_DURATION_IN_MILLIS) / ANIMATION_DURATION_IN_MILLIS;
mSecondsView.setAlpha(ALPHA_DELIMITER + factor * (1 - ALPHA_DELIMITER));
}
}
}