| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.systemui.statusbar.stack; |
| |
| import android.graphics.Rect; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewGroup; |
| |
| import com.android.systemui.statusbar.ExpandableView; |
| import com.android.systemui.statusbar.SpeedBumpView; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which |
| * can be applied to a viewGroup. |
| */ |
| public class StackScrollState { |
| |
| private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; |
| |
| private final ViewGroup mHostView; |
| private Map<ExpandableView, ViewState> mStateMap; |
| private final Rect mClipRect = new Rect(); |
| |
| public StackScrollState(ViewGroup hostView) { |
| mHostView = hostView; |
| mStateMap = new HashMap<ExpandableView, ViewState>(); |
| } |
| |
| public ViewGroup getHostView() { |
| return mHostView; |
| } |
| |
| public void resetViewStates() { |
| int numChildren = mHostView.getChildCount(); |
| for (int i = 0; i < numChildren; i++) { |
| ExpandableView child = (ExpandableView) mHostView.getChildAt(i); |
| ViewState viewState = mStateMap.get(child); |
| if (viewState == null) { |
| viewState = new ViewState(); |
| mStateMap.put(child, viewState); |
| } |
| // initialize with the default values of the view |
| viewState.height = child.getIntrinsicHeight(); |
| viewState.gone = child.getVisibility() == View.GONE; |
| viewState.alpha = 1; |
| viewState.notGoneIndex = -1; |
| } |
| } |
| |
| public ViewState getViewStateForView(View requestedView) { |
| return mStateMap.get(requestedView); |
| } |
| |
| public void removeViewStateForView(View child) { |
| mStateMap.remove(child); |
| } |
| |
| /** |
| * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}. |
| * The properties are only applied if they effectively changed. |
| */ |
| public void apply() { |
| int numChildren = mHostView.getChildCount(); |
| for (int i = 0; i < numChildren; i++) { |
| ExpandableView child = (ExpandableView) mHostView.getChildAt(i); |
| ViewState state = mStateMap.get(child); |
| if (state == null) { |
| Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + |
| "to the hostView"); |
| continue; |
| } |
| if (!state.gone) { |
| float alpha = child.getAlpha(); |
| float yTranslation = child.getTranslationY(); |
| float zTranslation = child.getTranslationZ(); |
| float scale = child.getScaleX(); |
| int height = child.getActualHeight(); |
| float newAlpha = state.alpha; |
| float newYTranslation = state.yTranslation; |
| float newZTranslation = state.zTranslation; |
| float newScale = state.scale; |
| int newHeight = state.height; |
| boolean becomesInvisible = newAlpha == 0.0f; |
| if (alpha != newAlpha) { |
| // apply layer type |
| boolean becomesFullyVisible = newAlpha == 1.0f; |
| boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible; |
| int layerType = child.getLayerType(); |
| int newLayerType = newLayerTypeIsHardware |
| ? View.LAYER_TYPE_HARDWARE |
| : View.LAYER_TYPE_NONE; |
| if (layerType != newLayerType) { |
| child.setLayerType(newLayerType, null); |
| } |
| |
| // apply alpha |
| if (!becomesInvisible) { |
| child.setAlpha(newAlpha); |
| } |
| } |
| |
| // apply visibility |
| int oldVisibility = child.getVisibility(); |
| int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; |
| if (newVisibility != oldVisibility) { |
| child.setVisibility(newVisibility); |
| } |
| |
| // apply yTranslation |
| if (yTranslation != newYTranslation) { |
| child.setTranslationY(newYTranslation); |
| } |
| |
| // apply zTranslation |
| if (zTranslation != newZTranslation) { |
| child.setTranslationZ(newZTranslation); |
| } |
| |
| // apply scale |
| if (scale != newScale) { |
| child.setScaleX(newScale); |
| child.setScaleY(newScale); |
| } |
| |
| // apply height |
| if (height != newHeight) { |
| child.setActualHeight(newHeight, false /* notifyListeners */); |
| } |
| |
| // apply dimming |
| child.setDimmed(state.dimmed, false /* animate */); |
| |
| // apply dark |
| child.setDark(state.dark, false /* animate */); |
| |
| // apply speed bump state |
| child.setBelowSpeedBump(state.belowSpeedBump); |
| |
| // apply scrimming |
| child.setScrimAmount(state.scrimAmount); |
| |
| // apply clipping |
| float oldClipTopAmount = child.getClipTopAmount(); |
| if (oldClipTopAmount != state.clipTopAmount) { |
| child.setClipTopAmount(state.clipTopAmount); |
| } |
| updateChildClip(child, newHeight, state.topOverLap); |
| |
| if(child instanceof SpeedBumpView) { |
| float lineEnd = newYTranslation + newHeight / 2; |
| performSpeedBumpAnimation(i, (SpeedBumpView) child, lineEnd); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Updates the clipping of a view |
| * |
| * @param child the view to update |
| * @param height the currently applied height of the view |
| * @param clipInset how much should this view be clipped from the top |
| */ |
| private void updateChildClip(View child, int height, int clipInset) { |
| mClipRect.set(0, |
| clipInset, |
| child.getWidth(), |
| height); |
| child.setClipBounds(mClipRect); |
| } |
| |
| private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd) { |
| View nextChild = getNextChildNotGone(i); |
| if (nextChild != null) { |
| ViewState nextState = getViewStateForView(nextChild); |
| boolean startIsAboveNext = nextState.yTranslation > speedBumpEnd; |
| speedBump.animateDivider(startIsAboveNext, null /* onFinishedRunnable */); |
| } |
| } |
| |
| private View getNextChildNotGone(int childIndex) { |
| int childCount = mHostView.getChildCount(); |
| for (int i = childIndex + 1; i < childCount; i++) { |
| View child = mHostView.getChildAt(i); |
| if (child.getVisibility() != View.GONE) { |
| return child; |
| } |
| } |
| return null; |
| } |
| |
| public static class ViewState { |
| |
| // These are flags such that we can create masks for filtering. |
| |
| public static final int LOCATION_UNKNOWN = 0x00; |
| public static final int LOCATION_FIRST_CARD = 0x01; |
| public static final int LOCATION_TOP_STACK_HIDDEN = 0x02; |
| public static final int LOCATION_TOP_STACK_PEEKING = 0x04; |
| public static final int LOCATION_MAIN_AREA = 0x08; |
| public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10; |
| public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20; |
| |
| float alpha; |
| float yTranslation; |
| float zTranslation; |
| int height; |
| boolean gone; |
| float scale; |
| boolean dimmed; |
| boolean dark; |
| boolean belowSpeedBump; |
| |
| /** |
| * A value between 0 and 1 indicating how much the view should be scrimmed. |
| * 1 means that the notifications will be darkened as much as possible. |
| */ |
| float scrimAmount; |
| |
| /** |
| * The amount which the view should be clipped from the top. This is calculated to |
| * perceive consistent shadows. |
| */ |
| int clipTopAmount; |
| |
| /** |
| * How much does the child overlap with the previous view on the top? Can be used for |
| * a clipping optimization |
| */ |
| int topOverLap; |
| |
| /** |
| * The index of the view, only accounting for views not equal to GONE |
| */ |
| int notGoneIndex; |
| |
| /** |
| * The location this view is currently rendered at. |
| * |
| * <p>See <code>LOCATION_</code> flags.</p> |
| */ |
| int location; |
| } |
| } |