【问题标题】:Set RecyclerView edge glow pre-lollipop when using appcompat使用 appcompat 时设置 RecyclerView 边缘发光 pre-lollipop
【发布时间】:2019-11-21 04:15:36
【问题描述】:

当使用 appcompat 材质主题时,我正在寻找一种方法来设置 RecyclerView 预棒棒糖中过度滚动指示器的颜色。

在内部,它使用 EdgeEffect 设置为内部可样式属性,除非您已经使用棒棒糖(讽刺),否则无法设置该属性。

使用反射不起作用,设置 EdgeEffect 的颜色也只能在棒棒糖上进行。

在我的 API21 应用程序中,它从主要材料颜色中提取,在 Kitkat 上它是白色,在此之前它是全息蓝色,我希望统一我的设计。

关于它是如何完成的任何想法?

【问题讨论】:

标签: android android-recyclerview colors android-appcompat


【解决方案1】:

使用以下设置边缘效果发光颜色。适用于支持 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 所说,这些方法必须在您的 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));
      }
});

【讨论】:

    【解决方案2】:

    感谢@Tomáš Linhart 指出。以下解决方案仅用于在 API >21 中更改边缘颜色。可与AppCompat配合使用,但变色效果仅在Lollipop及以上版本可见。


    我找到了一种使用反射设置颜色的方法。例如这里是更改顶部和底部边缘颜色的代码:

    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 的解决方案不同,这里必须调用包私有方法 ensureTopGlowensureBottomGlow 等,并在 onScrollStateChangedRecyclerView.OnScrollListener 方法中调用上述 setEdgeEffectColor(RecyclerView recycler, int color)

    例如:

    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 方法)

    【讨论】:

    • 您的解决方案没有回答问题,因为它是关于在使用 appcompat 时如何在 pre-lollipop 上执行此操作,而您的解决方案仅适用于 Lollipop。
    【解决方案3】:

    EdgeEffect 正在使用可绘制对象,因此您可以更改可绘制对象,如 this article 中所述,但它会影响您上下文中的所有 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);
    }
    

    【讨论】:

    • 确保将其包装在 Resources.NotFoundException try/catch 中。此资源在 Lollipop 中不可用
    【解决方案4】:

    我编写了实用程序类 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 { *; }
    

    【讨论】:

    • 我已经尝试过您的实用程序类并且它有效!奇怪的是我只将它应用于一个回收器视图作为测试,但它适用于所有其他组件,如视图寻呼机、选项卡等。看起来所有这些组件都只使用一个可绘制的过度滚动,所以你可以改变它的颜色一个可绘制的。最重要的是不需要反思(有点)。看到这个答案stackoverflow.com/questions/14940882/…
    • 我在最近的支持库中为 NestedScrollView 编辑了 setEdgeEffectColor 的代码。 NestedScrollView 现在直接使用EdgeEffect 而不是EdgeEffectCompat,但上述解决方案同时使用这两种方法。
    【解决方案5】:

    我也找不到为 RecyclerViews 设置过度滚动颜色的方法。

    因此,一个可能的解决方案是为 pre-v21 和 post-v21 设置不同的布局文件。

    缺点是你的代码会比较乱,需要有两个不同的适配器用于recyclerview/listview。

    【讨论】:

    • 我想你可能会发现它使用colorPrimary作为边缘阴影
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 2015-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多