package com.afollestad.aesthetic;
import android.annotation.TargetApi;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.EdgeEffectCompat;
import android.support.v4.widget.NestedScrollView;
import android.support.v7.widget.RecyclerView;
import android.widget.AbsListView;
import android.widget.EdgeEffect;
import android.widget.ScrollView;
import java.lang.reflect.Field;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
/** @author Aidan Follestad (afollestad) */
@RestrictTo(LIBRARY_GROUP)
final class EdgeGlowUtil {
private static Field EDGE_GLOW_FIELD_EDGE;
private static Field EDGE_GLOW_FIELD_GLOW;
private static Field EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT;
private static Field SCROLL_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static Field NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static Field LIST_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static Field RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT;
private static Field RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT;
private static Field RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static Field VIEW_PAGER_FIELD_EDGE_GLOW_LEFT;
private static Field VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT;
private static void invalidateEdgeEffectFields() {
if (EDGE_GLOW_FIELD_EDGE != null
&& EDGE_GLOW_FIELD_GLOW != null
&& EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT != null) {
EDGE_GLOW_FIELD_EDGE.setAccessible(true);
EDGE_GLOW_FIELD_GLOW.setAccessible(true);
EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT.setAccessible(true);
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Field edge = null;
Field glow = null;
for (Field f : EdgeEffect.class.getDeclaredFields()) {
switch (f.getName()) {
case "mEdge":
f.setAccessible(true);
edge = f;
break;
case "mGlow":
f.setAccessible(true);
glow = f;
break;
}
}
EDGE_GLOW_FIELD_EDGE = edge;
EDGE_GLOW_FIELD_GLOW = glow;
} else {
EDGE_GLOW_FIELD_EDGE = null;
EDGE_GLOW_FIELD_GLOW = null;
}
Field efc = null;
try {
efc = EdgeEffectCompat.class.getDeclaredField("mEdgeEffect");
} catch (NoSuchFieldException e) {
if (BuildConfig.DEBUG) e.printStackTrace();
}
EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT = efc;
}
private static void invalidateScrollViewFields() {
if (SCROLL_VIEW_FIELD_EDGE_GLOW_TOP != null && SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) {
SCROLL_VIEW_FIELD_EDGE_GLOW_TOP.setAccessible(true);
SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM.setAccessible(true);
return;
}
final Class<?> cls = ScrollView.class;
for (Field f : cls.getDeclaredFields()) {
switch (f.getName()) {
case "mEdgeGlowTop":
f.setAccessible(true);
SCROLL_VIEW_FIELD_EDGE_GLOW_TOP = f;
break;
case "mEdgeGlowBottom":
f.setAccessible(true);
SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM = f;
break;
}
}
}
private static void invalidateNestedScrollViewFields() {
if (NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP != null
&& NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) {
NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP.setAccessible(true);
NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM.setAccessible(true);
return;
}
Class cls = NestedScrollView.class;
for (Field f : cls.getDeclaredFields()) {
switch (f.getName()) {
case "mEdgeGlowTop":
f.setAccessible(true);
NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP = f;
break;
case "mEdgeGlowBottom":
f.setAccessible(true);
NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM = f;
break;
}
}
}
private static void invalidateListViewFields() {
if (LIST_VIEW_FIELD_EDGE_GLOW_TOP != null && LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) {
LIST_VIEW_FIELD_EDGE_GLOW_TOP.setAccessible(true);
LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM.setAccessible(true);
return;
}
final Class<?> cls = AbsListView.class;
for (Field f : cls.getDeclaredFields()) {
switch (f.getName()) {
case "mEdgeGlowTop":
f.setAccessible(true);
LIST_VIEW_FIELD_EDGE_GLOW_TOP = f;
break;
case "mEdgeGlowBottom":
f.setAccessible(true);
LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM = f;
break;
}
}
}
private static void invalidateRecyclerViewFields() {
if (RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP != null
&& RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT != null
&& RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT != null
&& RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) {
RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP.setAccessible(true);
RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT.setAccessible(true);
RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT.setAccessible(true);
RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM.setAccessible(true);
return;
}
Class cls = RecyclerView.class;
for (Field f : cls.getDeclaredFields()) {
switch (f.getName()) {
case "mTopGlow":
f.setAccessible(true);
RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP = f;
break;
case "mBottomGlow":
f.setAccessible(true);
RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM = f;
break;
case "mLeftGlow":
f.setAccessible(true);
RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT = f;
break;
case "mRightGlow":
f.setAccessible(true);
RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT = f;
break;
}
}
}
private static void invalidateViewPagerFields() {
if (VIEW_PAGER_FIELD_EDGE_GLOW_LEFT != null && VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT != null) {
VIEW_PAGER_FIELD_EDGE_GLOW_LEFT.setAccessible(true);
VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT.setAccessible(true);
return;
}
Class cls = ViewPager.class;
for (Field f : cls.getDeclaredFields()) {
switch (f.getName()) {
case "mLeftEdge":
f.setAccessible(true);
VIEW_PAGER_FIELD_EDGE_GLOW_LEFT = f;
break;
case "mRightEdge":
f.setAccessible(true);
VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT = f;
break;
}
}
}
// Setter methods
static void setEdgeGlowColor(@NonNull ScrollView scrollView, @ColorInt int color) {
invalidateScrollViewFields();
try {
Object ee;
ee = SCROLL_VIEW_FIELD_EDGE_GLOW_TOP.get(scrollView);
setEffectColor(ee, color);
ee = SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(scrollView);
setEffectColor(ee, color);
} catch (Exception ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
}
}
static void setEdgeGlowColor(@NonNull NestedScrollView scrollView, @ColorInt int color) {
invalidateNestedScrollViewFields();
try {
Object ee = NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP.get(scrollView);
setEffectColor(ee, color);
ee = NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(scrollView);
setEffectColor(ee, color);
} catch (Exception ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
}
}
static void setEdgeGlowColor(@NonNull AbsListView listView, @ColorInt int color) {
invalidateListViewFields();
try {
Object ee = LIST_VIEW_FIELD_EDGE_GLOW_TOP.get(listView);
setEffectColor(ee, color);
ee = LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(listView);
setEffectColor(ee, color);
} catch (Exception ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
}
}
static void setEdgeGlowColor(
@NonNull RecyclerView scrollView,
final @ColorInt int color,
@Nullable RecyclerView.OnScrollListener scrollListener) {
invalidateRecyclerViewFields();
invalidateRecyclerViewFields();
if (scrollListener == null) {
scrollListener =
new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
EdgeGlowUtil.setEdgeGlowColor(recyclerView, color, this);
}
};
scrollView.addOnScrollListener(scrollListener);
}
try {
Object ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP.get(scrollView);
setEffectColor(ee, color);
ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(scrollView);
setEffectColor(ee, color);
ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT.get(scrollView);
setEffectColor(ee, color);
ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT.get(scrollView);
setEffectColor(ee, color);
} catch (Exception ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
}
}
static void setEdgeGlowColor(@NonNull ViewPager pager, @ColorInt int color) {
invalidateViewPagerFields();
try {
Object ee = VIEW_PAGER_FIELD_EDGE_GLOW_LEFT.get(pager);
setEffectColor(ee, color);
ee = VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT.get(pager);
setEffectColor(ee, color);
} catch (Exception ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
}
}
// Utilities
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static void setEffectColor(Object edgeEffect, @ColorInt int color) {
invalidateEdgeEffectFields();
if (edgeEffect instanceof EdgeEffectCompat) {
// EdgeEffectCompat
try {
EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT.setAccessible(true);
edgeEffect = EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT.get(edgeEffect);
} catch (IllegalAccessException e) {
e.printStackTrace();
return;
}
}
if (edgeEffect == null) {
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// EdgeGlow
try {
EDGE_GLOW_FIELD_EDGE.setAccessible(true);
final Drawable mEdge = (Drawable) EDGE_GLOW_FIELD_EDGE.get(edgeEffect);
EDGE_GLOW_FIELD_GLOW.setAccessible(true);
final Drawable mGlow = (Drawable) EDGE_GLOW_FIELD_GLOW.get(edgeEffect);
mEdge.setColorFilter(color, PorterDuff.Mode.SRC_IN);
mGlow.setColorFilter(color, PorterDuff.Mode.SRC_IN);
mEdge.setCallback(null); // free up any references
mGlow.setCallback(null); // free up any references
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
// EdgeEffect
((EdgeEffect) edgeEffect).setColor(color);
}
}
}