StackScrollState.java revision d552d9d8e964c102e6832610be46cf2c041e8829
1/* 2 * Copyright (C) 2014 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 17package com.android.systemui.statusbar.stack; 18 19import android.graphics.Outline; 20import android.graphics.Rect; 21import android.util.Log; 22import android.view.View; 23import android.view.ViewGroup; 24 25import com.android.systemui.statusbar.ExpandableView; 26 27import java.util.HashMap; 28import java.util.Map; 29 30/** 31 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which 32 * can be applied to a viewGroup. 33 */ 34public class StackScrollState { 35 36 private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; 37 38 private final ViewGroup mHostView; 39 private Map<ExpandableView, ViewState> mStateMap; 40 private final Rect mClipRect = new Rect(); 41 private int mBackgroundRoundedRectCornerRadius; 42 private final Outline mChildOutline = new Outline(); 43 44 public StackScrollState(ViewGroup hostView) { 45 mHostView = hostView; 46 mStateMap = new HashMap<ExpandableView, ViewState>(); 47 mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize( 48 com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); 49 } 50 51 public ViewGroup getHostView() { 52 return mHostView; 53 } 54 55 public void resetViewStates() { 56 int numChildren = mHostView.getChildCount(); 57 for (int i = 0; i < numChildren; i++) { 58 ExpandableView child = (ExpandableView) mHostView.getChildAt(i); 59 ViewState viewState = mStateMap.get(child); 60 if (viewState == null) { 61 viewState = new ViewState(); 62 mStateMap.put(child, viewState); 63 } 64 // initialize with the default values of the view 65 viewState.height = child.getIntrinsicHeight(); 66 viewState.gone = child.getVisibility() == View.GONE; 67 viewState.alpha = 1; 68 } 69 } 70 71 public ViewState getViewStateForView(View requestedView) { 72 return mStateMap.get(requestedView); 73 } 74 75 public void removeViewStateForView(View child) { 76 mStateMap.remove(child); 77 } 78 79 /** 80 * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}. 81 * The properties are only applied if they effectively changed. 82 */ 83 public void apply() { 84 int numChildren = mHostView.getChildCount(); 85 float previousNotificationEnd = 0; 86 float previousNotificationStart = 0; 87 boolean previousNotificationIsSwiped = false; 88 for (int i = 0; i < numChildren; i++) { 89 ExpandableView child = (ExpandableView) mHostView.getChildAt(i); 90 ViewState state = mStateMap.get(child); 91 if (state == null) { 92 Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + 93 "to the hostView"); 94 continue; 95 } 96 if (!state.gone) { 97 float alpha = child.getAlpha(); 98 float yTranslation = child.getTranslationY(); 99 float zTranslation = child.getTranslationZ(); 100 float scale = child.getScaleX(); 101 int height = child.getActualHeight(); 102 float newAlpha = state.alpha; 103 float newYTranslation = state.yTranslation; 104 float newZTranslation = state.zTranslation; 105 float newScale = state.scale; 106 int newHeight = state.height; 107 boolean becomesInvisible = newAlpha == 0.0f; 108 if (alpha != newAlpha) { 109 // apply layer type 110 boolean becomesFullyVisible = newAlpha == 1.0f; 111 boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible; 112 int layerType = child.getLayerType(); 113 int newLayerType = newLayerTypeIsHardware 114 ? View.LAYER_TYPE_HARDWARE 115 : View.LAYER_TYPE_NONE; 116 if (layerType != newLayerType) { 117 child.setLayerType(newLayerType, null); 118 } 119 120 // apply alpha 121 if (!becomesInvisible) { 122 child.setAlpha(newAlpha); 123 } 124 } 125 126 // apply visibility 127 int oldVisibility = child.getVisibility(); 128 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; 129 if (newVisibility != oldVisibility) { 130 child.setVisibility(newVisibility); 131 } 132 133 // apply yTranslation 134 if (yTranslation != newYTranslation) { 135 child.setTranslationY(newYTranslation); 136 } 137 138 // apply zTranslation 139 if (zTranslation != newZTranslation) { 140 child.setTranslationZ(newZTranslation); 141 } 142 143 // apply scale 144 if (scale != newScale) { 145 child.setScaleX(newScale); 146 child.setScaleY(newScale); 147 } 148 149 // apply height 150 if (height != newHeight) { 151 child.setActualHeight(newHeight, false /* notifyListeners */); 152 } 153 154 // apply dimming 155 child.setDimmed(state.dimmed, false /* animate */); 156 157 // apply clipping and shadow 158 float newNotificationEnd = newYTranslation + newHeight; 159 160 // When the previous notification is swiped, we don't clip the content to the 161 // bottom of it. 162 float clipHeight = previousNotificationIsSwiped 163 ? newHeight 164 : newNotificationEnd - (previousNotificationEnd); 165 166 updateChildClippingAndBackground(child, newHeight, 167 clipHeight, 168 (int) (newHeight - (previousNotificationStart - newYTranslation))); 169 170 previousNotificationStart = newYTranslation + child.getClipTopAmount(); 171 previousNotificationEnd = newNotificationEnd; 172 previousNotificationIsSwiped = child.getTranslationX() != 0; 173 } 174 } 175 } 176 177 /** 178 * Updates the shadow outline and the clipping for a view. 179 * 180 * @param child the view to update 181 * @param realHeight the currently applied height of the view 182 * @param clipHeight the desired clip height, the rest of the view will be clipped from the top 183 * @param backgroundHeight the desired background height. The shadows of the view will be 184 * based on this height and the content will be clipped from the top 185 */ 186 private void updateChildClippingAndBackground(ExpandableView child, int realHeight, 187 float clipHeight, int backgroundHeight) { 188 if (realHeight > clipHeight) { 189 updateChildClip(child, realHeight, clipHeight); 190 } else { 191 child.setClipBounds(null); 192 } 193 if (realHeight > backgroundHeight) { 194 child.setClipTopAmount(realHeight - backgroundHeight); 195 } else { 196 child.setClipTopAmount(0); 197 } 198 } 199 200 /** 201 * Updates the clipping of a view 202 * 203 * @param child the view to update 204 * @param height the currently applied height of the view 205 * @param clipHeight the desired clip height, the rest of the view will be clipped from the top 206 */ 207 private void updateChildClip(View child, int height, float clipHeight) { 208 int clipInset = (int) (height - clipHeight); 209 mClipRect.set(0, 210 clipInset, 211 child.getWidth(), 212 height); 213 child.setClipBounds(mClipRect); 214 } 215 216 public static class ViewState { 217 218 // These are flags such that we can create masks for filtering. 219 220 public static final int LOCATION_UNKNOWN = 0x00; 221 public static final int LOCATION_FIRST_CARD = 0x01; 222 public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; 223 public static final int LOCATION_TOP_STACK_PEEKING = 0x04; 224 public static final int LOCATION_MAIN_AREA = 0x08; 225 public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; 226 public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; 227 228 float alpha; 229 float yTranslation; 230 float zTranslation; 231 int height; 232 boolean gone; 233 float scale; 234 boolean dimmed; 235 236 /** 237 * The location this view is currently rendered at. 238 * 239 * <p>See <code>LOCATION_</code> flags.</p> 240 */ 241 int location; 242 } 243} 244