All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.clockwork.globalactions.ViewGroupFader Maven / Gradle / Ivy

package com.android.clockwork.globalactions;

import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.BaseInterpolator;
import android.view.animation.PathInterpolator;

/**
 * This class is ported from
 * {@link com.google.android.clockwork.common.wearable.wearmaterial.list.ViewGroupFader} with minor
 * modifications set the opacity of the views during animation (uses setAlpha on the view instead of
 * setLayerType as the latter doesn't play nicely with a dialog. See - b/193583546)
 *
 * Fades of the children of a {@link ViewGroup} in and out, based on the position of the child.
 *
 * 

Children are "faded" when they lie entirely in a region on the top and bottom of a {@link * ViewGroup}. This region is sized as a percentage of the {@link ViewGroup}'s height, based on the * height of the child. When not in the top or bottom regions, children have their default alpha and * scale. * */ public class ViewGroupFader { public static final float SCALE_LOWER_BOUND = 0.7f; private float scaleLowerBound = SCALE_LOWER_BOUND; public static final float ALPHA_LOWER_BOUND = 0.5f; private float alphaLowerBound = ALPHA_LOWER_BOUND; private static final float CHAINED_BOUNDS_TOP_PERCENT = 0.6f; private static final float CHAINED_BOUNDS_BOTTOM_PERCENT = 0.2f; private static final float CHAINED_LOWER_REGION_PERCENT = 0.35f; private static final float CHAINED_UPPER_REGION_PERCENT = 0.55f; public float chainedBoundsTop = CHAINED_BOUNDS_TOP_PERCENT; public float chainedBoundsBottom = CHAINED_BOUNDS_BOTTOM_PERCENT; public float chainedLowerRegion = CHAINED_LOWER_REGION_PERCENT; public float chainedUpperRegion = CHAINED_UPPER_REGION_PERCENT; protected final ViewGroup parent; private final Rect containerBounds = new Rect(); private final Rect offsetViewBounds = new Rect(); private final AnimationCallback callback; private final ChildViewBoundsProvider childViewBoundsProvider; private ContainerBoundsProvider containerBoundsProvider; private float topBoundPixels; private float bottomBoundPixels; private BaseInterpolator topInterpolator = new PathInterpolator(0.3f, 0f, 0.7f, 1f); private BaseInterpolator bottomInterpolator = new PathInterpolator(0.3f, 0f, 0.7f, 1f); /** Callback which is called when attempting to fade a view. */ public interface AnimationCallback { boolean shouldFadeFromTop(View view); boolean shouldFadeFromBottom(View view); void viewHasBecomeFullSize(View view); } /** * Interface for providing the bounds of the child views. This is needed because for * RecyclerViews, we might need to use bounds that represents the post-layout position, instead of * the current position. */ // TODO(b/182846214): Clean up the interface design to avoid exposing too much details to users. public interface ChildViewBoundsProvider { void provideBounds(ViewGroup parent, View child, Rect bounds); } /** Interface for providing the bounds of the container for use in calculating item fades. */ public interface ContainerBoundsProvider { void provideBounds(ViewGroup parent, Rect bounds); } /** * Implementation of {@link ContainerBoundsProvider} that returns the screen bounds as the * container that is used for calculating the animation of the child elements in the ViewGroup. */ public static final class ScreenContainerBoundsProvider implements ContainerBoundsProvider { @Override public void provideBounds(ViewGroup parent, Rect bounds) { bounds.set( 0, 0, parent.getResources().getDisplayMetrics().widthPixels, parent.getResources().getDisplayMetrics().heightPixels); } } /** * Implementation of {@link ContainerBoundsProvider} that returns the parent ViewGroup bounds as * the container that is used for calculating the animation of the child elements in the * ViewGroup. */ public static final class ParentContainerBoundsProvider implements ContainerBoundsProvider { @Override public void provideBounds(ViewGroup parent, Rect bounds) { parent.getGlobalVisibleRect(bounds); } } /** * Default implementation of {@link ChildViewBoundsProvider} that returns the post-layout bounds * of the child view. This should be used when the {@link ViewGroupFader} is used together with a * RecyclerView. */ public static final class DefaultViewBoundsProvider implements ChildViewBoundsProvider { @Override public void provideBounds(ViewGroup parent, View child, Rect bounds) { child.getDrawingRect(bounds); bounds.offset(0, (int) child.getTranslationY()); parent.offsetDescendantRectToMyCoords(child, bounds); // Additionally offset the bounds based on parent container's absolute position. Rect parentGlobalVisibleBounds = new Rect(); parent.getGlobalVisibleRect(parentGlobalVisibleBounds); bounds.offset(parentGlobalVisibleBounds.left, parentGlobalVisibleBounds.top); } } /** * Implementation of {@link ChildViewBoundsProvider} that returns the global visible bounds of the * child view. This should be used when the {@link ViewGroupFader} is not used together with a * RecyclerView. */ public static final class GlobalVisibleViewBoundsProvider implements ChildViewBoundsProvider { @Override public void provideBounds(ViewGroup parent, View child, Rect bounds) { // Get the absolute position of the child. Normally we'd need to also reset the transformation // matrix before computing this, but the transformations we apply set a pivot that preserves // the coordinate of the top/bottom boundary used to compute the scaling factor in the first // place. child.getGlobalVisibleRect(bounds); } } public ViewGroupFader( ViewGroup parent, AnimationCallback callback, ChildViewBoundsProvider childViewBoundsProvider) { this.parent = parent; this.callback = callback; this.childViewBoundsProvider = childViewBoundsProvider; this.containerBoundsProvider = new ScreenContainerBoundsProvider(); } public AnimationCallback getAnimationCallback() { return callback; } public void setScaleLowerBound(float scale) { scaleLowerBound = scale; } public void setAlphaLowerBound(float alpha) { alphaLowerBound = alpha; } public void setTopInterpolator(BaseInterpolator interpolator) { this.topInterpolator = interpolator; } public void setBottomInterpolator(BaseInterpolator interpolator) { this.bottomInterpolator = interpolator; } public void setContainerBoundsProvider(ContainerBoundsProvider boundsProvider) { this.containerBoundsProvider = boundsProvider; } public void updateFade() { containerBoundsProvider.provideBounds(parent, containerBounds); topBoundPixels = containerBounds.height() * chainedBoundsTop; bottomBoundPixels = containerBounds.height() * chainedBoundsBottom; updateListElementFades(); } /** For each list element, calculate and adjust the scale and alpha based on its position */ private void updateListElementFades() { for (int i = 0; i < parent.getChildCount(); i++) { View child = parent.getChildAt(i); if (child.getVisibility() != View.VISIBLE) { continue; } childViewBoundsProvider.provideBounds(parent, child, offsetViewBounds); animateViewByPosition(child, offsetViewBounds, topBoundPixels, bottomBoundPixels); } } /** Set the bounds and change the view's scale and alpha accordingly */ private void animateViewByPosition( View view, Rect bounds, float topBoundPixels, float bottomBoundPixels) { float fadeOutRegionPercent; if (view.getHeight() < topBoundPixels && view.getHeight() > bottomBoundPixels) { // Scale from LOWER_REGION_PERCENT to UPPER_REGION_PERCENT based on the ratio of view height // to chain region height fadeOutRegionPercent = lerp( chainedLowerRegion, chainedUpperRegion, (view.getHeight() - bottomBoundPixels) / (topBoundPixels - bottomBoundPixels)); } else if (view.getHeight() < bottomBoundPixels) { fadeOutRegionPercent = chainedLowerRegion; } else { fadeOutRegionPercent = chainedUpperRegion; } int fadeOutRegionHeight = (int) (containerBounds.height() * fadeOutRegionPercent); int topFadeBoundary = fadeOutRegionHeight + containerBounds.top; int bottomFadeBoundary = containerBounds.bottom - fadeOutRegionHeight; boolean wasFullSize = (view.getScaleX() == 1); MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams(); view.setPivotX(view.getWidth() * 0.5f); if (bounds.top > bottomFadeBoundary && callback.shouldFadeFromBottom(view)) { view.setPivotY((float) -lp.topMargin); scaleAndFadeByRelativeOffset( view, bottomInterpolator.getInterpolation( (float) (containerBounds.bottom - bounds.top) / fadeOutRegionHeight)); } else if (bounds.bottom < topFadeBoundary && callback.shouldFadeFromTop(view)) { view.setPivotY(view.getMeasuredHeight() + (float) lp.bottomMargin); scaleAndFadeByRelativeOffset( view, topInterpolator.getInterpolation( (float) (bounds.bottom - containerBounds.top) / fadeOutRegionHeight)); } else { if (!wasFullSize) { callback.viewHasBecomeFullSize(view); } setDefaultSizeAndAlphaForView(view); } } /** Change the scale and opacity of the view based on its offset to the determining bound */ private void scaleAndFadeByRelativeOffset(View view, float offset) { float alpha = lerp(alphaLowerBound, 1, offset); view.setAlpha(alpha); float scale = lerp(scaleLowerBound, 1, offset); view.setScaleX(scale); view.setScaleY(scale); } /** Set the scale and alpha of the view to the full default */ private void setDefaultSizeAndAlphaForView(View view) { view.setAlpha(1f); view.setScaleX(1f); view.setScaleY(1f); } /** * Linear interpolation between [start, end] using value as fraction. * * @param min the starting point of the interpolation range. * @param max the ending point of the interpolation range. * @param value the proportion of the range to linearly interpolate for. * @return the interpolated value. */ private static float lerp(float min, float max, float value) { return min + (max - min) * value; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy