/*
* Copyright 2016 Google Inc.
*
* 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 io.plaidapp.ui.transitions;
import android.animation.Animator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Point;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.transition.TransitionValues;
import android.transition.Visibility;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import io.plaidapp.R;
import io.plaidapp.util.AnimUtils;
/**
* A transition which shows/hides a view with a circular clipping mask. Callers should provide the
* center point of the reveal either {@link #setCenter(Point) directly} or by
* {@link #centerOn(View) specifying} another view to center on; otherwise the target {@code view}'s
* pivot point will be used.
*/
public class CircularReveal extends Visibility {
private Point center;
private float startRadius;
private float endRadius;
private @IdRes int centerOnId = View.NO_ID;
private View centerOn;
public CircularReveal() {
super();
}
public CircularReveal(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircularReveal);
startRadius = a.getDimension(R.styleable.CircularReveal_startRadius, 0f);
endRadius = a.getDimension(R.styleable.CircularReveal_endRadius, 0f);
centerOnId = a.getResourceId(R.styleable.CircularReveal_centerOn, View.NO_ID);
a.recycle();
}
/**
* The center point of the reveal or conceal, relative to the target {@code view}.
*/
public void setCenter(@NonNull Point center) {
this.center = center;
}
/**
* Center the reveal or conceal on this view.
*/
public void centerOn(@NonNull View source) {
centerOn = source;
}
/**
* Sets the radius that <strong>reveals</strong> start from.
*/
public void setStartRadius(float startRadius) {
this.startRadius = startRadius;
}
/**
* Sets the radius that <strong>conceals</strong> end at.
*/
public void setEndRadius(float endRadius) {
this.endRadius = endRadius;
}
@Override
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues,
TransitionValues endValues) {
if (view == null || view.getHeight() == 0 || view.getWidth() == 0) return null;
ensureCenterPoint(sceneRoot, view);
return new AnimUtils.NoPauseAnimator(ViewAnimationUtils.createCircularReveal(
view,
center.x,
center.y,
startRadius,
getFullyRevealedRadius(view)));
}
@Override
public Animator onDisappear(ViewGroup sceneRoot, View view,
TransitionValues startValues,
TransitionValues endValues) {
if (view == null || view.getHeight() == 0 || view.getWidth() == 0) return null;
ensureCenterPoint(sceneRoot, view);
return new AnimUtils.NoPauseAnimator(ViewAnimationUtils.createCircularReveal(
view,
center.x,
center.y,
getFullyRevealedRadius(view),
endRadius));
}
private void ensureCenterPoint(ViewGroup sceneRoot, View view) {
if (center != null) return;
if (centerOn != null || centerOnId != View.NO_ID) {
View source;
if (centerOn != null) {
source = centerOn;
} else {
source = sceneRoot.findViewById(centerOnId);
}
if (source != null) {
// use window location to allow views in diff hierarchies
int[] loc = new int[2];
source.getLocationInWindow(loc);
int srcX = loc[0] + (source.getWidth() / 2);
int srcY = loc[1] + (source.getHeight() / 2);
view.getLocationInWindow(loc);
center = new Point(srcX - loc[0], srcY - loc[1]);
}
}
// else use the pivot point
if (center == null) {
center = new Point(Math.round(view.getPivotX()), Math.round(view.getPivotY()));
}
}
private float getFullyRevealedRadius(@NonNull View view) {
return (float) Math.hypot(
Math.max(center.x, view.getWidth() - center.x),
Math.max(center.y, view.getHeight() - center.y));
}
}