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 17 package com.android.systemui.statusbar.stack; 18 19 import android.graphics.Rect; 20 import android.util.Log; 21 import android.view.View; 22 import android.view.ViewGroup; 23 24 import com.android.systemui.R; 25 import com.android.systemui.statusbar.DismissView; 26 import com.android.systemui.statusbar.EmptyShadeView; 27 import com.android.systemui.statusbar.ExpandableView; 28 import com.android.systemui.statusbar.SpeedBumpView; 29 30 import java.util.HashMap; 31 import java.util.Map; 32 33 /** 34 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which 35 * can be applied to a viewGroup. 36 */ 37 public class StackScrollState { 38 39 private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; 40 41 private final ViewGroup mHostView; 42 private Map<ExpandableView, ViewState> mStateMap; 43 private final Rect mClipRect = new Rect(); 44 private final int mClearAllTopPadding; 45 StackScrollState(ViewGroup hostView)46 public StackScrollState(ViewGroup hostView) { 47 mHostView = hostView; 48 mStateMap = new HashMap<ExpandableView, ViewState>(); 49 mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize( 50 R.dimen.clear_all_padding_top); 51 } 52 getHostView()53 public ViewGroup getHostView() { 54 return mHostView; 55 } 56 resetViewStates()57 public void resetViewStates() { 58 int numChildren = mHostView.getChildCount(); 59 for (int i = 0; i < numChildren; i++) { 60 ExpandableView child = (ExpandableView) mHostView.getChildAt(i); 61 ViewState viewState = mStateMap.get(child); 62 if (viewState == null) { 63 viewState = new ViewState(); 64 mStateMap.put(child, viewState); 65 } 66 // initialize with the default values of the view 67 viewState.height = child.getIntrinsicHeight(); 68 viewState.gone = child.getVisibility() == View.GONE; 69 viewState.alpha = 1; 70 viewState.notGoneIndex = -1; 71 } 72 } 73 getViewStateForView(View requestedView)74 public ViewState getViewStateForView(View requestedView) { 75 return mStateMap.get(requestedView); 76 } 77 removeViewStateForView(View child)78 public void removeViewStateForView(View child) { 79 mStateMap.remove(child); 80 } 81 82 /** 83 * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}. 84 * The properties are only applied if they effectively changed. 85 */ apply()86 public void apply() { 87 int numChildren = mHostView.getChildCount(); 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 xTranslation = child.getTranslationX(); 100 float zTranslation = child.getTranslationZ(); 101 float scale = child.getScaleX(); 102 int height = child.getActualHeight(); 103 float newAlpha = state.alpha; 104 float newYTranslation = state.yTranslation; 105 float newZTranslation = state.zTranslation; 106 float newScale = state.scale; 107 int newHeight = state.height; 108 boolean becomesInvisible = newAlpha == 0.0f; 109 if (alpha != newAlpha && xTranslation == 0) { 110 // apply layer type 111 boolean becomesFullyVisible = newAlpha == 1.0f; 112 boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible; 113 int layerType = child.getLayerType(); 114 int newLayerType = newLayerTypeIsHardware 115 ? View.LAYER_TYPE_HARDWARE 116 : View.LAYER_TYPE_NONE; 117 if (layerType != newLayerType) { 118 child.setLayerType(newLayerType, null); 119 } 120 121 // apply alpha 122 child.setAlpha(newAlpha); 123 } 124 125 // apply visibility 126 int oldVisibility = child.getVisibility(); 127 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; 128 if (newVisibility != oldVisibility) { 129 child.setVisibility(newVisibility); 130 } 131 132 // apply yTranslation 133 if (yTranslation != newYTranslation) { 134 child.setTranslationY(newYTranslation); 135 } 136 137 // apply zTranslation 138 if (zTranslation != newZTranslation) { 139 child.setTranslationZ(newZTranslation); 140 } 141 142 // apply scale 143 if (scale != newScale) { 144 child.setScaleX(newScale); 145 child.setScaleY(newScale); 146 } 147 148 // apply height 149 if (height != newHeight) { 150 child.setActualHeight(newHeight, false /* notifyListeners */); 151 } 152 153 // apply dimming 154 child.setDimmed(state.dimmed, false /* animate */); 155 156 // apply dark 157 child.setDark(state.dark, false /* animate */, 0 /* delay */); 158 159 // apply hiding sensitive 160 child.setHideSensitive( 161 state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); 162 163 // apply speed bump state 164 child.setBelowSpeedBump(state.belowSpeedBump); 165 166 // apply clipping 167 float oldClipTopAmount = child.getClipTopAmount(); 168 if (oldClipTopAmount != state.clipTopAmount) { 169 child.setClipTopAmount(state.clipTopAmount); 170 } 171 updateChildClip(child, newHeight, state.topOverLap); 172 173 if(child instanceof SpeedBumpView) { 174 performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0); 175 } else if (child instanceof DismissView) { 176 DismissView dismissView = (DismissView) child; 177 boolean visible = state.topOverLap < mClearAllTopPadding; 178 dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); 179 } else if (child instanceof EmptyShadeView) { 180 EmptyShadeView emptyShadeView = (EmptyShadeView) child; 181 boolean visible = state.topOverLap <= 0; 182 emptyShadeView.performVisibilityAnimation( 183 visible && !emptyShadeView.willBeGone()); 184 } 185 } 186 } 187 } 188 189 /** 190 * Updates the clipping of a view 191 * 192 * @param child the view to update 193 * @param height the currently applied height of the view 194 * @param clipInset how much should this view be clipped from the top 195 */ 196 private void updateChildClip(View child, int height, int clipInset) { 197 mClipRect.set(0, 198 clipInset, 199 child.getWidth(), 200 height); 201 child.setClipBounds(mClipRect); 202 } 203 204 public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, ViewState state, 205 long delay) { 206 View nextChild = getNextChildNotGone(i); 207 if (nextChild != null) { 208 float lineEnd = state.yTranslation + state.height / 2; 209 ViewState nextState = getViewStateForView(nextChild); 210 boolean startIsAboveNext = nextState.yTranslation > lineEnd; 211 speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */); 212 } 213 } 214 215 private View getNextChildNotGone(int childIndex) { 216 int childCount = mHostView.getChildCount(); 217 for (int i = childIndex + 1; i < childCount; i++) { 218 View child = mHostView.getChildAt(i); 219 if (child.getVisibility() != View.GONE) { 220 return child; 221 } 222 } 223 return null; 224 } 225 226 public static class ViewState { 227 228 // These are flags such that we can create masks for filtering. 229 230 public static final int LOCATION_UNKNOWN = 0x00; 231 public static final int LOCATION_FIRST_CARD = 0x01; 232 public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; 233 public static final int LOCATION_TOP_STACK_PEEKING = 0x04; 234 public static final int LOCATION_MAIN_AREA = 0x08; 235 public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; 236 public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; 237 /** The view isn't layouted at all. */ 238 public static final int LOCATION_GONE = 0x40; 239 240 float alpha; 241 float yTranslation; 242 float zTranslation; 243 int height; 244 boolean gone; 245 float scale; 246 boolean dimmed; 247 boolean dark; 248 boolean hideSensitive; 249 boolean belowSpeedBump; 250 251 /** 252 * The amount which the view should be clipped from the top. This is calculated to 253 * perceive consistent shadows. 254 */ 255 int clipTopAmount; 256 257 /** 258 * How much does the child overlap with the previous view on the top? Can be used for 259 * a clipping optimization 260 */ 261 int topOverLap; 262 263 /** 264 * The index of the view, only accounting for views not equal to GONE 265 */ 266 int notGoneIndex; 267 268 /** 269 * The location this view is currently rendered at. 270 * 271 * <p>See <code>LOCATION_</code> flags.</p> 272 */ 273 int location; 274 } 275 } 276