StackScrollState.java revision eceda3d83814e20cabddc4f0755d475fa2f3d8ff
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.R; 25import com.android.systemui.statusbar.DismissView; 26import com.android.systemui.statusbar.ExpandableView; 27import com.android.systemui.statusbar.SpeedBumpView; 28 29import java.util.HashMap; 30import java.util.Map; 31 32/** 33 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which 34 * can be applied to a viewGroup. 35 */ 36public class StackScrollState { 37 38 private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; 39 40 private final ViewGroup mHostView; 41 private Map<ExpandableView, ViewState> mStateMap; 42 private final Rect mClipRect = new Rect(); 43 private final int mClearAllTopPadding; 44 45 public StackScrollState(ViewGroup hostView) { 46 mHostView = hostView; 47 mStateMap = new HashMap<ExpandableView, ViewState>(); 48 mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize( 49 R.dimen.clear_all_padding_top); 50 } 51 52 public ViewGroup getHostView() { 53 return mHostView; 54 } 55 56 public void resetViewStates() { 57 int numChildren = mHostView.getChildCount(); 58 for (int i = 0; i < numChildren; i++) { 59 ExpandableView child = (ExpandableView) mHostView.getChildAt(i); 60 ViewState viewState = mStateMap.get(child); 61 if (viewState == null) { 62 viewState = new ViewState(); 63 mStateMap.put(child, viewState); 64 } 65 // initialize with the default values of the view 66 viewState.height = child.getIntrinsicHeight(); 67 viewState.gone = child.getVisibility() == View.GONE; 68 viewState.alpha = 1; 69 viewState.notGoneIndex = -1; 70 } 71 } 72 73 public ViewState getViewStateForView(View requestedView) { 74 return mStateMap.get(requestedView); 75 } 76 77 public void removeViewStateForView(View child) { 78 mStateMap.remove(child); 79 } 80 81 /** 82 * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}. 83 * The properties are only applied if they effectively changed. 84 */ 85 public void apply() { 86 int numChildren = mHostView.getChildCount(); 87 for (int i = 0; i < numChildren; i++) { 88 ExpandableView child = (ExpandableView) mHostView.getChildAt(i); 89 ViewState state = mStateMap.get(child); 90 if (state == null) { 91 Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + 92 "to the hostView"); 93 continue; 94 } 95 if (!state.gone) { 96 float alpha = child.getAlpha(); 97 float yTranslation = child.getTranslationY(); 98 float xTranslation = child.getTranslationX(); 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 && xTranslation == 0) { 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 dark 158 child.setDark(state.dark, false /* animate */); 159 160 // apply speed bump state 161 child.setBelowSpeedBump(state.belowSpeedBump); 162 163 // apply scrimming 164 child.setScrimAmount(state.scrimAmount); 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 float lineEnd = newYTranslation + newHeight / 2; 175 performSpeedBumpAnimation(i, (SpeedBumpView) child, lineEnd); 176 } else if (child instanceof DismissView) { 177 DismissView dismissView = (DismissView) child; 178 boolean visible = state.topOverLap < mClearAllTopPadding; 179 dismissView.performVisibilityAnimation(visible); 180 } 181 } 182 } 183 } 184 185 /** 186 * Updates the clipping of a view 187 * 188 * @param child the view to update 189 * @param height the currently applied height of the view 190 * @param clipInset how much should this view be clipped from the top 191 */ 192 private void updateChildClip(View child, int height, int clipInset) { 193 mClipRect.set(0, 194 clipInset, 195 child.getWidth(), 196 height); 197 child.setClipBounds(mClipRect); 198 } 199 200 private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd) { 201 View nextChild = getNextChildNotGone(i); 202 if (nextChild != null) { 203 ViewState nextState = getViewStateForView(nextChild); 204 boolean startIsAboveNext = nextState.yTranslation > speedBumpEnd; 205 speedBump.animateDivider(startIsAboveNext, null /* onFinishedRunnable */); 206 } 207 } 208 209 private View getNextChildNotGone(int childIndex) { 210 int childCount = mHostView.getChildCount(); 211 for (int i = childIndex + 1; i < childCount; i++) { 212 View child = mHostView.getChildAt(i); 213 if (child.getVisibility() != View.GONE) { 214 return child; 215 } 216 } 217 return null; 218 } 219 220 public static class ViewState { 221 222 // These are flags such that we can create masks for filtering. 223 224 public static final int LOCATION_UNKNOWN = 0x00; 225 public static final int LOCATION_FIRST_CARD = 0x01; 226 public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; 227 public static final int LOCATION_TOP_STACK_PEEKING = 0x04; 228 public static final int LOCATION_MAIN_AREA = 0x08; 229 public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; 230 public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; 231 232 float alpha; 233 float yTranslation; 234 float zTranslation; 235 int height; 236 boolean gone; 237 float scale; 238 boolean dimmed; 239 boolean dark; 240 boolean belowSpeedBump; 241 242 /** 243 * A value between 0 and 1 indicating how much the view should be scrimmed. 244 * 1 means that the notifications will be darkened as much as possible. 245 */ 246 float scrimAmount; 247 248 /** 249 * The amount which the view should be clipped from the top. This is calculated to 250 * perceive consistent shadows. 251 */ 252 int clipTopAmount; 253 254 /** 255 * How much does the child overlap with the previous view on the top? Can be used for 256 * a clipping optimization 257 */ 258 int topOverLap; 259 260 /** 261 * The index of the view, only accounting for views not equal to GONE 262 */ 263 int notGoneIndex; 264 265 /** 266 * The location this view is currently rendered at. 267 * 268 * <p>See <code>LOCATION_</code> flags.</p> 269 */ 270 int location; 271 } 272} 273