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