NotificationContentView.java revision b5605e58cb8080c8c887b1885336b707596c8094
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; 18 19import android.content.Context; 20import android.graphics.Paint; 21import android.graphics.PorterDuff; 22import android.graphics.PorterDuffXfermode; 23import android.graphics.Rect; 24import android.util.AttributeSet; 25import android.view.View; 26import android.view.ViewTreeObserver; 27import android.view.animation.Interpolator; 28import android.view.animation.LinearInterpolator; 29import android.widget.FrameLayout; 30import com.android.systemui.R; 31 32/** 33 * A frame layout containing the actual payload of the notification, including the contracted and 34 * expanded layout. This class is responsible for clipping the content and and switching between the 35 * expanded and contracted view depending on its clipped size. 36 */ 37public class NotificationContentView extends FrameLayout { 38 39 private static final long ANIMATION_DURATION_LENGTH = 170; 40 41 private final Rect mClipBounds = new Rect(); 42 43 private View mContractedChild; 44 private View mExpandedChild; 45 46 private NotificationViewWrapper mContractedWrapper; 47 48 private int mSmallHeight; 49 private int mClipTopAmount; 50 private int mContentHeight; 51 52 private final Interpolator mLinearInterpolator = new LinearInterpolator(); 53 54 private boolean mContractedVisible = true; 55 private boolean mDark; 56 57 private final Paint mFadePaint = new Paint(); 58 private boolean mAnimate; 59 private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener 60 = new ViewTreeObserver.OnPreDrawListener() { 61 @Override 62 public boolean onPreDraw() { 63 mAnimate = true; 64 getViewTreeObserver().removeOnPreDrawListener(this); 65 return true; 66 } 67 }; 68 69 public NotificationContentView(Context context, AttributeSet attrs) { 70 super(context, attrs); 71 mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 72 reset(true); 73 } 74 75 @Override 76 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 77 super.onLayout(changed, left, top, right, bottom); 78 updateClipping(); 79 } 80 81 @Override 82 protected void onAttachedToWindow() { 83 super.onAttachedToWindow(); 84 updateVisibility(); 85 } 86 87 public void reset(boolean resetActualHeight) { 88 if (mContractedChild != null) { 89 mContractedChild.animate().cancel(); 90 } 91 if (mExpandedChild != null) { 92 mExpandedChild.animate().cancel(); 93 } 94 removeAllViews(); 95 mContractedChild = null; 96 mExpandedChild = null; 97 mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); 98 mContractedVisible = true; 99 if (resetActualHeight) { 100 mContentHeight = mSmallHeight; 101 } 102 } 103 104 public View getContractedChild() { 105 return mContractedChild; 106 } 107 108 public View getExpandedChild() { 109 return mExpandedChild; 110 } 111 112 public void setContractedChild(View child) { 113 if (mContractedChild != null) { 114 mContractedChild.animate().cancel(); 115 removeView(mContractedChild); 116 } 117 sanitizeContractedLayoutParams(child); 118 addView(child); 119 mContractedChild = child; 120 mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child); 121 selectLayout(false /* animate */, true /* force */); 122 mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */); 123 } 124 125 public void setExpandedChild(View child) { 126 if (mExpandedChild != null) { 127 mExpandedChild.animate().cancel(); 128 removeView(mExpandedChild); 129 } 130 addView(child); 131 mExpandedChild = child; 132 selectLayout(false /* animate */, true /* force */); 133 } 134 135 @Override 136 protected void onVisibilityChanged(View changedView, int visibility) { 137 super.onVisibilityChanged(changedView, visibility); 138 updateVisibility(); 139 } 140 141 private void updateVisibility() { 142 setVisible(isShown()); 143 } 144 145 private void setVisible(final boolean isVisible) { 146 if (isVisible) { 147 148 // We only animate if we are drawn at least once, otherwise the view might animate when 149 // it's shown the first time 150 getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener); 151 } else { 152 getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener); 153 mAnimate = false; 154 } 155 } 156 157 public void setContentHeight(int contentHeight) { 158 contentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight()); 159 mContentHeight = contentHeight; 160 selectLayout(mAnimate /* animate */, false /* force */); 161 updateClipping(); 162 } 163 164 public int getContentHeight() { 165 return mContentHeight; 166 } 167 168 public int getMaxHeight() { 169 170 // The maximum height is just the laid out height. 171 return getHeight(); 172 } 173 174 public int getMinHeight() { 175 return mSmallHeight; 176 } 177 178 public void setClipTopAmount(int clipTopAmount) { 179 mClipTopAmount = clipTopAmount; 180 updateClipping(); 181 } 182 183 private void updateClipping() { 184 mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight); 185 setClipBounds(mClipBounds); 186 } 187 188 private void sanitizeContractedLayoutParams(View contractedChild) { 189 LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams(); 190 lp.height = mSmallHeight; 191 contractedChild.setLayoutParams(lp); 192 } 193 194 private void selectLayout(boolean animate, boolean force) { 195 if (mContractedChild == null) { 196 return; 197 } 198 boolean showContractedChild = showContractedChild(); 199 if (showContractedChild != mContractedVisible || force) { 200 if (animate && mExpandedChild != null) { 201 runSwitchAnimation(showContractedChild); 202 } else if (mExpandedChild != null) { 203 mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE); 204 mContractedChild.setAlpha(showContractedChild ? 1f : 0f); 205 mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE); 206 mExpandedChild.setAlpha(showContractedChild ? 0f : 1f); 207 } 208 } 209 mContractedVisible = showContractedChild; 210 } 211 212 private void runSwitchAnimation(final boolean showContractedChild) { 213 mContractedChild.setVisibility(View.VISIBLE); 214 mExpandedChild.setVisibility(View.VISIBLE); 215 mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); 216 mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); 217 setLayerType(LAYER_TYPE_HARDWARE, null); 218 mContractedChild.animate() 219 .alpha(showContractedChild ? 1f : 0f) 220 .setDuration(ANIMATION_DURATION_LENGTH) 221 .setInterpolator(mLinearInterpolator); 222 mExpandedChild.animate() 223 .alpha(showContractedChild ? 0f : 1f) 224 .setDuration(ANIMATION_DURATION_LENGTH) 225 .setInterpolator(mLinearInterpolator) 226 .withEndAction(new Runnable() { 227 @Override 228 public void run() { 229 mContractedChild.setLayerType(LAYER_TYPE_NONE, null); 230 mExpandedChild.setLayerType(LAYER_TYPE_NONE, null); 231 setLayerType(LAYER_TYPE_NONE, null); 232 mContractedChild.setVisibility(showContractedChild 233 ? View.VISIBLE 234 : View.INVISIBLE); 235 mExpandedChild.setVisibility(showContractedChild 236 ? View.INVISIBLE 237 : View.VISIBLE); 238 } 239 }); 240 } 241 242 private boolean showContractedChild() { 243 return mContentHeight <= mSmallHeight || mExpandedChild == null; 244 } 245 246 public void notifyContentUpdated() { 247 selectLayout(false /* animate */, true /* force */); 248 if (mContractedChild != null) { 249 mContractedWrapper.notifyContentUpdated(); 250 mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */); 251 } 252 } 253 254 public boolean isContentExpandable() { 255 return mExpandedChild != null; 256 } 257 258 public void setDark(boolean dark, boolean fade, long delay) { 259 if (mDark == dark || mContractedChild == null) return; 260 mDark = dark; 261 mContractedWrapper.setDark(dark, fade, delay); 262 } 263 264 @Override 265 public boolean hasOverlappingRendering() { 266 267 // This is not really true, but good enough when fading from the contracted to the expanded 268 // layout, and saves us some layers. 269 return false; 270 } 271} 272