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