com.microsoft.mimickeralarm.mimics.ProgressButton.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.mimickeralarm.mimics.ProgressButton.java

Source

/*
 *
 * Copyright (c) Microsoft. All rights reserved.
 * Licensed under the MIT license.
 *
 * Project Oxford: http://ProjectOxford.ai
 *
 * Project Oxford Mimicker Alarm Github:
 * https://github.com/Microsoft/ProjectOxford-Apps-MimickerAlarm
 *
 * Copyright (c) Microsoft Corporation
 * All rights reserved.
 *
 * MIT License:
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package com.microsoft.mimickeralarm.mimics;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;

import com.microsoft.mimickeralarm.R;

/**
 * Simple UI class that implements a button with states
 *
 * The states are :
 * capture image,
 * capture audio,
 * stop capturing audio,
 * processing
 **/
public class ProgressButton extends ImageView {
    private static final int PRESSED_ANIMATION_DURATION = 200;
    private static final float PRESSED_ANIMATION_GROW_FACTOR = 1.2f;
    private static final int LOADING_ANIMATION_DURATION = 2000;
    private static final long INTERACTION_HINT_ANIMATION_DURATION = 1000;
    private static final float INTERACTION_HINT_GROW_FACTOR = 1.1f;
    private static int sYellow, sBlue, sGrey, sWhite;
    private State mState;
    private State mReadyState;
    private Paint mBrush;
    private float mRadius;
    private int mCenterX, mCenterY;
    private float mInitialRadius;
    private Bitmap mMicrophoneIcon;
    private Bitmap mCameraIcon;
    private ObjectAnimator mPressedAnimation;
    private ObjectAnimator mLoadingAnimation;
    // A growing/shrinking animation of the button border to give the hint to the user to press the button
    private ObjectAnimator mInteractionHintAnimation;
    private float mLoadingAnimationProgress;
    private RectF mLoadingAnimationRect;

    public ProgressButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setFocusable(true);
        setScaleType(ScaleType.CENTER_INSIDE);

        sYellow = ContextCompat.getColor(context, R.color.yellow3);
        sBlue = ContextCompat.getColor(context, R.color.blue);
        sGrey = ContextCompat.getColor(context, R.color.grey);
        sWhite = ContextCompat.getColor(context, R.color.white);
        mMicrophoneIcon = ((BitmapDrawable) ContextCompat.getDrawable(context, android.R.drawable.ic_btn_speak_now))
                .getBitmap();
        mCameraIcon = ((BitmapDrawable) ContextCompat.getDrawable(context, android.R.drawable.ic_menu_camera))
                .getBitmap();

        mBrush = new Paint(Paint.ANTI_ALIAS_FLAG);

        mLoadingAnimation = ObjectAnimator.ofFloat(this, "loadingAnimationProgress", 0f, 360f);
        mLoadingAnimation.setDuration(LOADING_ANIMATION_DURATION);
        mLoadingAnimation.setRepeatCount(ObjectAnimator.INFINITE);

        mPressedAnimation = ObjectAnimator.ofFloat(this, "radius", 0f, 0f);
        mPressedAnimation.setDuration(PRESSED_ANIMATION_DURATION);
        mPressedAnimation.setInterpolator(new DecelerateInterpolator());
    }

    public ProgressButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ProgressButton(Context context) {
        this(context, null);
    }

    // These setters and getters are required by animator
    public float getRadius() {
        return mRadius;
    }

    public void setRadius(float radius) {
        this.mRadius = radius;
        this.invalidate();
    }

    public float getLoadingAnimationProgress() {
        return mLoadingAnimationProgress;
    }

    public void setLoadingAnimationProgress(float loadingAnimationProgress) {
        this.mLoadingAnimationProgress = loadingAnimationProgress;
        this.invalidate();
    }

    private void prepareDrawText(int color) {
        mBrush.setFlags(Paint.LINEAR_TEXT_FLAG);
        mBrush.setStyle(Paint.Style.STROKE);
        mBrush.setColor(color);
        mBrush.setTextAlign(Paint.Align.CENTER);
        mBrush.setStrokeWidth(0f);
        mBrush.setTextSize(mInitialRadius);
    }

    private void prepareDrawFill(int color) {
        mBrush.setFlags(Paint.ANTI_ALIAS_FLAG);
        mBrush.setStyle(Paint.Style.FILL);
        mBrush.setColor(color);
    }

    private void prepareDrawStroke(int color) {
        mBrush.setFlags(Paint.ANTI_ALIAS_FLAG);
        mBrush.setStyle(Paint.Style.STROKE);
        mBrush.setStrokeWidth(10f);
        mBrush.setColor(color);
    }

    @Override
    public void setPressed(boolean pressed) {
        super.setPressed(pressed);
        if (pressed) {
            mPressedAnimation.setFloatValues(mRadius, mInitialRadius * PRESSED_ANIMATION_GROW_FACTOR);
            mPressedAnimation.start();
        } else {
            mPressedAnimation.setFloatValues(mRadius, mInitialRadius);
            mPressedAnimation.start();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mState == State.ReadyCamera) {
            prepareDrawFill(sYellow);
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);

            canvas.drawBitmap(mCameraIcon, mCenterX - (mCameraIcon.getWidth() / 2),
                    mCenterY - (mCameraIcon.getHeight() / 2), mBrush);
        } else if (mState == State.ReadyAudio) {
            prepareDrawFill(sYellow);
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);

            canvas.drawBitmap(mMicrophoneIcon, mCenterX - (mMicrophoneIcon.getWidth() / 2),
                    mCenterY - (mMicrophoneIcon.getHeight() / 2), mBrush);
        } else if (mState == State.Loading) {
            prepareDrawFill(sBlue);
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);

            prepareDrawStroke(sWhite);
            canvas.drawArc(mLoadingAnimationRect, mLoadingAnimationProgress, 300f, false, mBrush);
        } else if (mState == State.Waiting) {
            prepareDrawFill(sGrey);
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);
            prepareDrawFill(sWhite);
            canvas.drawCircle(mCenterX, mCenterY, (float) (mRadius * 0.7), mBrush);
            prepareDrawFill(sGrey);
            float w = (float) (mRadius * 0.3);
            canvas.drawRect(mCenterX - w, mCenterY - w, mCenterX + w, mCenterY + w, mBrush);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w / 2;
        mCenterY = h / 2;
        mInitialRadius = Math.min(w, h) / 2
                - getContext().getResources().getDimensionPixelSize(R.dimen.progress_button_padding);
        mRadius = mInitialRadius;
        prepareDrawText(sWhite);

        float radius = mInitialRadius / 2f;
        mLoadingAnimationRect = new RectF(mCenterX - radius, mCenterY - radius, mCenterX + radius,
                mCenterY + radius);

        mInteractionHintAnimation = ObjectAnimator.ofFloat(this, "radius", mInitialRadius,
                mInitialRadius * INTERACTION_HINT_GROW_FACTOR, mInitialRadius);
        mInteractionHintAnimation.setDuration(INTERACTION_HINT_ANIMATION_DURATION);
        mInteractionHintAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
        mInteractionHintAnimation.setRepeatCount(ValueAnimator.INFINITE);
        mInteractionHintAnimation.start();
    }

    public void setReady() {
        mState = mReadyState;
        setClickable(true);
        stop();
        if (mInteractionHintAnimation != null) {
            // When the game starts, setReady() will be called before onSizeChanged() is called.
            // However, we will not have mInitialRadius set until OnSizeChanged() is called.
            // So we will start animation in OnSizeChanged().
            // However, if the user fails the mimics, setReady() will be called for several times.
            // In that case, we will re-start the animation here.
            mInteractionHintAnimation.start();
        }
        invalidate();
    }

    public void setReadyState(State state) {
        mReadyState = state;
    }

    public Boolean isReady() {
        return mState == State.ReadyCamera || mState == State.ReadyAudio;
    }

    public void waiting() {
        mState = State.Waiting;
        mInteractionHintAnimation.cancel();
        invalidate();
    }

    public void loading() {
        mState = State.Loading;
        setClickable(false);
        mInteractionHintAnimation.cancel();
        mLoadingAnimation.start();
    }

    public void stop() {
        mLoadingAnimation.cancel();
        mPressedAnimation.cancel();
        if (mInteractionHintAnimation != null) {
            mInteractionHintAnimation.cancel();
        }
    }

    public enum State {
        ReadyCamera, ReadyAudio, Loading, Waiting,
    }
}