shaylh
shaylh

Reputation: 1859

Gradient Radius as percentage of screen size

I'm trying to create a shape drawable with radial gradient background, with radius that will adjust to the screen size (take a look at the relevant documentation).

This is my code:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <gradient
        android:endColor="#000"
        android:gradientRadius="50%p"
        android:startColor="#5d2456"
        android:type="radial" />
</shape>

But it doens't seem to work. if I remove the "%p", it works, but then the radius will be static, thus not adjusting to the screen size...Any idea what's wrong?

Upvotes: 35

Views: 29149

Answers (5)

Blazej SLEBODA
Blazej SLEBODA

Reputation: 9935

It's impossible to set a shape corner radius expressed in percentage in a drawable xml.

Upvotes: 0

miguel
miguel

Reputation: 16600

Note that gradientRadius percentages do work in Lollipop. But if you have to support pre-Lollipop I expanded upon @marnaish's answer adding XML attributes. My gradientRadius is defined as a percentage of the parent view's width:

public class RadialGradientView extends View {
    private final int endColor;
    private final int startColor;
    private final float gradientRadiusWidthPercent;
    private final float centerY;
    private final float centerX;
    private Paint paint;

    public RadialGradientView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RadialGradientView, 0, 0);

        startColor = a.getColor(R.styleable.RadialGradientView_startColor, Color.RED);
        endColor = a.getColor(R.styleable.RadialGradientView_endColor, Color.BLACK);
        gradientRadiusWidthPercent = a.getFloat(R.styleable.RadialGradientView_gradientRadiusWidthPercent, 1);
        centerX = a.getFloat(R.styleable.RadialGradientView_centerX, .5f);
        centerY = a.getFloat(R.styleable.RadialGradientView_centerY, .5f);

        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        RadialGradient gradient = new RadialGradient(
                parentWidth*centerX,
                parentHeight*centerY,
                parentWidth*gradientRadiusWidthPercent,
                new int[] {startColor, endColor},
                null,
                android.graphics.Shader.TileMode.CLAMP);

        paint.setDither(true);
        paint.setShader(gradient);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
    }

}

In attrs.xml:

<declare-styleable name="RadialGradientView">
    <attr name="startColor" format="color|reference"/>
    <attr name="endColor" format="color|reference"/>
    <attr name="gradientRadiusWidthPercent" format="float"/>
    <attr name="centerX" format="float"/>
    <attr name="centerY" format="float"/>
</declare-styleable>

Unfortunately you can't create an XML drawable from a custom class, so you can't set it as a View's android:background. The workaround is to use a FrameLayout to layer it as the background.

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="100dp">

    <com.RadialGradientView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:centerX=".3"
        app:centerY=".5"
        app:endColor="#0f0"
        app:startColor="#f00"
        app:gradientRadiusWidthPercent=".5"
        />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="What's up world?"/>
</FrameLayout>

Upvotes: 5

ilomambo
ilomambo

Reputation: 8350

From what I´ve tested, the % does work, but not as you expected.
First of all

android:gradientRadius="50"

seems to take the value as pixels 50px

android:gradientRadius="50%"

is converted as if 50% = 0.5 px, try

android:gradientRadius="5000%"

and you will see a 50px radius.
Using %p has a similar result. Obviously this is something I hope will be changed in the future, because it does not have much use as it is. Usually XML ShapeDrawable resources adapt their size to some external container, in this case gradientRadius is setting the size regardless of the container.

Upvotes: 12

marnaish
marnaish

Reputation: 2286

I ended up creating a custom View with following overriden onDraw method:

@Override
protected void onDraw(Canvas canvas) {
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

    int maxSize = Math.max(getHeight(), getWidth());
    RadialGradient gradient = new RadialGradient(
            getWidth()/2,
            getHeight()/2,
            maxSize, 
            new int[] {Color.RED, Color.BLACK},
            new float[] {0, 1}, 
            android.graphics.Shader.TileMode.CLAMP);

    paint.setDither(true);
    paint.setShader(gradient);

    canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
}

In your layout just add the View:

<yourpackage.GradientView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Of course it would be possible to create attributes for the View, eg. color, percentage to allow customization via XML.

Upvotes: 9

Josh
Josh

Reputation: 123

You could create a shape drawable at runtime similiar to this post https://stackoverflow.com/a/4943888/604504

This way you could calculate the percentage after retrieving the screen size.

Upvotes: 0

Related Questions