Search code examples
androidandroid-drawableandroid-shape

Android: drawable ring shape multi-gradient


I'm trying to achieve something like the following:

the image inside is not too much of a problem, but the ring appears to be one. I was able to do this:

using:

<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="ring"
    android:thickness="@dimen/all_time_best_frame_thickness"
    android:useLevel="false"
    >
    <gradient
        android:startColor="@color/gold_gradient_start"
        android:centerColor="@color/gold_gradient_end"
        android:endColor="@color/gold_gradient_start"
        android:angle="180" />
</shape>

as you can see, this only takes 3 colors. So I tried to do it programmatically:

val ring = GradientDrawable(
            GradientDrawable.Orientation.LEFT_RIGHT, intArrayOf(
                ContextCompat.getColor(requireContext(), R.color.gold_gradient_1),
                ContextCompat.getColor(requireContext(), R.color.gold_gradient_2),
                ContextCompat.getColor(requireContext(), R.color.gold_gradient_3),
                ContextCompat.getColor(requireContext(), R.color.gold_gradient_4),
                ContextCompat.getColor(requireContext(), R.color.gold_gradient_5)
            )
        )

            ring.shape = GradientDrawable.OVAL
            ring.thickness = resources.getDimension(R.dimen.all_time_best_frame_thickness).toInt()
            binding.goldFrame.background = ring

This doesn't draw anything, so I'm sure I'm missing something. Also ring.thickness is only available on API 29 and my min is 23. So I need to find some alternative.

Layout is:

<LinearLayout
                        android:id="@+id/gold_frame"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:background="@drawable/drawable_gold_frame">

                        <com.google.android.material.imageview.ShapeableImageView
                            android:id="@+id/gold_photo"
                            android:layout_width="@dimen/all_time_best_photo_size"
                            android:layout_height="@dimen/all_time_best_photo_size"
                            android:layout_margin="@dimen/all_time_best_photo_margin"
                            app:shapeAppearanceOverlay="@style/CircleImageView" />
                    </LinearLayout>

Any idea on how to approach this?


Solution

  • I couldn't achieve it with a ring shape, so what I ended up doing is:

    <FrameLayout
                        android:id="@+id/gold_frame"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content">
    
                        <com.google.android.material.imageview.ShapeableImageView
                            android:id="@+id/gold_photo"
                            android:layout_width="@dimen/all_time_best_photo_size"
                            android:layout_height="@dimen/all_time_best_photo_size"
                            android:layout_margin="@dimen/all_time_best_frame_thickness"
                            app:shapeAppearanceOverlay="@style/CircleImageView" />
                    </FrameLayout>
    

    and

    val medal = GradientDrawable(
                GradientDrawable.Orientation.LEFT_RIGHT, intArrayOf(
                    ContextCompat.getColor(requireContext(), R.color.gold_gradient_1),
                    ContextCompat.getColor(requireContext(), R.color.gold_gradient_2),
                    ContextCompat.getColor(requireContext(), R.color.gold_gradient_3),
                    ContextCompat.getColor(requireContext(), R.color.gold_gradient_4),
                    ContextCompat.getColor(requireContext(), R.color.gold_gradient_5)
                )
            )
    
                medal.shape = GradientDrawable.OVAL
                binding.goldFrame.background = medal
    

    This way I create a circle with gradient inside and then set it as background as the ImageView's container. Is not perfect as it doesn't work if you have transparency in the images you want, but works in my case.

    Also, it can be improved via removing the container and doing:

    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/gold_photo"
        android:layout_width="@dimen/all_time_best_photo_size"
        android:layout_height="@dimen/all_time_best_photo_size"
        android:padding="@dimen/all_time_best_frame_thickness"
        app:shapeAppearanceOverlay="@style/CircleImageView" />
    

    and setting the background to the ImageView itself, unfortunately ShapeableImageView scales the background according to padding (standard ImageView doesn't do it).

    A user created the issue on github and solved the problem via PR, which has been merged but not yet released, check out issue and pull request.

    I'll post an update if I ever manage to do it using Ring shape.