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