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