1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.app; 18 19import android.content.Context; 20import android.graphics.Canvas; 21import android.graphics.Rect; 22import android.graphics.drawable.BitmapDrawable; 23import android.util.AttributeSet; 24import android.view.animation.Interpolator; 25import android.widget.ListView; 26 27import java.util.ArrayList; 28import java.util.Iterator; 29import java.util.List; 30 31/** 32 * A ListView which has an additional overlay layer. {@link BitmapDrawable} 33 * can be added to the layer and can be animated. 34 */ 35final class OverlayListView extends ListView { 36 private final List<OverlayObject> mOverlayObjects = new ArrayList<>(); 37 38 public OverlayListView(Context context) { 39 super(context); 40 } 41 42 public OverlayListView(Context context, AttributeSet attrs) { 43 super(context, attrs); 44 } 45 46 public OverlayListView(Context context, AttributeSet attrs, int defStyleAttr) { 47 super(context, attrs, defStyleAttr); 48 } 49 50 /** 51 * Adds an object to the overlay layer. 52 * 53 * @param object An object to be added. 54 */ 55 public void addOverlayObject(OverlayObject object) { 56 mOverlayObjects.add(object); 57 } 58 59 /** 60 * Starts all animations of objects in the overlay layer. 61 */ 62 public void startAnimationAll() { 63 for (OverlayObject object : mOverlayObjects) { 64 if (!object.isAnimationStarted()) { 65 object.startAnimation(getDrawingTime()); 66 } 67 } 68 } 69 70 /** 71 * Stops all animations of objects in the overlay layer. 72 */ 73 public void stopAnimationAll() { 74 for (OverlayObject object : mOverlayObjects) { 75 object.stopAnimation(); 76 } 77 } 78 79 @Override 80 public void onDraw(Canvas canvas) { 81 super.onDraw(canvas); 82 if (mOverlayObjects.size() > 0) { 83 Iterator<OverlayObject> it = mOverlayObjects.iterator(); 84 while (it.hasNext()) { 85 OverlayObject object = it.next(); 86 BitmapDrawable bitmap = object.getBitmapDrawable(); 87 if (bitmap != null) { 88 bitmap.draw(canvas); 89 } 90 if (!object.update(getDrawingTime())) { 91 it.remove(); 92 } 93 } 94 } 95 } 96 97 /** 98 * A class that represents an object to be shown in the overlay layer. 99 */ 100 public static class OverlayObject { 101 private BitmapDrawable mBitmap; 102 private float mCurrentAlpha = 1.0f; 103 private Rect mCurrentBounds; 104 private Interpolator mInterpolator; 105 private long mDuration; 106 private Rect mStartRect; 107 private int mDeltaY; 108 private float mStartAlpha = 1.0f; 109 private float mEndAlpha = 1.0f; 110 private long mStartTime; 111 private boolean mIsAnimationStarted; 112 private boolean mIsAnimationEnded; 113 private OnAnimationEndListener mListener; 114 115 public OverlayObject(BitmapDrawable bitmap, Rect startRect) { 116 mBitmap = bitmap; 117 mStartRect = startRect; 118 mCurrentBounds = new Rect(startRect); 119 if (mBitmap != null && mCurrentBounds != null) { 120 mBitmap.setAlpha((int) (mCurrentAlpha * 255)); 121 mBitmap.setBounds(mCurrentBounds); 122 } 123 } 124 125 /** 126 * Returns the bitmap that this object represents. 127 * 128 * @return BitmapDrawable that this object has. 129 */ 130 public BitmapDrawable getBitmapDrawable() { 131 return mBitmap; 132 } 133 134 /** 135 * Returns the started status of the animation. 136 * 137 * @return True if the animation has started, false otherwise. 138 */ 139 public boolean isAnimationStarted() { 140 return mIsAnimationStarted; 141 } 142 143 /** 144 * Sets animation for varying alpha. 145 * 146 * @param startAlpha Starting alpha value for the animation, where 1.0 means 147 * fully opaque and 0.0 means fully transparent. 148 * @param endAlpha Ending alpha value for the animation. 149 * @return This OverlayObject to allow for chaining of calls. 150 */ 151 public OverlayObject setAlphaAnimation(float startAlpha, float endAlpha) { 152 mStartAlpha = startAlpha; 153 mEndAlpha = endAlpha; 154 return this; 155 } 156 157 /** 158 * Sets animation for moving objects vertically. 159 * 160 * @param deltaY Distance to move in pixels. 161 * @return This OverlayObject to allow for chaining of calls. 162 */ 163 public OverlayObject setTranslateYAnimation(int deltaY) { 164 mDeltaY = deltaY; 165 return this; 166 } 167 168 /** 169 * Sets how long the animation will last. 170 * 171 * @param duration Duration in milliseconds 172 * @return This OverlayObject to allow for chaining of calls. 173 */ 174 public OverlayObject setDuration(long duration) { 175 mDuration = duration; 176 return this; 177 } 178 179 /** 180 * Sets the acceleration curve for this animation. 181 * 182 * @param interpolator The interpolator which defines the acceleration curve 183 * @return This OverlayObject to allow for chaining of calls. 184 */ 185 public OverlayObject setInterpolator(Interpolator interpolator) { 186 mInterpolator = interpolator; 187 return this; 188 } 189 190 /** 191 * Binds an animation end listener to the animation. 192 * 193 * @param listener the animation end listener to be notified. 194 * @return This OverlayObject to allow for chaining of calls. 195 */ 196 public OverlayObject setAnimationEndListener(OnAnimationEndListener listener) { 197 mListener = listener; 198 return this; 199 } 200 201 /** 202 * Starts the animation and sets the start time. 203 * 204 * @param startTime Start time to be set in Millis 205 */ 206 public void startAnimation(long startTime) { 207 mStartTime = startTime; 208 mIsAnimationStarted = true; 209 } 210 211 /** 212 * Stops the animation. 213 */ 214 public void stopAnimation() { 215 mIsAnimationStarted = true; 216 mIsAnimationEnded = true; 217 if (mListener != null) { 218 mListener.onAnimationEnd(); 219 } 220 } 221 222 /** 223 * Calculates and updates current bounds and alpha value. 224 * 225 * @param currentTime Current time.in millis 226 */ 227 public boolean update(long currentTime) { 228 if (mIsAnimationEnded) { 229 return false; 230 } 231 float normalizedTime = (currentTime - mStartTime) / (float) mDuration; 232 normalizedTime = Math.max(0.0f, Math.min(1.0f, normalizedTime)); 233 if (!mIsAnimationStarted) { 234 normalizedTime = 0.0f; 235 } 236 float interpolatedTime = (mInterpolator == null) ? normalizedTime 237 : mInterpolator.getInterpolation(normalizedTime); 238 int deltaY = (int) (mDeltaY * interpolatedTime); 239 mCurrentBounds.top = mStartRect.top + deltaY; 240 mCurrentBounds.bottom = mStartRect.bottom + deltaY; 241 mCurrentAlpha = mStartAlpha + (mEndAlpha - mStartAlpha) * interpolatedTime; 242 if (mBitmap != null && mCurrentBounds != null) { 243 mBitmap.setAlpha((int) (mCurrentAlpha * 255)); 244 mBitmap.setBounds(mCurrentBounds); 245 } 246 if (mIsAnimationStarted && normalizedTime >= 1.0f) { 247 mIsAnimationEnded = true; 248 if (mListener != null) { 249 mListener.onAnimationEnd(); 250 } 251 } 252 return !mIsAnimationEnded; 253 } 254 255 /** 256 * An animation listener that receives notifications when the animation ends. 257 */ 258 public interface OnAnimationEndListener { 259 /** 260 * Notifies the end of the animation. 261 */ 262 public void onAnimationEnd(); 263 } 264 } 265} 266