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