android 闹钟提醒和电接听界面效果(下)

上一章把资源文件和布局代码贴上,现在是实际代码。

//文件名ThemeUtils.java


package Notification;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;


public final class ThemeUtils {

    /** Temporary array used internally to resolve attributes. */
    private static final int[] TEMP_ATTR = new int[1];

    private ThemeUtils() {
        // Prevent instantiation.
    }

    /**
     * Convenience method for retrieving a themed color value.
     *
     * @param context the {@link Context} to resolve the theme attribute against
     * @param attr    the attribute corresponding to the color to resolve
     * @return the color value of the resolved attribute
     */
    public static int resolveColor(Context context,  int attr) {
        return resolveColor(context, attr, null /* stateSet */);
    }

    /**
     * Convenience method for retrieving a themed color value.
     *
     * @param context  the {@link Context} to resolve the theme attribute against
     * @param attr     the attribute corresponding to the color to resolve
     * @param stateSet an array of {@link android.view.View} states
     * @return the color value of the resolved attribute
     */
    //根据个人需要改变颜色
    public static int resolveColor(Context context,  int attr, int[] stateSet) {
        final TypedArray a;
        synchronized (TEMP_ATTR) {
            TEMP_ATTR[0] = attr;
            a = context.obtainStyledAttributes(TEMP_ATTR);
        }

        try {
            if (stateSet == null) {
                return a.getColor(0, Color.RED);
            }

            final ColorStateList colorStateList = a.getColorStateList(0);
            if (colorStateList != null) {
                return colorStateList.getColorForState(stateSet, Color.RED);
            }
            return Color.RED;
        } finally {
            a.recycle();
        }
    }

    /**
     * Convenience method for retrieving a themed drawable.
     *
     * @param context the {@link Context} to resolve the theme attribute against
     * @param attr    the attribute corresponding to the drawable to resolve
     * @return the drawable of the resolved attribute
     */
    public static Drawable resolveDrawable(Context context,  int attr) {
        final TypedArray a;
        synchronized (TEMP_ATTR) {
            TEMP_ATTR[0] = attr;
            a = context.obtainStyledAttributes(TEMP_ATTR);
        }

        try {
            return a.getDrawable(0);
        } finally {
            a.recycle();
        }
    }
}
//AnimatorUtils.java其中有些无用代码没有剔除

package Notification;

import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.Property;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.ImageView;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class AnimatorUtils {

    public static final Interpolator DECELERATE_ACCELERATE_INTERPOLATOR = new Interpolator() {
        @Override
        public float getInterpolation(float x) {
            return 0.5f + 4.0f * (x - 0.5f) * (x - 0.5f) * (x - 0.5f);
        }
    };

    public static final Interpolator INTERPOLATOR_FAST_OUT_SLOW_IN =
            new FastOutSlowInInterpolator();

    public static final Property<View, Integer> BACKGROUND_ALPHA =
            new Property<View, Integer>(Integer.class, "background.alpha") {
        @Override
        public Integer get(View view) {
            Drawable background = view.getBackground();
            if (background instanceof LayerDrawable
                    && ((LayerDrawable) background).getNumberOfLayers() > 0) {
                background = ((LayerDrawable) background).getDrawable(0);
            }
            return background.getAlpha();
        }

        @Override
        public void set(View view, Integer value) {
            setBackgroundAlpha(view, value);
        }
    };

    /**
     * Sets the alpha of the top layer's drawable (of the background) only, if the background is a
     * layer drawable, to ensure that the other layers (i.e., the selectable item background, and
     * therefore the touch feedback RippleDrawable) are not affected.
     *
     * @param view the affected view
     * @param value the alpha value (0-255)
     */
    public static void setBackgroundAlpha(View view, Integer value) {
        Drawable background = view.getBackground();
        if (background instanceof LayerDrawable
                && ((LayerDrawable) background).getNumberOfLayers() > 0) {
            background = ((LayerDrawable) background).getDrawable(0);
        }
        background.setAlpha(value);
    }

    public static final Property<ImageView, Integer> DRAWABLE_ALPHA =
            new Property<ImageView, Integer>(Integer.class, "drawable.alpha") {
        @Override
        public Integer get(ImageView view) {
            return view.getDrawable().getAlpha();
        }

        @Override
        public void set(ImageView view, Integer value) {
            view.getDrawable().setAlpha(value);
        }
    };

    public static final Property<ImageView, Integer> DRAWABLE_TINT =
            new Property<ImageView, Integer>(Integer.class, "drawable.tint") {
        @Override
        public Integer get(ImageView view) {
            return null;
        }

        @Override
        public void set(ImageView view, Integer value) {
            // Ensure the drawable is wrapped using DrawableCompat.
            final Drawable drawable = view.getDrawable();
            final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
            if (wrappedDrawable != drawable) {
                view.setImageDrawable(wrappedDrawable);
            }
            // Set the new tint value via DrawableCompat.
            DrawableCompat.setTint(wrappedDrawable, value);
        }
    };

    @SuppressWarnings("unchecked")
    public static final TypeEvaluator<Integer> ARGB_EVALUATOR = new ArgbEvaluator();

    private static Method sAnimateValue;
    private static boolean sTryAnimateValue = true;

    public static void setAnimatedFraction(ValueAnimator animator, float fraction) {


        if (sTryAnimateValue) {
            // try to set the animated fraction directly so that it isn't affected by the
            // internal animator scale or time (b/17938711)
            try {
                if (sAnimateValue == null) {
                    sAnimateValue = ValueAnimator.class
                            .getDeclaredMethod("animateValue", float.class);
                    sAnimateValue.setAccessible(true);
                }

                sAnimateValue.invoke(animator, fraction);
                return;
            } catch (Exception e) {
                // something went wrong, don't try that again
                sTryAnimateValue = false;
            }
        }

        // if that doesn't work then just fall back to setting the current play time
        animator.setCurrentPlayTime(Math.round(fraction * animator.getDuration()));
    }

    public static void reverse(ValueAnimator... animators) {
        for (ValueAnimator animator : animators) {
            final float fraction = animator.getAnimatedFraction();
            if (fraction > 0.0f) {
                animator.reverse();
                setAnimatedFraction(animator, 1.0f - fraction);
            }
        }
    }

    public static void cancel(ValueAnimator... animators) {
        for (ValueAnimator animator : animators) {
            animator.cancel();
        }
    }

    public static ValueAnimator getScaleAnimator(View view, float... values) {
        return ObjectAnimator.ofPropertyValuesHolder(view,
                PropertyValuesHolder.ofFloat(View.SCALE_X, values),
                PropertyValuesHolder.ofFloat(View.SCALE_Y, values));
    }

    public static ValueAnimator getAlphaAnimator(View view, float... values) {
        return ObjectAnimator.ofFloat(view, View.ALPHA, values);
    }

    public static final Property<View, Integer> VIEW_LEFT =
            new Property<View, Integer>(Integer.class, "left") {
                @Override
                public Integer get(View view) {
                    return view.getLeft();
                }

                @Override
                public void set(View view, Integer left) {
                    view.setLeft(left);
                }
            };

    public static final Property<View, Integer> VIEW_TOP =
            new Property<View, Integer>(Integer.class, "top") {
                @Override
                public Integer get(View view) {
                    return view.getTop();
                }

                @Override
                public void set(View view, Integer top) {
                    view.setTop(top);
                }
            };

    public static final Property<View, Integer> VIEW_BOTTOM =
            new Property<View, Integer>(Integer.class, "bottom") {
                @Override
                public Integer get(View view) {
                    return view.getBottom();
                }

                @Override
                public void set(View view, Integer bottom) {
                    view.setBottom(bottom);
                }
            };

    public static final Property<View, Integer> VIEW_RIGHT =
            new Property<View, Integer>(Integer.class, "right") {
                @Override
                public Integer get(View view) {
                    return view.getRight();
                }

                @Override
                public void set(View view, Integer right) {
                    view.setRight(right);
                }
            };

    /**
     * @param target the view to be morphed
     * @param from the bounds of the {@code target} before animating
     * @param to the bounds of the {@code target} after animating
     * @return an animator that morphs the {@code target} between the {@code from} bounds and the
     *      {@code to} bounds. Note that it is the *content* bounds that matter here, so padding
     *      insets contributed by the background are subtracted from the views when computing the
     *      {@code target} bounds.
     */
    public static Animator getBoundsAnimator(View target, View from, View to) {
        // Fetch the content insets for the views. Content bounds are what matter, not total bounds.
        final Rect targetInsets = new Rect();
        target.getBackground().getPadding(targetInsets);
        final Rect fromInsets = new Rect();
        from.getBackground().getPadding(fromInsets);
        final Rect toInsets = new Rect();
        to.getBackground().getPadding(toInsets);

        // Before animating, the content bounds of target must match the content bounds of from.
        final int startLeft = from.getLeft() - fromInsets.left + targetInsets.left;
        final int startTop = from.getTop() - fromInsets.top + targetInsets.top;
        final int startRight = from.getRight() - fromInsets.right + targetInsets.right;
        final int startBottom = from.getBottom() - fromInsets.bottom + targetInsets.bottom;

        // After animating, the content bounds of target must match the content bounds of to.
        final int endLeft = to.getLeft() - toInsets.left + targetInsets.left;
        final int endTop = to.getTop() - toInsets.top + targetInsets.top;
        final int endRight = to.getRight() - toInsets.right + targetInsets.right;
        final int endBottom = to.getBottom() - toInsets.bottom + targetInsets.bottom;

        return getBoundsAnimator(target, startLeft, startTop, startRight, startBottom, endLeft,
                endTop, endRight, endBottom);
    }

    /**
     * Returns an animator that animates the bounds of a single view.
     */
    public static Animator getBoundsAnimator(View view, int fromLeft, int fromTop, int fromRight,
            int fromBottom, int toLeft, int toTop, int toRight, int toBottom) {
        view.setLeft(fromLeft);
        view.setTop(fromTop);
        view.setRight(fromRight);
        view.setBottom(fromBottom);

        return ObjectAnimator.ofPropertyValuesHolder(view,
                PropertyValuesHolder.ofInt(VIEW_LEFT, toLeft),
                PropertyValuesHolder.ofInt(VIEW_TOP, toTop),
                PropertyValuesHolder.ofInt(VIEW_RIGHT, toRight),
                PropertyValuesHolder.ofInt(VIEW_BOTTOM, toBottom));
    }

    public static void startDrawableAnimation(ImageView view) {
        final Drawable d = view.getDrawable();
        if (d instanceof Animatable) {
            ((Animatable) d).start();
        }
    }
}
//CircleView.java圆形扩散效果视图

package Notification;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Property;
import android.view.Gravity;
import android.view.View;

import com.demo.remind.R;


/**
 * A {@link View} that draws primitive circles.
 */
public class CircleView extends View {

    /**
     * A Property wrapper around the fillColor functionality handled by the
     * {@link #setFillColor(int)} and {@link #getFillColor()} methods.
     */
    public final static Property<CircleView, Integer> FILL_COLOR =
            new Property<CircleView, Integer>(Integer.class, "fillColor") {
        @Override
        public Integer get(CircleView view) {
            return view.getFillColor();
        }

        @Override
        public void set(CircleView view, Integer value) {
            view.setFillColor(value);
        }
    };

    /**
     * A Property wrapper around the radius functionality handled by the
     * {@link #setRadius(float)} and {@link #getRadius()} methods.
     */
    public final static Property<CircleView, Float> RADIUS =
            new Property<CircleView, Float>(Float.class, "radius") {
        @Override
        public Float get(CircleView view) {
            return view.getRadius();
        }

        @Override
        public void set(CircleView view, Float value) {
            view.setRadius(value);
        }
    };

    /**
     * The {@link Paint} used to draw the circle.
     */
    private final Paint mCirclePaint = new Paint();

    private int mGravity;
    private float mCenterX;
    private float mCenterY;
    private float mRadius;

    public CircleView(Context context) {
        this(context, null /* attrs */);
    }

    public CircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0 /* defStyleAttr */);
    }

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.CircleView, defStyleAttr, 0 /* defStyleRes */);

        mGravity = a.getInt(R.styleable.CircleView_android_gravity, Gravity.NO_GRAVITY);
        mCenterX = a.getDimension(R.styleable.CircleView_centerX, 0.0f);
        mCenterY = a.getDimension(R.styleable.CircleView_centerY, 0.0f);
        mRadius = a.getDimension(R.styleable.CircleView_radius, 0.0f);

        mCirclePaint.setColor(a.getColor(R.styleable.CircleView_fillColor, Color.WHITE));

        a.recycle();
    }

    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        super.onRtlPropertiesChanged(layoutDirection);

        if (mGravity != Gravity.NO_GRAVITY) {
            applyGravity(mGravity, layoutDirection);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (mGravity != Gravity.NO_GRAVITY) {
            applyGravity(mGravity, getLayoutDirection());
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // draw the circle, duh
        canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);
    }

    @Override
    public boolean hasOverlappingRendering() {
        // only if we have a background, which we shouldn't...
        return getBackground() != null;
    }

    /**
     * @return the current {@link Gravity} used to align/size the circle
     */
    public final int getGravity() {
        return mGravity;
    }

    /**
     * Describes how to align/size the circle relative to the view's bounds. Defaults to
     * {@link Gravity#NO_GRAVITY}.
     * <p/>
     * Note: using {@link #setCenterX(float)}, {@link #setCenterY(float)}, or
     * {@link #setRadius(float)} will automatically clear any conflicting gravity bits.
     *
     * @param gravity the {@link Gravity} flags to use
     * @return this object, allowing calls to methods in this class to be chained
     * @see
     */
    public CircleView setGravity(int gravity) {
        if (mGravity != gravity) {
            mGravity = gravity;

            if (gravity != Gravity.NO_GRAVITY && isLayoutDirectionResolved()) {
                applyGravity(gravity, getLayoutDirection());
            }
        }
        return this;
    }

    /**
     * @return the ARGB color used to fill the circle
     */
    public final int getFillColor() {
        return mCirclePaint.getColor();
    }

    /**
     * Sets the ARGB color used to fill the circle and invalidates only the affected area.
     *
     * @param color the ARGB color to use
     * @return this object, allowing calls to methods in this class to be chained
     * @see
     */
    public CircleView setFillColor(int color) {
        if (mCirclePaint.getColor() != color) {
            mCirclePaint.setColor(color);

            // invalidate the current area
            invalidate(mCenterX, mCenterY, mRadius);
        }
        return this;
    }

    /**
     * Sets the x-coordinate for the center of the circle and invalidates only the affected area.
     *
     * @param centerX the x-coordinate to use, relative to the view's bounds
     * @return this object, allowing calls to methods in this class to be chained
     * @see
     */
    public CircleView setCenterX(float centerX) {
        final float oldCenterX = mCenterX;
        if (oldCenterX != centerX) {
            mCenterX = centerX;

            // invalidate the old/new areas
            invalidate(oldCenterX, mCenterY, mRadius);
            invalidate(centerX, mCenterY, mRadius);
        }

        // clear the horizontal gravity flags
        mGravity &= ~Gravity.HORIZONTAL_GRAVITY_MASK;

        return this;
    }

    /**
     * Sets the y-coordinate for the center of the circle and invalidates only the affected area.
     *
     * @param centerY the y-coordinate to use, relative to the view's bounds
     * @return this object, allowing calls to methods in this class to be chained
     * @see
     */
    public CircleView setCenterY(float centerY) {
        final float oldCenterY = mCenterY;
        if (oldCenterY != centerY) {
            mCenterY = centerY;

            // invalidate the old/new areas
            invalidate(mCenterX, oldCenterY, mRadius);
            invalidate(mCenterX, centerY, mRadius);
        }

        // clear the vertical gravity flags
        mGravity &= ~Gravity.VERTICAL_GRAVITY_MASK;

        return this;
    }

    /**
     * @return the radius of the circle
     */
    public final float getRadius() {
        return mRadius;
    }

    /**
     * Sets the radius of the circle and invalidates only the affected area.
     *
     * @param radius the radius to use
     * @return this object, allowing calls to methods in this class to be chained
     * @see
     */
    public CircleView setRadius(float radius) {
        final float oldRadius = mRadius;
        if (oldRadius != radius) {
            mRadius = radius;

            // invalidate the old/new areas
            invalidate(mCenterX, mCenterY, oldRadius);
            if (radius > oldRadius) {
                invalidate(mCenterX, mCenterY, radius);
            }
        }

        // clear the fill gravity flags
        if ((mGravity & Gravity.FILL_HORIZONTAL) == Gravity.FILL_HORIZONTAL) {
            mGravity &= ~Gravity.FILL_HORIZONTAL;
        }
        if ((mGravity & Gravity.FILL_VERTICAL) == Gravity.FILL_VERTICAL) {
            mGravity &= ~Gravity.FILL_VERTICAL;
        }

        return this;
    }

    /**
     * Invalidates the rectangular area that circumscribes the circle defined by {@code centerX},
     * {@code centerY}, and {@code radius}.
     */
    private void invalidate(float centerX, float centerY, float radius) {
        invalidate((int) (centerX - radius - 0.5f), (int) (centerY - radius - 0.5f),
                (int) (centerX + radius + 0.5f), (int) (centerY + radius + 0.5f));
    }

    /**
     * Applies the specified {@code gravity} and {@code layoutDirection}, adjusting the alignment
     * and size of the circle depending on the resolved {@link Gravity} flags. Also invalidates the
     * affected area if necessary.
     *
     * @param gravity the {@link Gravity} the {@link Gravity} flags to use
     * @param layoutDirection the layout direction used to resolve the absolute gravity
     */
    @SuppressLint("RtlHardcoded")
    private void applyGravity(int gravity, int layoutDirection) {
        final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);

        final float oldRadius = mRadius;
        final float oldCenterX = mCenterX;
        final float oldCenterY = mCenterY;

        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.LEFT:
                mCenterX = 0.0f;
                break;
            case Gravity.CENTER_HORIZONTAL:
            case Gravity.FILL_HORIZONTAL:
                mCenterX = getWidth() / 2.0f;
                break;
            case Gravity.RIGHT:
                mCenterX = getWidth();
                break;
        }

        switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
            case Gravity.TOP:
                mCenterY = 0.0f;
                break;
            case Gravity.CENTER_VERTICAL:
            case Gravity.FILL_VERTICAL:
                mCenterY = getHeight() / 2.0f;
                break;
            case Gravity.BOTTOM:
                mCenterY = getHeight();
                break;
        }

        switch (absoluteGravity & Gravity.FILL) {
            case Gravity.FILL:
                mRadius = Math.min(getWidth(), getHeight()) / 2.0f;
                break;
            case Gravity.FILL_HORIZONTAL:
                mRadius = getWidth() / 2.0f;
                break;
            case Gravity.FILL_VERTICAL:
                mRadius = getHeight() / 2.0f;
                break;
        }

        if (oldCenterX != mCenterX || oldCenterY != mCenterY || oldRadius != mRadius) {
            invalidate(oldCenterX, oldCenterY, oldRadius);
            invalidate(mCenterX, mCenterY, mRadius);
        }
    }
}
//BaseActivity.java

package Notification;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;

import android.view.View;


/**
 * Base activity class that changes the app window's color based on the current hour.
 */
public abstract class BaseActivity extends Activity {
    public static final TypeEvaluator<Integer> ARGB_EVALUATOR = new ArgbEvaluator();
    /** Sets the app window color on each frame of the {@link #mAppColorAnimator}. */
    private final AppColorAnimationListener mAppColorAnimationListener
            = new AppColorAnimationListener();

    /** The current animator that is changing the app window color or {@code null}. */
    private ValueAnimator mAppColorAnimator;

    /** Draws the app window's color. */
    private ColorDrawable mBackground;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Allow the content to layout behind the status and navigation bars.
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

        final  int color = ThemeUtils.resolveColor(this, android.R.attr.windowBackground);
        adjustAppColor(color, false /* animate */);
    }

    @Override
    protected void onStart() {
        super.onStart();

        // Ensure the app window color is up-to-date.
        final  int color = ThemeUtils.resolveColor(this, android.R.attr.windowBackground);
        adjustAppColor(color, false /* animate */);
    }

    /**
     * Adjusts the current app window color of this activity; animates the change if desired.
     *
     * @param color   the ARGB value to set as the current app window color
     * @param animate {@code true} if the change should be animated
     */
    protected void adjustAppColor( int color, boolean animate) {
        // Create and install the drawable that defines the window color.
        if (mBackground == null) {
            mBackground = new ColorDrawable(color);
            getWindow().setBackgroundDrawable(mBackground);
        }

        // Cancel the current window color animation if one exists.
        if (mAppColorAnimator != null) {
            mAppColorAnimator.cancel();
        }

        final int currentColor = mBackground.getColor();
        if (currentColor != color) {
            if (animate) {
                mAppColorAnimator = ValueAnimator.ofObject(ARGB_EVALUATOR, currentColor, color)
                        .setDuration(3000L);
                mAppColorAnimator.addUpdateListener(mAppColorAnimationListener);
                mAppColorAnimator.addListener(mAppColorAnimationListener);
                mAppColorAnimator.start();
            } else {
                setAppColor(color);
            }
        }
    }

    private void setAppColor( int color) {
        mBackground.setColor(color);
    }

    /**
     * Sets the app window color to the current color produced by the animator.
     */
    private final class AppColorAnimationListener extends AnimatorListenerAdapter
            implements AnimatorUpdateListener {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            final int color = (int) valueAnimator.getAnimatedValue();
            setAppColor(color);
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            if (mAppColorAnimator == animation) {
                mAppColorAnimator = null;
            }
        }
    }
}
//AlarmActivity.java


package Notification;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.view.animation.PathInterpolatorCompat;
import android.os.IBinder;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.TextClock;
import android.widget.TextView;


import com.demo.remind.R;

import java.util.List;

import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;

public class AlarmActivity extends BaseActivity
        implements View.OnClickListener, View.OnTouchListener {
    String TAG = "AlarmActivity";

    public static final String ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE";

    /**
     * AlarmActivity and AlarmService listen for this broadcast intent so that other
     * applications can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION).
     */
    public static final String ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS";

    /**
     * A public action sent by AlarmService when the alarm has started.
     */
    public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";

    /**
     * A public action sent by AlarmService when the alarm has stopped for any reason.
     */
    public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE";

    /**
     * Private action used to stop an alarm with this service.
     */
    public static final String STOP_ALARM_ACTION = "STOP_ALARM";

    private static final TimeInterpolator PULSE_INTERPOLATOR =
            PathInterpolatorCompat.create(0.4f, 0.0f, 0.2f, 1.0f);
    private static final TimeInterpolator REVEAL_INTERPOLATOR =
            PathInterpolatorCompat.create(0.0f, 0.0f, 0.2f, 1.0f);


    private static final int PULSE_DURATION_MILLIS = 1000;
    private static final int ALARM_BOUNCE_DURATION_MILLIS = 500;
    private static final int ALERT_REVEAL_DURATION_MILLIS = 500;
    private static final int ALERT_FADE_DURATION_MILLIS = 500;
    private static final int ALERT_DISMISS_DELAY_MILLIS = 2000;

    private static final float BUTTON_SCALE_DEFAULT = 0.7f;
    private static final int BUTTON_DRAWABLE_ALPHA_DEFAULT = 165;

    private final Handler mHandler = new Handler();
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            Log.v(TAG, "Received broadcast: %s" + action);

            if (!mAlarmHandled) {
                switch (action) {
                    case ALARM_SNOOZE_ACTION:
                        snooze();
                        break;
                    case ALARM_DISMISS_ACTION:
                        dismiss();
                        break;
                    case ALARM_DONE_ACTION:
                        finish();
                        break;
                    default:
                        Log.i(TAG, "Unknown broadcast: %s" + action);
                        break;
                }
            } else {
                Log.v("Ignored broadcast: %s", action);
            }
        }
    };


    private boolean mAlarmHandled;
    private int mCurrentHourColor;
    private AccessibilityManager mAccessibilityManager;

    private ViewGroup mAlertView;
    private TextView mAlertTitleView;
    private TextView mAlertInfoView;

    private ViewGroup mContentView;
    private ImageView mAlarmButton;
    private ImageView mSnoozeButton;
    private ImageView mDismissButton;
    private TextView mHintView;

    private ValueAnimator mAlarmAnimator;
    private ValueAnimator mSnoozeAnimator;
    private ValueAnimator mDismissAnimator;
    private ValueAnimator mPulseAnimator;

    private int mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setVolumeControlStream(AudioManager.STREAM_ALARM);

        // Get the volume/camera button behavior setting

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);

        // Hide navigation bar to minimize accidental tap on Home key
        hideNavigationBar();

        // Close dialogs and window shade, so this is fully visible
        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));

        mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);

        setContentView(R.layout.alarm_activity);

        mAlertView = (ViewGroup) findViewById(R.id.alert);
        mAlertTitleView = (TextView) mAlertView.findViewById(R.id.alert_title);
        mAlertInfoView = (TextView) mAlertView.findViewById(R.id.alert_info);

        mContentView = (ViewGroup) findViewById(R.id.content);
        mAlarmButton = (ImageView) mContentView.findViewById(R.id.alarm);
        mSnoozeButton = (ImageView) mContentView.findViewById(R.id.snooze);
        mDismissButton = (ImageView) mContentView.findViewById(R.id.dismiss);
        mHintView = (TextView) mContentView.findViewById(R.id.hint);

        final TextView titleView = (TextView) mContentView.findViewById(R.id.title);
        final TextClock digitalClock = (TextClock) mContentView.findViewById(R.id.digital_clock);
        final CircleView pulseView = (CircleView) mContentView.findViewById(R.id.pulse);

        titleView.setText("dddddddddddddddd");

        mCurrentHourColor = getResources().getColor(R.color.black_54p);
        getWindow().setBackgroundDrawable(new ColorDrawable(mCurrentHourColor));

        mAlarmButton.setOnTouchListener(this);
        mSnoozeButton.setOnClickListener(this);
        mDismissButton.setOnClickListener(this);

        mAlarmAnimator = AnimatorUtils.getScaleAnimator(mAlarmButton, 1.0f, 0.0f);
        mSnoozeAnimator = getButtonAnimator(mSnoozeButton, Color.WHITE);
        mDismissAnimator = getButtonAnimator(mDismissButton, mCurrentHourColor);
        mPulseAnimator = ObjectAnimator.ofPropertyValuesHolder(pulseView,
                PropertyValuesHolder.ofFloat(CircleView.RADIUS, 0.0f, pulseView.getRadius()),
                PropertyValuesHolder.ofObject(CircleView.FILL_COLOR, AnimatorUtils.ARGB_EVALUATOR,
                        ColorUtils.setAlphaComponent(pulseView.getFillColor(), 0)));
        mPulseAnimator.setDuration(PULSE_DURATION_MILLIS);
        mPulseAnimator.setInterpolator(PULSE_INTERPOLATOR);
        mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mPulseAnimator.start();
    }

    @Override
    protected void onResume() {
        super.onResume();
        resetAnimations();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent keyEvent) {
        // Do this in dispatch to intercept a few of the system keys.
        Log.v(TAG, "dispatchKeyEvent: %s" + keyEvent);

        final int keyCode = keyEvent.getKeyCode();
        switch (keyCode) {
            // Volume keys and camera keys dismiss the alarm.
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_MUTE:
            case KeyEvent.KEYCODE_HEADSETHOOK:
            case KeyEvent.KEYCODE_CAMERA:
            case KeyEvent.KEYCODE_FOCUS:
                if (!mAlarmHandled) {
                    if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
                        snooze();
                    }
                    return true;

                }
        }
        return super.dispatchKeyEvent(keyEvent);
    }

    @Override
    public void onBackPressed() {
        // Don't allow back to dismiss.
    }

    @Override
    public void onClick(View view) {
        if (mAlarmHandled) {
            return;
        }

        // If in accessibility mode, allow snooze/dismiss by double tapping on respective icons.
        if (isAccessibilityEnabled()) {
            if (view == mSnoozeButton) {
                snooze();
            } else if (view == mDismissButton) {
                dismiss();
            }
            return;
        }

        if (view == mSnoozeButton) {
            hintSnooze();
        } else if (view == mDismissButton) {
            hintDismiss();
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {


        final int action = event.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {

            // Track the pointer that initiated the touch sequence.
            mInitialPointerIndex = event.getPointerId(event.getActionIndex());

            // Stop the pulse, allowing the last pulse to finish.
            mPulseAnimator.setRepeatCount(0);
        } else if (action == MotionEvent.ACTION_CANCEL) {

            // Clear the pointer index.
            mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID;

            // Reset everything.
            resetAnimations();
        }

        final int actionIndex = event.getActionIndex();
        if (mInitialPointerIndex == MotionEvent.INVALID_POINTER_ID
                || mInitialPointerIndex != event.getPointerId(actionIndex)) {
            // Ignore any pointers other than the initial one, bail early.
            return true;
        }

        final int[] contentLocation = {0, 0};
        mContentView.getLocationOnScreen(contentLocation);

        final float x = event.getRawX() - contentLocation[0];
        final float y = event.getRawY() - contentLocation[1];

        final int alarmLeft = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft();
        final int alarmRight = mAlarmButton.getRight() - mAlarmButton.getPaddingRight();

        final float snoozeFraction, dismissFraction;
       /* if (mContentView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
            snoozeFraction = getFraction(alarmRight, mSnoozeButton.getLeft(), x);
            dismissFraction = getFraction(alarmLeft, mDismissButton.getRight(), x);
        } else {*/
        snoozeFraction = getFraction(alarmLeft, mSnoozeButton.getRight(), x);
        dismissFraction = getFraction(alarmRight, mDismissButton.getLeft(), x);
        //}
        setAnimatedFractions(snoozeFraction, dismissFraction);

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {

            mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID;
            if (snoozeFraction == 1.0f) {
                snooze();
            } else if (dismissFraction == 1.0f) {
                dismiss();
            } else {
                if (snoozeFraction > 0.0f || dismissFraction > 0.0f) {
                    // Animate back to the initial state.
                    AnimatorUtils.reverse(mAlarmAnimator, mSnoozeAnimator, mDismissAnimator);
                } else if (mAlarmButton.getTop() <= y && y <= mAlarmButton.getBottom()) {
                    // User touched the alarm button, hint the dismiss action.
                    hintDismiss();
                }

                // Restart the pulse.
                mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE);
                if (!mPulseAnimator.isStarted()) {
                    mPulseAnimator.start();
                }
            }
        }

        return true;
    }

    private void hideNavigationBar() {
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }

    /**
     * Returns {@code true} if accessibility is enabled, to enable alternate behavior for click
     * handling, etc.
     */
    private boolean isAccessibilityEnabled() {
        if (mAccessibilityManager == null || !mAccessibilityManager.isEnabled()) {
            // Accessibility is unavailable or disabled.
            return false;
        } else if (mAccessibilityManager.isTouchExplorationEnabled()) {
            // TalkBack's touch exploration mode is enabled.
            return true;
        }

        // Check if "Switch Access" is enabled.
        final List<AccessibilityServiceInfo> enabledAccessibilityServices =
                mAccessibilityManager.getEnabledAccessibilityServiceList(FEEDBACK_GENERIC);
        return !enabledAccessibilityServices.isEmpty();
    }

    private void hintSnooze() {
        final int alarmLeft = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft();
        final int alarmRight = mAlarmButton.getRight() - mAlarmButton.getPaddingRight();
        final float translationX = Math.max(mSnoozeButton.getLeft() - alarmRight, 0)
                + Math.min(mSnoozeButton.getRight() - alarmLeft, 0);
        getAlarmBounceAnimator(translationX, translationX < 0.0f ?
                R.string.description_direction_left : R.string.description_direction_right).start();
    }

    private void hintDismiss() {
        final int alarmLeft = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft();
        final int alarmRight = mAlarmButton.getRight() - mAlarmButton.getPaddingRight();
        final float translationX = Math.max(mDismissButton.getLeft() - alarmRight, 0)
                + Math.min(mDismissButton.getRight() - alarmLeft, 0);
        getAlarmBounceAnimator(translationX, translationX < 0.0f ?
                R.string.description_direction_left : R.string.description_direction_right).start();
    }

    /**
     * Set animators to initial values and restart pulse on alarm button.
     */
    private void resetAnimations() {
        // Set the animators to their initial values.
        setAnimatedFractions(0.0f /* snoozeFraction */, 0.0f /* dismissFraction */);
        // Restart the pulse.
        mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE);
        if (!mPulseAnimator.isStarted()) {
            mPulseAnimator.start();
        }
    }

    /**
     * Perform snooze animation and send snooze intent.
     */
    private void snooze() {
        mAlarmHandled = true;

        final int colorAccent = getResources().getColor(R.color.black_54p);//ThemeUtils.resolveColor(this, R.attr.colorAccent);
        setAnimatedFractions(1.0f /* snoozeFraction */, 0.0f /* dismissFraction */);

        final String infoText = "";
        final String accessibilityText = "hellow";

        getAlertAnimator(mSnoozeButton, R.string.alarm_alert_snoozed_text, infoText,
                accessibilityText, colorAccent, colorAccent).start();

    }

    private void dismiss() {
        mAlarmHandled = true;
        setAnimatedFractions(0.0f /* snoozeFraction */, 1.0f /* dismissFraction */);
        getAlertAnimator(mDismissButton, R.string.alarm_alert_off_text, null /* infoText */,
                getString(R.string.alarm_alert_off_text) /* accessibilityText */,
                Color.WHITE, mCurrentHourColor).start();
    }


    private void setAnimatedFractions(float snoozeFraction, float dismissFraction) {
        final float alarmFraction = Math.max(snoozeFraction, dismissFraction);
        AnimatorUtils.setAnimatedFraction(mAlarmAnimator, alarmFraction);
        AnimatorUtils.setAnimatedFraction(mSnoozeAnimator, snoozeFraction);
        AnimatorUtils.setAnimatedFraction(mDismissAnimator, dismissFraction);
    }

    private float getFraction(float x0, float x1, float x) {
        return Math.max(Math.min((x - x0) / (x1 - x0), 1.0f), 0.0f);
    }

    private ValueAnimator getButtonAnimator(ImageView button, int tintColor) {
        return ObjectAnimator.ofPropertyValuesHolder(button,
                PropertyValuesHolder.ofFloat(View.SCALE_X, BUTTON_SCALE_DEFAULT, 1.0f),
                PropertyValuesHolder.ofFloat(View.SCALE_Y, BUTTON_SCALE_DEFAULT, 1.0f),
                PropertyValuesHolder.ofInt(AnimatorUtils.BACKGROUND_ALPHA, 0, 255),
                PropertyValuesHolder.ofInt(AnimatorUtils.DRAWABLE_ALPHA,
                        BUTTON_DRAWABLE_ALPHA_DEFAULT, 255),
                PropertyValuesHolder.ofObject(AnimatorUtils.DRAWABLE_TINT,
                        AnimatorUtils.ARGB_EVALUATOR, Color.WHITE, tintColor));
    }

    private ValueAnimator getAlarmBounceAnimator(float translationX, final int hintResId) {
        final ValueAnimator bounceAnimator = ObjectAnimator.ofFloat(mAlarmButton,
                View.TRANSLATION_X, mAlarmButton.getTranslationX(), translationX, 0.0f);
        bounceAnimator.setInterpolator(AnimatorUtils.DECELERATE_ACCELERATE_INTERPOLATOR);
        bounceAnimator.setDuration(ALARM_BOUNCE_DURATION_MILLIS);
        bounceAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animator) {
                mHintView.setText(hintResId);
                if (mHintView.getVisibility() != View.VISIBLE) {
                    mHintView.setVisibility(View.VISIBLE);
                    ObjectAnimator.ofFloat(mHintView, View.ALPHA, 0.0f, 1.0f).start();
                }
            }
        });
        return bounceAnimator;
    }

    private Animator getAlertAnimator(final View source, final int titleResId,
                                      final String infoText, final String accessibilityText, final int revealColor,
                                      final int backgroundColor) {
        final ViewGroup containerView = (ViewGroup) findViewById(android.R.id.content);

        final Rect sourceBounds = new Rect(0, 0, source.getHeight(), source.getWidth());
        containerView.offsetDescendantRectToMyCoords(source, sourceBounds);

        final int centerX = sourceBounds.centerX();
        final int centerY = sourceBounds.centerY();

        final int xMax = Math.max(centerX, containerView.getWidth() - centerX);
        final int yMax = Math.max(centerY, containerView.getHeight() - centerY);

        final float startRadius = Math.max(sourceBounds.width(), sourceBounds.height()) / 2.0f;
        final float endRadius = (float) Math.sqrt(xMax * xMax + yMax * yMax);

        final CircleView revealView = new CircleView(this)
                .setCenterX(centerX)
                .setCenterY(centerY)
                .setFillColor(revealColor);
        containerView.addView(revealView);

        // TODO: Fade out source icon over the reveal (like LOLLIPOP version).

        final Animator revealAnimator = ObjectAnimator.ofFloat(
                revealView, CircleView.RADIUS, startRadius, endRadius);
        revealAnimator.setDuration(ALERT_REVEAL_DURATION_MILLIS);
        revealAnimator.setInterpolator(REVEAL_INTERPOLATOR);
        revealAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animator) {
                mAlertView.setVisibility(View.VISIBLE);
                mAlertTitleView.setText(titleResId);

                if (infoText != null) {
                    mAlertInfoView.setText(infoText);
                    mAlertInfoView.setVisibility(View.VISIBLE);
                }
                mContentView.setVisibility(View.GONE);

                getWindow().setBackgroundDrawable(new ColorDrawable(backgroundColor));
            }
        });

        final ValueAnimator fadeAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
        fadeAnimator.setDuration(ALERT_FADE_DURATION_MILLIS);
        fadeAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                containerView.removeView(revealView);
            }
        });

        final AnimatorSet alertAnimator = new AnimatorSet();
        alertAnimator.play(revealAnimator).before(fadeAnimator);
        alertAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animator) {
                mAlertView.announceForAccessibility(accessibilityText);
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        finish();
                    }
                }, ALERT_DISMISS_DELAY_MILLIS);
            }
        });

        return alertAnimator;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值