Created
January 16, 2018 19:33
-
-
Save mariusboepple/bf869e02541cd4750550e88fa07b5ddd to your computer and use it in GitHub Desktop.
ShadowView for Android based on Roman Nurik's Muzei App
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="ShadowView"> | |
<attr name="android:gravity" /> | |
</declare-styleable> | |
</resources> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2014 Google Inc. | |
* | |
* 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. | |
*/ | |
import android.util.LruCache; | |
/** | |
* Shadow view based on the {@code ScrimUtil.java} class from the Muzei App. Take a look at | |
* <a href="https://plus.google.com/+RomanNurik/posts/2QvHVFWrHZf">this post</a> from Roman | |
* Nurik for more details. Find the source code | |
* <a href="https://github.com/romannurik/muzei/blob/master/main/src/main/java/com/google/android/apps/muzei/util/ScrimUtil.java">here</a>. | |
*/ | |
public class ShadowView extends View { | |
private static final LruCache<Integer, Drawable> cubicGradientScrimCache = new LruCache<>(10); | |
public ShadowView(Context context) { | |
super(context); | |
initialize(context, null, 0, 0); | |
} | |
public ShadowView(Context context, @Nullable AttributeSet attrs) { | |
super(context, attrs); | |
initialize(context, attrs, 0, 0); | |
} | |
public ShadowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
initialize(context, attrs, defStyleAttr, 0); | |
} | |
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) | |
public ShadowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
super(context, attrs, defStyleAttr, defStyleRes); | |
initialize(context, attrs, defStyleAttr, defStyleRes); | |
} | |
/** | |
* Initializes the view. | |
* | |
* @param context The Context the view is running in, through which it can | |
* access the current theme, resources, etc. | |
* @param attrs The attributes of the XML tag that is inflating the view. | |
* @param defStyleAttr An attribute in the current theme that contains a | |
* reference to a style resource that supplies default values for | |
* the view. Can be 0 to not look for defaults. | |
* @param defStyleRes A resource identifier of a style resource that | |
* supplies default values for the view, used only if | |
* defStyleAttr is 0 or can not be found in the theme. Can be 0 | |
* to not look for defaults. | |
* @see View(Context, AttributeSet, int) | |
*/ | |
private void initialize(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
int gravity = Gravity.TOP; | |
// Get the attributes. | |
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShadowView, defStyleAttr, defStyleRes); | |
try { | |
if (a != null) { | |
gravity = a.getInt(R.styleable.ShadowView_android_gravity, gravity); | |
} | |
} finally { | |
if (a != null) a.recycle(); | |
} | |
// Set the gradient as background. | |
setBackground(makeCubicGradientScrimDrawable(0x44000000, 8, gravity)); | |
} | |
/** | |
* Creates an approximated cubic gradient using a multi-stop linear gradient. | |
*/ | |
private Drawable makeCubicGradientScrimDrawable(int baseColor, int numStops, int gravity) { | |
// Generate a cache key by hashing together the inputs, based on the method described in the Effective Java book | |
int cacheKeyHash = baseColor; | |
cacheKeyHash = 31 * cacheKeyHash + numStops; | |
cacheKeyHash = 31 * cacheKeyHash + gravity; | |
Drawable cachedGradient = cubicGradientScrimCache.get(cacheKeyHash); | |
if (cachedGradient != null) { | |
return cachedGradient; | |
} | |
numStops = Math.max(numStops, 2); | |
PaintDrawable paintDrawable = new PaintDrawable(); | |
paintDrawable.setShape(new RectShape()); | |
final int[] stopColors = new int[numStops]; | |
int red = Color.red(baseColor); | |
int green = Color.green(baseColor); | |
int blue = Color.blue(baseColor); | |
int alpha = Color.alpha(baseColor); | |
for (int i = 0; i < numStops; i++) { | |
float x = i * 1f / (numStops - 1); | |
float opacity = constrain(0, 1, (float) Math.pow(x, 3)); | |
stopColors[i] = Color.argb((int) (alpha * opacity), red, green, blue); | |
} | |
final float x0, x1, y0, y1; | |
switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { | |
case Gravity.LEFT: | |
x0 = 1; | |
x1 = 0; | |
break; | |
case Gravity.RIGHT: | |
x0 = 0; | |
x1 = 1; | |
break; | |
default: | |
x0 = 0; | |
x1 = 0; | |
break; | |
} | |
switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { | |
case Gravity.TOP: | |
y0 = 1; | |
y1 = 0; | |
break; | |
case Gravity.BOTTOM: | |
y0 = 0; | |
y1 = 1; | |
break; | |
default: | |
y0 = 0; | |
y1 = 0; | |
break; | |
} | |
paintDrawable.setShaderFactory(new ShapeDrawable.ShaderFactory() { | |
@Override | |
public Shader resize(int width, int height) { | |
return new LinearGradient( | |
width * x0, | |
height * y0, | |
width * x1, | |
height * y1, | |
stopColors, null, | |
Shader.TileMode.CLAMP); | |
} | |
}); | |
cubicGradientScrimCache.put(cacheKeyHash, paintDrawable); | |
return paintDrawable; | |
} | |
private float constrain(float min, float max, float v) { | |
return Math.max(min, Math.min(max, v)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This code has a memory leak in it:
compiles down to an inner class that keeps a reference to the surrounding object. As a result this leaks the surrounding object, and with it the context, and with that the entire Activity.
The suggested fix is moving this code into a static inner class that takes x0, x1, y0, y1, stopColors directly as parameters. Example (Kotlin):