1 /* 2 * Copyright 2019 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 17 package com.android.car.apps.common.util; 18 19 import static android.view.View.INVISIBLE; 20 import static android.view.View.VISIBLE; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.content.res.Resources; 25 import android.content.res.TypedArray; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.FrameLayout; 29 import android.widget.TextView; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.Nullable; 33 import androidx.annotation.StringRes; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** 39 * Utility methods to operate over views. 40 */ 41 public class ViewUtils { 42 43 /** Listener to take action when animations are done. */ 44 public interface ViewAnimEndListener { 45 /** 46 * Called when the animation created by {@link #hideViewAnimated} or 47 * {@link #showHideViewAnimated} has reached its end. 48 */ onAnimationEnd(View view)49 void onAnimationEnd(View view); 50 } 51 52 /** Shows the view if show is set to true otherwise hides it. */ showHideViewAnimated(boolean show, @NonNull View view, int duration, @Nullable ViewAnimEndListener listener)53 public static void showHideViewAnimated(boolean show, @NonNull View view, int duration, 54 @Nullable ViewAnimEndListener listener) { 55 if (show) { 56 showViewAnimated(view, duration, listener); 57 } else { 58 hideViewAnimated(view, duration, listener); 59 } 60 } 61 62 /** 63 * Hides a view using a fade-out animation 64 * 65 * @param view {@link View} to be hidden 66 * @param duration animation duration in milliseconds. 67 */ hideViewAnimated(@onNull View view, int duration, @Nullable ViewAnimEndListener listener)68 public static void hideViewAnimated(@NonNull View view, int duration, 69 @Nullable ViewAnimEndListener listener) { 70 // Cancel existing animation to avoid race condition 71 // if show and hide are called at the same time 72 view.animate().cancel(); 73 74 if (!view.isLaidOut()) { 75 // If the view hasn't been displayed yet, just adjust visibility without animation 76 view.setVisibility(View.GONE); 77 return; 78 } 79 80 Animator.AnimatorListener hider = hideViewAfterAnimation(view); 81 view.animate() 82 .setDuration(duration) 83 .setListener(new AnimatorListenerAdapter() { 84 @Override 85 public void onAnimationEnd(Animator animation) { 86 hider.onAnimationEnd(animation); 87 if (listener != null) { 88 listener.onAnimationEnd(view); 89 } 90 } 91 }) 92 .alpha(0f); 93 } 94 95 /** Hides a view using a fade-out animation. */ hideViewAnimated(@onNull View view, int duration)96 public static void hideViewAnimated(@NonNull View view, int duration) { 97 hideViewAnimated(view, duration, null); 98 } 99 100 /** Returns an AnimatorListener that hides the view at the end. */ hideViewAfterAnimation(View view)101 public static Animator.AnimatorListener hideViewAfterAnimation(View view) { 102 return new AnimatorListenerAdapter() { 103 @Override 104 public void onAnimationEnd(Animator animation) { 105 view.setVisibility(View.GONE); 106 } 107 }; 108 } 109 110 /** 111 * Hides views using a fade-out animation 112 * 113 * @param views {@link View}s to be hidden 114 * @param duration animation duration in milliseconds. 115 */ 116 public static void hideViewsAnimated(@Nullable List<View> views, int duration) { 117 if (views == null) { 118 return; 119 } 120 for (View view : views) { 121 if (view != null) { 122 hideViewAnimated(view, duration, null); 123 } 124 } 125 } 126 127 /** 128 * Shows a view using a fade-in animation. The view's alpha value isn't changed so that 129 * animating an already visible won't have a visible effect. Therefore, <b>views initialized as 130 * hidden must have their alpha set to 0 prior to calling this method</b>. 131 * 132 * @param view {@link View} to be shown 133 * @param duration animation duration in milliseconds. 134 */ 135 public static void showViewAnimated(@NonNull View view, int duration, 136 @Nullable ViewAnimEndListener listener) { 137 // Cancel existing animation to avoid race condition 138 // if show and hide are called at the same time 139 view.animate().cancel(); 140 141 // Do the animation even if the view isn't laid out which is often the case for a view 142 // that isn't shown (otherwise the view just pops onto the screen... 143 144 view.animate() 145 .setDuration(duration) 146 .setListener(new AnimatorListenerAdapter() { 147 @Override 148 public void onAnimationStart(Animator animation) { 149 view.setVisibility(VISIBLE); 150 } 151 @Override 152 public void onAnimationEnd(Animator animation) { 153 if (listener != null) { 154 listener.onAnimationEnd(view); 155 } 156 } 157 }) 158 .alpha(1f); 159 } 160 161 /** Shows a view using a fade-in animation. */ 162 public static void showViewAnimated(@NonNull View view, int duration) { 163 showViewAnimated(view, duration, null); 164 } 165 166 /** 167 * Shows views using a fade-out animation 168 * 169 * @param views {@link View}s to be shown. 170 * @param duration animation duration in milliseconds. 171 */ 172 public static void showViewsAnimated(@Nullable List<View> views, int duration) { 173 for (View view : views) { 174 if (view != null) { 175 showViewAnimated(view, duration, null); 176 } 177 } 178 } 179 180 /** Sets the visibility of the (optional) view to {@link View#VISIBLE} or {@link View#GONE}. */ 181 public static void setVisible(@Nullable View view, boolean visible) { 182 if (view != null) { 183 view.setVisibility(visible ? VISIBLE : View.GONE); 184 } 185 } 186 187 /** Sets the visibility of the views to {@link View#VISIBLE} or {@link View#GONE}. */ 188 public static void setVisible(@Nullable List<View> views, boolean visible) { 189 for (View view : views) { 190 setVisible(view, visible); 191 } 192 } 193 194 /** 195 * Sets the visibility of the (optional) view to {@link View#INVISIBLE} or {@link View#VISIBLE}. 196 */ 197 public static void setInvisible(@Nullable View view, boolean invisible) { 198 if (view != null) { 199 view.setVisibility(invisible ? INVISIBLE : VISIBLE); 200 } 201 } 202 203 /** Sets the text to the (optional) {@link TextView}. */ 204 public static void setText(@Nullable TextView view, @StringRes int resId) { 205 if (view != null) { 206 view.setText(resId); 207 } 208 } 209 210 /** Sets the text to the (optional) {@link TextView}. */ 211 public static void setText(@Nullable TextView view, CharSequence text) { 212 if (view != null) { 213 view.setText(text); 214 } 215 } 216 217 /** Sets the enabled state of the (optional) view. */ 218 public static void setEnabled(@Nullable View view, boolean enabled) { 219 if (view != null) { 220 view.setEnabled(enabled); 221 } 222 } 223 224 /** Sets the activated state of the (optional) view. */ 225 public static void setActivated(@Nullable View view, boolean activated) { 226 if (view != null) { 227 view.setActivated(activated); 228 } 229 } 230 231 /** Sets onClickListener for the (optional) view. */ 232 public static void setOnClickListener(@Nullable View view, @Nullable View.OnClickListener l) { 233 if (view != null) { 234 view.setOnClickListener(l); 235 } 236 } 237 238 /** Helper interface for {@link #getViewsById(View, Resources, int, Filter)} getViewsById}. */ 239 public interface Filter { 240 /** Returns whether a view should be added to the returned List. */ 241 boolean isValid(View view); 242 } 243 244 /** Get views from typed array. */ 245 public static List<View> getViewsById(@NonNull View root, @NonNull Resources res, int arrayId, 246 @Nullable Filter filter) { 247 TypedArray viewIds = res.obtainTypedArray(arrayId); 248 List<View> views = new ArrayList<>(viewIds.length()); 249 for (int i = 0; i < viewIds.length(); i++) { 250 int viewId = viewIds.getResourceId(i, 0); 251 if (viewId != 0) { 252 View view = root.findViewById(viewId); 253 if (view != null && (filter == null || filter.isValid(view))) { 254 views.add(view); 255 } 256 } 257 } 258 viewIds.recycle(); 259 return views; 260 } 261 262 /** Removes the view from its parent (if it has one). */ 263 public static void removeFromParent(@Nullable View view) { 264 if (view != null) { 265 ViewGroup parent = (ViewGroup) view.getParent(); 266 if (parent != null) { 267 parent.removeView(view); 268 } 269 } 270 } 271 272 /** Adds the {@code view} into the {@code container}. */ 273 public static void setView(@Nullable View view, FrameLayout container) { 274 if (view != null) { 275 // Don't set the view if it stays the same. 276 if (container.getChildCount() == 1 && container.getChildAt(0) == view) { 277 return; 278 } 279 280 // As we are removing views (on BT disconnect, for example), some items will be 281 // shifting from expanded to collapsed (like Queue item) - remove those from the 282 // group before adding to the new slot 283 removeFromParent(view); 284 285 container.removeAllViews(); 286 container.addView(view); 287 container.setVisibility(VISIBLE); 288 } else { 289 if (container.getChildCount() != 0) { 290 container.removeAllViews(); 291 } 292 container.setVisibility(INVISIBLE); 293 } 294 } 295 } 296