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 
17 package com.android.internal.widget;
18 
19 import android.util.ArraySet;
20 import android.view.View;
21 import android.view.ViewGroup;
22 import android.view.ViewParent;
23 
24 import com.android.internal.R;
25 
26 /**
27  * A utility class that allows to clip views and their parents to allow for better transitions
28  */
29 public class ViewClippingUtil {
30     private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
31     private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
32     private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
33 
setClippingDeactivated(final View transformedView, boolean deactivated, ClippingParameters clippingParameters)34     public static void setClippingDeactivated(final View transformedView, boolean deactivated,
35         ClippingParameters clippingParameters) {
36         if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
37             return;
38         }
39         if (!(transformedView.getParent() instanceof ViewGroup)) {
40             return;
41         }
42         ViewGroup parent = (ViewGroup) transformedView.getParent();
43         while (true) {
44             if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
45                 return;
46             }
47             ArraySet<View> clipSet = (ArraySet<View>) parent.getTag(CLIP_CLIPPING_SET);
48             if (clipSet == null) {
49                 clipSet = new ArraySet<>();
50                 parent.setTagInternal(CLIP_CLIPPING_SET, clipSet);
51             }
52             Boolean clipChildren = (Boolean) parent.getTag(CLIP_CHILDREN_TAG);
53             if (clipChildren == null) {
54                 clipChildren = parent.getClipChildren();
55                 parent.setTagInternal(CLIP_CHILDREN_TAG, clipChildren);
56             }
57             Boolean clipToPadding = (Boolean) parent.getTag(CLIP_TO_PADDING);
58             if (clipToPadding == null) {
59                 clipToPadding = parent.getClipToPadding();
60                 parent.setTagInternal(CLIP_TO_PADDING, clipToPadding);
61             }
62             if (!deactivated) {
63                 clipSet.remove(transformedView);
64                 if (clipSet.isEmpty()) {
65                     parent.setClipChildren(clipChildren);
66                     parent.setClipToPadding(clipToPadding);
67                     parent.setTagInternal(CLIP_CLIPPING_SET, null);
68                     clippingParameters.onClippingStateChanged(parent, true);
69                 }
70             } else {
71                 clipSet.add(transformedView);
72                 parent.setClipChildren(false);
73                 parent.setClipToPadding(false);
74                 clippingParameters.onClippingStateChanged(parent, false);
75             }
76             if (clippingParameters.shouldFinish(parent)) {
77                 return;
78             }
79             final ViewParent viewParent = parent.getParent();
80             if (viewParent instanceof ViewGroup) {
81                 parent = (ViewGroup) viewParent;
82             } else {
83                 return;
84             }
85         }
86     }
87 
88     public interface ClippingParameters {
89         /**
90          * Should we stop clipping at this view? If true is returned, {@param view} is the last view
91          * where clipping is activated / deactivated.
92          */
shouldFinish(View view)93         boolean shouldFinish(View view);
94 
95         /**
96          * Is it allowed to enable clipping on this view.
97          */
isClippingEnablingAllowed(View view)98         default boolean isClippingEnablingAllowed(View view) {
99             return !MessagingPropertyAnimator.isAnimatingTranslation(view);
100         }
101 
102         /**
103          * A method that is called whenever the view starts clipping again / stops clipping to the
104          * children and padding.
105          */
onClippingStateChanged(View view, boolean isClipping)106         default void onClippingStateChanged(View view, boolean isClipping) {};
107     }
108 }
109