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 android.support.v4.widget; 18 19import android.content.Context; 20import android.graphics.Canvas; 21import android.graphics.Color; 22import android.graphics.Paint; 23import android.graphics.RadialGradient; 24import android.graphics.Shader; 25import android.graphics.drawable.ShapeDrawable; 26import android.graphics.drawable.shapes.OvalShape; 27import android.support.v4.content.ContextCompat; 28import android.support.v4.view.ViewCompat; 29import android.view.View; 30import android.view.animation.Animation; 31import android.widget.ImageView; 32 33/** 34 * Private class created to work around issues with AnimationListeners being 35 * called before the animation is actually complete and support shadows on older 36 * platforms. 37 */ 38class CircleImageView extends ImageView { 39 40 private static final int KEY_SHADOW_COLOR = 0x1E000000; 41 private static final int FILL_SHADOW_COLOR = 0x3D000000; 42 // PX 43 private static final float X_OFFSET = 0f; 44 private static final float Y_OFFSET = 1.75f; 45 private static final float SHADOW_RADIUS = 3.5f; 46 private static final int SHADOW_ELEVATION = 4; 47 48 private Animation.AnimationListener mListener; 49 int mShadowRadius; 50 51 CircleImageView(Context context, int color) { 52 super(context); 53 final float density = getContext().getResources().getDisplayMetrics().density; 54 final int shadowYOffset = (int) (density * Y_OFFSET); 55 final int shadowXOffset = (int) (density * X_OFFSET); 56 57 mShadowRadius = (int) (density * SHADOW_RADIUS); 58 59 ShapeDrawable circle; 60 if (elevationSupported()) { 61 circle = new ShapeDrawable(new OvalShape()); 62 ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 63 } else { 64 OvalShape oval = new OvalShadow(mShadowRadius); 65 circle = new ShapeDrawable(oval); 66 setLayerType(View.LAYER_TYPE_SOFTWARE, circle.getPaint()); 67 circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 68 KEY_SHADOW_COLOR); 69 final int padding = mShadowRadius; 70 // set padding so the inner image sits correctly within the shadow. 71 setPadding(padding, padding, padding, padding); 72 } 73 circle.getPaint().setColor(color); 74 ViewCompat.setBackground(this, circle); 75 } 76 77 private boolean elevationSupported() { 78 return android.os.Build.VERSION.SDK_INT >= 21; 79 } 80 81 @Override 82 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 83 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 84 if (!elevationSupported()) { 85 setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight() 86 + mShadowRadius * 2); 87 } 88 } 89 90 public void setAnimationListener(Animation.AnimationListener listener) { 91 mListener = listener; 92 } 93 94 @Override 95 public void onAnimationStart() { 96 super.onAnimationStart(); 97 if (mListener != null) { 98 mListener.onAnimationStart(getAnimation()); 99 } 100 } 101 102 @Override 103 public void onAnimationEnd() { 104 super.onAnimationEnd(); 105 if (mListener != null) { 106 mListener.onAnimationEnd(getAnimation()); 107 } 108 } 109 110 /** 111 * Update the background color of the circle image view. 112 * 113 * @param colorRes Id of a color resource. 114 */ 115 public void setBackgroundColorRes(int colorRes) { 116 setBackgroundColor(ContextCompat.getColor(getContext(), colorRes)); 117 } 118 119 @Override 120 public void setBackgroundColor(int color) { 121 if (getBackground() instanceof ShapeDrawable) { 122 ((ShapeDrawable) getBackground()).getPaint().setColor(color); 123 } 124 } 125 126 private class OvalShadow extends OvalShape { 127 private RadialGradient mRadialGradient; 128 private Paint mShadowPaint; 129 130 OvalShadow(int shadowRadius) { 131 super(); 132 mShadowPaint = new Paint(); 133 mShadowRadius = shadowRadius; 134 updateRadialGradient((int) rect().width()); 135 } 136 137 @Override 138 protected void onResize(float width, float height) { 139 super.onResize(width, height); 140 updateRadialGradient((int) width); 141 } 142 143 @Override 144 public void draw(Canvas canvas, Paint paint) { 145 final int viewWidth = CircleImageView.this.getWidth(); 146 final int viewHeight = CircleImageView.this.getHeight(); 147 canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint); 148 canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint); 149 } 150 151 private void updateRadialGradient(int diameter) { 152 mRadialGradient = new RadialGradient(diameter / 2, diameter / 2, 153 mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT }, 154 null, Shader.TileMode.CLAMP); 155 mShadowPaint.setShader(mRadialGradient); 156 } 157 } 158} 159