1 /* 2 * Copyright (C) 2017 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 package com.android.wallpaper.picker.individual; 17 18 import android.animation.Animator; 19 import android.animation.Animator.AnimatorListener; 20 import android.animation.AnimatorSet; 21 import android.animation.ObjectAnimator; 22 import android.content.Context; 23 import android.os.Handler; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.View.OnHoverListener; 27 import android.widget.ImageView; 28 29 import com.android.wallpaper.R; 30 31 /** 32 * Implementation of {@code SelectionAnimator} which uses a checkmark and inset around the tile to 33 * indicate a selected state. 34 */ 35 public class CheckmarkSelectionAnimator implements SelectionAnimator { 36 private static final int HOVER_TIMEOUT_MS = 200; 37 private static final float HOVER_CHECK_CIRCLE_OPACITY = 0.67f; 38 39 private Context mAppContext; 40 41 private View mTile; 42 private ImageView mCheckCircle; 43 private View mLoadingIndicatorContainer; 44 private boolean mIsSelected; 45 private boolean mIsHovered; 46 private Handler mHoverHandler; 47 48 private Runnable mHoverEnterRunnable = new Runnable() { 49 @Override 50 public void run() { 51 mIsHovered = true; 52 53 mCheckCircle.setImageDrawable(mAppContext.getDrawable( 54 R.drawable.material_ic_check_circle_white_24)); 55 mCheckCircle.setVisibility(View.VISIBLE); 56 ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat( 57 mCheckCircle, "alpha", 0f, HOVER_CHECK_CIRCLE_OPACITY); 58 alphaAnimator.start(); 59 60 alphaAnimator.addListener(new AnimatorListener() { 61 @Override 62 public void onAnimationStart(Animator animation) { 63 } 64 65 @Override 66 public void onAnimationEnd(Animator animation) { 67 mIsHovered = true; 68 } 69 70 @Override 71 public void onAnimationCancel(Animator animation) { 72 } 73 74 @Override 75 public void onAnimationRepeat(Animator animation) { 76 } 77 }); 78 } 79 }; 80 81 private Runnable mHoverExitRunnable = new Runnable() { 82 @Override 83 public void run() { 84 mIsHovered = false; 85 86 ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat( 87 mCheckCircle, "alpha", HOVER_CHECK_CIRCLE_OPACITY, 0f); 88 alphaAnimator.addListener(new AnimatorListener() { 89 @Override 90 public void onAnimationStart(Animator animation) { 91 } 92 93 @Override 94 public void onAnimationEnd(Animator animation) { 95 mCheckCircle.setVisibility(View.GONE); 96 mIsHovered = false; 97 } 98 99 @Override 100 public void onAnimationCancel(Animator animation) { 101 } 102 103 @Override 104 public void onAnimationRepeat(Animator animation) { 105 } 106 }); 107 108 alphaAnimator.start(); 109 } 110 }; 111 CheckmarkSelectionAnimator(Context appContext, View itemView)112 public CheckmarkSelectionAnimator(Context appContext, View itemView) { 113 mAppContext = appContext; 114 115 mTile = itemView.findViewById(R.id.tile); 116 mCheckCircle = (ImageView) itemView.findViewById(R.id.check_circle); 117 mLoadingIndicatorContainer = itemView.findViewById(R.id.loading_indicator_container); 118 mHoverHandler = new Handler(); 119 120 mTile.setOnHoverListener(new OnHoverListener() { 121 @Override 122 public boolean onHover(View v, MotionEvent event) { 123 // If this ViewHolder is already selected, then don't change the state of the check circle. 124 if (mIsSelected) { 125 return false; 126 } 127 128 int actionMasked = event.getActionMasked(); 129 130 switch (actionMasked) { 131 case MotionEvent.ACTION_HOVER_ENTER: 132 animateHoverEnter(); 133 break; 134 case MotionEvent.ACTION_HOVER_EXIT: 135 animateHoverExit(); 136 break; 137 default: 138 // fall out 139 } 140 141 return false; 142 } 143 }); 144 } 145 146 @Override selectImmediately()147 public void selectImmediately() { 148 mIsSelected = true; 149 int insetPx = mAppContext.getResources().getDimensionPixelSize( 150 R.dimen.grid_item_individual_wallpaper_selected_inset); 151 mTile.setPadding(insetPx, insetPx, insetPx, insetPx); 152 mCheckCircle.setImageDrawable(mAppContext.getDrawable(R.drawable.check_circle_blue)); 153 mCheckCircle.setVisibility(View.VISIBLE); 154 mCheckCircle.setAlpha(1f); 155 mLoadingIndicatorContainer.setVisibility(View.GONE); 156 } 157 158 @Override deselectImmediately()159 public void deselectImmediately() { 160 mIsSelected = false; 161 mCheckCircle.setAlpha(0f); 162 mCheckCircle.setVisibility(View.GONE); 163 mTile.setPadding(0, 0, 0, 0); 164 mLoadingIndicatorContainer.setVisibility(View.GONE); 165 } 166 167 @Override animateSelected()168 public void animateSelected() { 169 // If already selected, do nothing. 170 if (mIsSelected) { 171 return; 172 } 173 174 mLoadingIndicatorContainer.setVisibility(View.GONE); 175 176 int[][] values = new int[2][4]; 177 values[0] = new int[]{0, 0, 0, 0}; 178 int insetPx = mAppContext.getResources().getDimensionPixelSize( 179 R.dimen.grid_item_individual_wallpaper_selected_inset); 180 values[1] = new int[]{insetPx, insetPx, insetPx, insetPx}; 181 182 ObjectAnimator paddingAnimator = ObjectAnimator.ofMultiInt(mTile, "padding", values); 183 ObjectAnimator checkCircleAlphaAnimator = ObjectAnimator.ofFloat(mCheckCircle, "alpha", 0f, 1f); 184 185 mCheckCircle.setImageDrawable(mAppContext.getDrawable(R.drawable.check_circle_blue)); 186 187 AnimatorSet animatorSet = new AnimatorSet(); 188 animatorSet.playTogether(paddingAnimator, checkCircleAlphaAnimator); 189 animatorSet.setDuration(200); 190 animatorSet.start(); 191 192 mCheckCircle.setVisibility(View.VISIBLE); 193 194 mIsSelected = true; 195 } 196 197 @Override animateDeselected()198 public void animateDeselected() { 199 mLoadingIndicatorContainer.setVisibility(View.GONE); 200 201 // If already deselected, do nothing. 202 if (!mIsSelected) { 203 return; 204 } 205 206 int[][] values = new int[2][4]; 207 int insetPx = mAppContext.getResources().getDimensionPixelSize( 208 R.dimen.grid_item_individual_wallpaper_selected_inset); 209 values[0] = new int[]{insetPx, insetPx, insetPx, insetPx}; 210 values[1] = new int[]{0, 0, 0, 0}; 211 212 ObjectAnimator paddingAnimator = ObjectAnimator.ofMultiInt(mTile, "padding", values); 213 ObjectAnimator checkCircleAlphaAnimator = ObjectAnimator.ofFloat(mCheckCircle, "alpha", 1f, 0f); 214 215 AnimatorSet animatorSet = new AnimatorSet(); 216 animatorSet.playTogether(paddingAnimator, checkCircleAlphaAnimator); 217 animatorSet.setDuration(200); 218 animatorSet.start(); 219 220 checkCircleAlphaAnimator.addListener(new AnimatorListener() { 221 @Override 222 public void onAnimationStart(Animator animation) { 223 } 224 225 @Override 226 public void onAnimationEnd(Animator animation) { 227 mCheckCircle.setVisibility(View.GONE); 228 } 229 230 @Override 231 public void onAnimationCancel(Animator animation) { 232 } 233 234 @Override 235 public void onAnimationRepeat(Animator animation) { 236 } 237 }); 238 239 mIsSelected = false; 240 } 241 242 @Override showLoading()243 public void showLoading() { 244 mLoadingIndicatorContainer.setVisibility(View.VISIBLE); 245 } 246 247 @Override showNotLoading()248 public void showNotLoading() { 249 mLoadingIndicatorContainer.setVisibility(View.GONE); 250 } 251 animateHoverEnter()252 private void animateHoverEnter() { 253 removeHoverHandlerCallbacks(); 254 255 if (mIsHovered) { 256 return; 257 } 258 259 mHoverHandler.postDelayed(mHoverEnterRunnable, HOVER_TIMEOUT_MS); 260 } 261 animateHoverExit()262 private void animateHoverExit() { 263 removeHoverHandlerCallbacks(); 264 265 if (!mIsHovered) { 266 return; 267 } 268 269 mHoverHandler.postDelayed(mHoverExitRunnable, HOVER_TIMEOUT_MS); 270 } 271 272 @Override isSelected()273 public boolean isSelected() { 274 return mIsSelected; 275 } 276 removeHoverHandlerCallbacks()277 private void removeHoverHandlerCallbacks() { 278 mHoverHandler.removeCallbacks(mHoverEnterRunnable); 279 mHoverHandler.removeCallbacks(mHoverExitRunnable); 280 } 281 } 282