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