StackScrollState.java revision fe40f7d13bfc1faa35c9a131ce4be5104cb8f6b9
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.R; 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<View, ViewState> mStateMap; 40 private int mScrollY; 41 private final Rect mClipRect = new Rect(); 42 private int mBackgroundRoundedRectCornerRadius; 43 private final Outline mChildOutline = new Outline(); 44 private final int mChildDividerHeight; 45 46 public int getScrollY() { 47 return mScrollY; 48 } 49 50 public void setScrollY(int scrollY) { 51 this.mScrollY = scrollY; 52 } 53 54 public StackScrollState(ViewGroup hostView) { 55 mHostView = hostView; 56 mStateMap = new HashMap<View, ViewState>(); 57 mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize( 58 com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); 59 mChildDividerHeight = hostView.getResources().getDimensionPixelSize(R.dimen 60 .notification_divider_height); 61 } 62 63 public ViewGroup getHostView() { 64 return mHostView; 65 } 66 67 public void resetViewStates() { 68 int numChildren = mHostView.getChildCount(); 69 for (int i = 0; i < numChildren; i++) { 70 View child = mHostView.getChildAt(i); 71 ViewState viewState = mStateMap.get(child); 72 if (viewState == null) { 73 viewState = new ViewState(); 74 mStateMap.put(child, viewState); 75 } 76 // initialize with the default values of the view 77 viewState.height = child.getHeight(); 78 viewState.alpha = 1; 79 viewState.gone = child.getVisibility() == View.GONE; 80 } 81 } 82 83 84 public ViewState getViewStateForView(View requestedView) { 85 return mStateMap.get(requestedView); 86 } 87 88 public void removeViewStateForView(View child) { 89 mStateMap.remove(child); 90 } 91 92 /** 93 * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}. 94 * The properties are only applied if they effectively changed. 95 */ 96 public void apply() { 97 int numChildren = mHostView.getChildCount(); 98 float previousNotificationEnd = 0; 99 float previousNotificationStart = 0; 100 for (int i = 0; i < numChildren; i++) { 101 View child = mHostView.getChildAt(i); 102 ViewState state = mStateMap.get(child); 103 if (state == null) { 104 Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + 105 "to the hostView"); 106 continue; 107 } 108 if (!state.gone) { 109 float alpha = child.getAlpha(); 110 float yTranslation = child.getTranslationY(); 111 float zTranslation = child.getTranslationZ(); 112 int height = child.getHeight(); 113 float newAlpha = state.alpha; 114 float newYTranslation = state.yTranslation; 115 float newZTranslation = state.zTranslation; 116 int newHeight = state.height; 117 boolean becomesInvisible = newAlpha == 0.0f; 118 if (alpha != newAlpha) { 119 // apply layer type 120 boolean becomesFullyVisible = newAlpha == 1.0f; 121 boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible; 122 int layerType = child.getLayerType(); 123 int newLayerType = newLayerTypeIsHardware 124 ? View.LAYER_TYPE_HARDWARE 125 : View.LAYER_TYPE_NONE; 126 if (layerType != newLayerType) { 127 child.setLayerType(newLayerType, null); 128 } 129 130 // apply alpha 131 if (!becomesInvisible) { 132 child.setAlpha(newAlpha); 133 } 134 } 135 136 // apply visibility 137 int oldVisibility = child.getVisibility(); 138 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; 139 if (newVisibility != oldVisibility) { 140 child.setVisibility(newVisibility); 141 } 142 143 // apply yTranslation 144 if (yTranslation != newYTranslation) { 145 child.setTranslationY(newYTranslation); 146 } 147 148 // apply zTranslation 149 if (zTranslation != newZTranslation) { 150 child.setTranslationZ(newZTranslation); 151 } 152 153 // apply height 154 if (height != newHeight) { 155 applyNewHeight(child, newHeight); 156 } 157 158 // apply clipping and shadow 159 float newNotificationEnd = newYTranslation + newHeight; 160 updateChildClippingAndShadow(child, newHeight, 161 newNotificationEnd - (previousNotificationEnd - mChildDividerHeight), 162 newHeight - (previousNotificationStart - newYTranslation)); 163 164 previousNotificationStart = newYTranslation; 165 previousNotificationEnd = newNotificationEnd; 166 } 167 } 168 } 169 170 /** 171 * Updates the shadow outline and the clipping for a view. 172 * 173 * @param child the view to update 174 * @param realHeight the currently applied height of the view 175 * @param clipHeight the desired clip height, the rest of the view will be clipped from the top 176 * @param shadowHeight the desired height of the shadow, the shadow ends on the bottom 177 */ 178 private void updateChildClippingAndShadow(View child, int realHeight, float clipHeight, 179 float shadowHeight) { 180 if (realHeight > shadowHeight) { 181 updateChildOutline(child, realHeight, shadowHeight); 182 } else { 183 updateChildOutline(child, realHeight, realHeight); 184 } 185 if (realHeight > clipHeight) { 186 updateChildClip(child, realHeight, clipHeight); 187 } else { 188 child.setClipBounds(null); 189 } 190 } 191 192 /** 193 * Updates the clipping of a view 194 * 195 * @param child the view to update 196 * @param height the currently applied height of the view 197 * @param clipHeight the desired clip height, the rest of the view will be clipped from the top 198 */ 199 private void updateChildClip(View child, int height, float clipHeight) { 200 int clipInset = (int) (height - clipHeight); 201 mClipRect.set(0, 202 clipInset, 203 child.getWidth(), 204 height); 205 child.setClipBounds(mClipRect); 206 } 207 208 /** 209 * Updates the outline of a view 210 * 211 * @param child the view to update 212 * @param height the currently applied height of the view 213 * @param outlineHeight the desired height of the outline, the outline ends on the bottom 214 */ 215 private void updateChildOutline(View child, int height, 216 float outlineHeight) { 217 int shadowInset = (int) (height - outlineHeight); 218 getOutlineForSize(child.getLeft(), 219 child.getTop() + shadowInset, 220 child.getWidth(), 221 child.getHeight() - shadowInset, 222 mChildOutline); 223 child.setOutline(mChildOutline); 224 } 225 226 private void getOutlineForSize(int leftInset, int topInset, int width, int height, 227 Outline result) { 228 result.setRoundRect(leftInset, topInset, leftInset + width, topInset + height, 229 mBackgroundRoundedRectCornerRadius); 230 } 231 232 private void applyNewHeight(View child, int newHeight) { 233 ViewGroup.LayoutParams lp = child.getLayoutParams(); 234 lp.height = newHeight; 235 child.setLayoutParams(lp); 236 } 237 238 239 public static class ViewState { 240 241 // These are flags such that we can create masks for filtering. 242 243 public static final int LOCATION_UNKNOWN = 0x00; 244 public static final int LOCATION_FIRST_CARD = 0x01; 245 public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; 246 public static final int LOCATION_TOP_STACK_PEEKING = 0x04; 247 public static final int LOCATION_MAIN_AREA = 0x08; 248 public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; 249 public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; 250 251 float alpha; 252 float yTranslation; 253 float zTranslation; 254 int height; 255 boolean gone; 256 257 /** 258 * The location this view is currently rendered at. 259 * 260 * <p>See <code>LOCATION_</code> flags.</p> 261 */ 262 int location; 263 } 264} 265