我正在寻找一种方法来在使用appcompat材质主题时在RecyclerView pre-lollip中设置过度滚动指示器的颜色。
在内部,它使用EdgeEffect设置为内部可设置属性,除非你已经在棒棒糖(具有讽刺意味),否则无法设置。
使用反射不起作用,只能在棒棒糖上设置EdgeEffect的颜色。
在我的API21应用程序上,它从主要材料颜色中提取,在Kitkat上它是白色的,之前它是全蓝色的,我想要统一我的设计。
关于它是如何完成的任何想法?
答案 0 :(得分:7)
使用以下设置边缘效果发光颜色。适用于支持EdgeEffect(API 14+)的所有平台版本,否则将无声地失败。
void themeRecyclerView(Context context, RecyclerView recyclerView) {
int yourColor = Color.parseColor("#your_color");
try {
final Class<?> clazz = RecyclerView.class;
for (final String name : new String[]{"ensureTopGlow", "ensureBottomGlow", "ensureLeftGlow", "ensureRightGlow"}) {
Method method = clazz.getDeclaredMethod(name);
method.setAccessible(true);
method.invoke(recyclerView);
}
for (final String name : new String[]{"mTopGlow", "mBottomGlow", "mRightGlow", "mLeftGlow"}) {
final Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
final Object edge = field.get(recyclerView);
final Field fEdgeEffect = edge.getClass().getDeclaredField("mEdgeEffect");
fEdgeEffect.setAccessible(true);
setEdgeEffectColor((EdgeEffect) fEdgeEffect.get(edge), yourColor);
}
} catch (final Exception | NoClassDefFoundError ignored) {
}
}
void setEdgeEffectColor(EdgeEffect edgeEffect, int color) {
try {
if (Build.VERSION.SDK_INT >= 21) {
edgeEffect.setColor(color);
return;
}
for(String name : new String[]{"mEdge", "mGlow"}){
final Field field = EdgeEffect.class.getDeclaredField(name);
field.setAccessible(true);
final Drawable drawable = (Drawable) field.get(edgeEffect);
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
drawable.setCallback(null);
}
} catch (final Exception | NoClassDefFoundError ignored) {
}
}
感谢@Lukas Novak提供了大部分代码..
正如Lukas所说,必须在onScrollListener
的{{1}}内调用这些方法:
RecyclerView
答案 1 :(得分:5)
感谢@TomášLinhart指出它。以下解决方案仅用于在API&gt; 21中改变边缘颜色。它可以与AppCompat一起使用,但只有在棒棒糖及以上才能看到改变颜色的效果。
我找到了一种使用反射来设置颜色的方法。例如,这里是更改顶部和底部边缘颜色的代码:
public static void setEdgeGlowColor(final RecyclerView recyclerView, final int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
final Class<?> clazz = RecyclerView.class;
for (final String name : new String[] {"ensureTopGlow", "ensureBottomGlow"}) {
Method method = clazz.getDeclaredMethod(name);
method.setAccessible(true);
method.invoke(recyclerView);
}
for (final String name : new String[] {"mTopGlow", "mBottomGlow"}) {
final Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
final Object edge = field.get(recyclerView); // android.support.v4.widget.EdgeEffectCompat
final Field fEdgeEffect = edge.getClass().getDeclaredField("mEdgeEffect");
fEdgeEffect.setAccessible(true);
((EdgeEffect) fEdgeEffect.get(edge)).setColor(color);
}
} catch (final Exception ignored) {}
}
}
与其他组件(如ListView或ScrollView)的解决方案不同,此处必须调用包私有方法ensureTopGlow
,ensureBottomGlow
等,并在setEdgeEffectColor(RecyclerView recycler, int color)
方法中调用上面的onScrollStateChanged
RecyclerView.OnScrollListener
。
例如:
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
EdgeChanger.setEdgeGlowColor(recycler, getResources().getColor(R.color.your_color));
}
});
默认情况下,Android会在开始滚动时调用ensure*Glow
方法。在这些方法中,初始化的新EdgeEffect
具有默认颜色,但仅在尚未初始化时。要防止出现这种情况,您必须调用ensure*Glow
方法,然后更改边缘的颜色,以便后续初始化EdgeEffect
将被忽略(如上面的setEdgeGlowColor
方法)
答案 2 :(得分:3)
EdgeEffect正在使用drawable,因此您可以更改this article中描述的drawable,但它会影响您上下文中的所有EdgeEffect
类。
基本上只是介绍和调用这种方法,但是文章中描述了一些陷阱,所以我建议你先阅读它。
static void brandGlowEffect(Context context, int brandColor) {
//glow
int glowDrawableId = context.getResources().getIdentifier("overscroll_glow", "drawable", "android");
Drawable androidGlow = context.getResources().getDrawable(glowDrawableId);
androidGlow.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN);
//edge
int edgeDrawableId = context.getResources().getIdentifier("overscroll_edge", "drawable", "android");
Drawable androidEdge = context.getResources().getDrawable(edgeDrawableId);
androidEdge.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN);
}
答案 3 :(得分:0)
我一直无法找到为RecyclerViews设置过度滚动颜色的方法。
因此,可能的解决方案是为v21之前和之后的版本设置不同的布局文件。
缺点是你的代码会很乱,你需要为recyclerview / listview提供两个不同的适配器。
答案 4 :(得分:0)
我写了实用程序类EdgeChanger,它是我之前发布的帖子@Jared Hummler code和@Eugen Pechanec code的混合。
此实用程序类使用反射来改变边缘发光的颜色
ScrollView, NestedScrollView, ListView, ViewPager and RecyclerView
在使用AppCompat时与Marshmallow,Lollipop和pre-Lollipop设备配合使用,因此您无需使用EdgeEffectOverride等第三方库或使用不同的布局。
仅在想要在onCreate()之后更改边缘发光颜色时使用此选项,否则应使用setTheme和不同主题使用不同颜色属性colorPrimary或colorEdgeEffect。
public class EdgeChanger {
private static final Class<?> CLASS_SCROLL_VIEW = ScrollView.class;
private static Field SCROLL_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static final Class<?> CLASS_LIST_VIEW = AbsListView.class;
private static Field LIST_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static final Class<?> CLASS_NESTED_SCROLL_VIEW = NestedScrollView.class;
private static Field NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static Method NESTED_SCROLL_VIEW_METHOD_ENSURE_GLOWS;
private static final Class<?> CLASS_RECYCLER_VIEW = RecyclerView.class;
private static Field RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP;
private static Field RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM;
private static Method RECYCLER_VIEW_METHOD_ENSURE_GLOW_TOP;
private static Method RECYCLER_VIEW_METHOD_ENSURE_GLOW_BOTTOM;
private static final Class<?> CLASS_VIEW_PAGER = ViewPager.class;
private static Field VIEW_PAGER_FIELD_EDGE_GLOW_LEFT;
private static Field VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT;
static {
Field edgeGlowTop = null, edgeGlowBottom = null;
Method ensureGlowTop = null, ensureGlowBottom = null;
for (Field f : CLASS_SCROLL_VIEW.getDeclaredFields()) {
switch (f.getName()) {
case "mEdgeGlowTop":
f.setAccessible(true);
edgeGlowTop = f;
break;
case "mEdgeGlowBottom":
f.setAccessible(true);
edgeGlowBottom = f;
break;
}
}
SCROLL_VIEW_FIELD_EDGE_GLOW_TOP = edgeGlowTop;
SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM = edgeGlowBottom;
for (Field f : CLASS_LIST_VIEW.getDeclaredFields()) {
switch (f.getName()) {
case "mEdgeGlowTop":
f.setAccessible(true);
edgeGlowTop = f;
break;
case "mEdgeGlowBottom":
f.setAccessible(true);
edgeGlowBottom = f;
break;
}
}
LIST_VIEW_FIELD_EDGE_GLOW_TOP = edgeGlowTop;
LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM = edgeGlowBottom;
for (Field f : CLASS_NESTED_SCROLL_VIEW.getDeclaredFields()) {
switch (f.getName()) {
case "mEdgeGlowTop":
f.setAccessible(true);
edgeGlowTop = f;
break;
case "mEdgeGlowBottom":
f.setAccessible(true);
edgeGlowBottom = f;
break;
}
}
for (Method m : CLASS_NESTED_SCROLL_VIEW.getDeclaredMethods()) {
switch (m.getName()) {
case "ensureGlows":
m.setAccessible(true);
ensureGlowTop = m;
break;
}
}
NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP = edgeGlowTop;
NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM = edgeGlowBottom;
NESTED_SCROLL_VIEW_METHOD_ENSURE_GLOWS = ensureGlowTop;
for (Field f : CLASS_RECYCLER_VIEW.getDeclaredFields()) {
switch (f.getName()) {
case "mTopGlow":
f.setAccessible(true);
edgeGlowTop = f;
break;
case "mBottomGlow":
f.setAccessible(true);
edgeGlowBottom = f;
break;
}
}
for (Method m : CLASS_RECYCLER_VIEW.getDeclaredMethods()) {
switch (m.getName()) {
case "ensureTopGlow":
m.setAccessible(true);
ensureGlowTop = m;
break;
case "ensureBottomGlow":
m.setAccessible(true);
ensureGlowBottom = m;
break;
}
}
RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP = edgeGlowTop;
RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM = edgeGlowBottom;
RECYCLER_VIEW_METHOD_ENSURE_GLOW_TOP = ensureGlowTop;
RECYCLER_VIEW_METHOD_ENSURE_GLOW_BOTTOM = ensureGlowBottom;
for (Field f : CLASS_VIEW_PAGER.getDeclaredFields()) {
switch (f.getName()) {
case "mLeftEdge":
f.setAccessible(true);
edgeGlowTop = f;
break;
case "mRightEdge":
f.setAccessible(true);
edgeGlowBottom = f;
break;
}
}
VIEW_PAGER_FIELD_EDGE_GLOW_LEFT = edgeGlowTop;
VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT = edgeGlowBottom;
}
public static void setEdgeGlowColor(AbsListView listView, int color) {
try {
setEdgeEffectColor(LIST_VIEW_FIELD_EDGE_GLOW_TOP.get(listView), color);
setEdgeEffectColor(LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(listView), color);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setEdgeGlowColor(ScrollView scrollView, int color) {
try {
setEdgeEffectColor(SCROLL_VIEW_FIELD_EDGE_GLOW_TOP.get(scrollView), color);
setEdgeEffectColor(SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(scrollView), color);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setEdgeGlowColor(final ViewPager viewPager, final int color) {
try {
setEdgeEffectColor(VIEW_PAGER_FIELD_EDGE_GLOW_LEFT.get(viewPager), color);
setEdgeEffectColor(VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT.get(viewPager), color);
} catch (final Exception e) {
e.printStackTrace();
}
}
public static void setEdgeGlowColor(NestedScrollView scrollView, int color) {
try {
NESTED_SCROLL_VIEW_METHOD_ENSURE_GLOWS.invoke(scrollView);
setEdgeEffectColor(NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP.get(scrollView), color);
setEdgeEffectColor(NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(scrollView), color);
} catch (final Exception | NoClassDefFoundError e) {
e.printStackTrace();
}
}
public static void setEdgeGlowColor(final RecyclerView recyclerView, final int color) {
try {
RECYCLER_VIEW_METHOD_ENSURE_GLOW_TOP.invoke(recyclerView);
RECYCLER_VIEW_METHOD_ENSURE_GLOW_BOTTOM.invoke(recyclerView);
setEdgeEffectColor(RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP.get(recyclerView), color);
setEdgeEffectColor(RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(recyclerView), color);
} catch (final Exception | NoClassDefFoundError e) {
e.printStackTrace();
}
}
private static void setEdgeEffectColor(Object object, int color) {
try {
EdgeEffect edgeEffect = null;
if (object instanceof EdgeEffectCompat) {
final Field fEdgeEffect = object.getClass().getDeclaredField("mEdgeEffect");
fEdgeEffect.setAccessible(true);
edgeEffect = (EdgeEffect) fEdgeEffect.get(object);
} else if (object instanceof EdgeEffect) {
edgeEffect = (EdgeEffect) object;
}
if (Build.VERSION.SDK_INT >= 21) {
edgeEffect.setColor(color);
} else {
for (String name : new String[] {"mEdge", "mGlow"}) {
final Field field = EdgeEffect.class.getDeclaredField(name);
field.setAccessible(true);
final Drawable drawable = (Drawable) field.get(edgeEffect);
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
drawable.setCallback(null);
}
}
} catch (final Exception | NoClassDefFoundError e) {
e.printStackTrace();
}
}
}
如果您正在使用ProGuard,请不要忘记添加这些规则(不要重命名要使用反射工作的字段):
-keepnames class android.widget.ScrollView { *; }
-keepnames class android.widget.AbsListView { *; }
-keepnames class android.support.v4.widget.NestedScrollView { *; }
-keepnames class android.support.v7.widget.RecyclerView { *; }
-keepnames class android.support.v4.view.ViewPager { *; }
-keepnames class android.widget.EdgeEffect { *; }
-keepnames class android.support.v4.widget.EdgeEffectCompat { *; }