Увеличить центр рисуемого градиента Android

Я создал отрисовываемый градиент Android, где верх и низ черный, а центр прозрачный:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <gradient
        android:startColor="@android:color/black"
        android:centerColor="@android:color/transparent"
        android:endColor="@android:color/black"
        android:angle="90"/>
</shape>

Визуализированный градиент выглядит так:

градиентное изображение

Как видите, черные части распространяются на большую часть экрана. Я хочу, чтобы черный отображался только на небольшой части верха и низа. Есть ли способ увеличить прозрачный центр или уменьшить верхнюю и нижнюю черные полосы?

Я попытался поиграть с некоторыми другими XML-атрибутами, упомянутыми в связанной документации GradientDrawable, но ни один из них, похоже, не имеет значения.


person Tot Zam    schedule 16.03.2017    source источник
comment
если нет возможности установить более одного цвета с атрибутами xml, вы можете попытаться исказить рисуемый объект, если это возможно каким-то образом. иначе иди программно   -  person lelloman    schedule 16.03.2017
comment
@lelloman Я также открыт для программных предложений, хотя придерживаться только xml, вероятно, было бы проще. У тебя есть идеи?   -  person Tot Zam    schedule 16.03.2017
comment
Я думаю, что вам нужен LinearGradient с черным-белым-черным, но только не на одном и том же расстоянии, я боюсь, что GradientDrawable не позволяет вам настраивать положение цветов, вы бы использовали пользовательский Drawable программно?   -  person lelloman    schedule 16.03.2017
comment
@lelloman Спасибо за ответ. Мне нужно будет опробовать ваше решение. Просто указываю, чтобы избежать путаницы, средний цвет прозрачный, а не белый.   -  person Tot Zam    schedule 16.03.2017
comment
это не должно быть проблемой, я думаю, вы можете просто заменить его на 0, также вы можете попробовать разное количество шагов и цветов   -  person lelloman    schedule 16.03.2017
comment
Вы можете сделать черные части в процентах от высоты представления, наложив два градиентных рисунка. Проверьте этот gist gist.github.com/luksprog/4ac6e7550564b3823840d4a0943c8dd6 для примера (в примере выше каждая черная часть будет занимать 10% высоты обзора).   -  person user    schedule 16.03.2017
comment
@Luksprog Увидев первое предложение thenaoh, я действительно думал попробовать что-то подобное. В этом случае, действительно ли centerColor что-то делает, или я могу его пропустить?   -  person Tot Zam    schedule 16.03.2017
comment
Идея заключалась в том, чтобы использовать centerColor и centerY в качестве псевдоначального цвета. В противном случае без centerColor я думаю, что градиент просто распространится по всей высоте (между прозрачным и черным).   -  person user    schedule 16.03.2017


Ответы (3)


Для решения только для XML вы можете создать layer-list с двумя отдельными объектами gradient.

Следующий код создает два перекрывающихся объекта gradient и использует centerY с centerColor для смещения черной секции. Здесь атрибуты centerY установлены на 0.9 и 0.1, поэтому черный цвет ограничен верхними и нижними 10% высоты просмотра.

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <gradient
                android:angle="90"
                android:centerColor="@android:color/transparent"
                android:centerY="0.9"
                android:endColor="@android:color/black"
                android:startColor="@android:color/transparent" />
        </shape>
    </item>

    <item>
        <shape>
            <gradient
                android:angle="90"
                android:centerColor="@android:color/transparent"
                android:centerY="0.1"
                android:endColor="@android:color/transparent"
                android:startColor="@android:color/black" />
        </shape>
    </item>
</layer-list>

Для уровня API 23 или выше также будет работать следующее решение с использованием android:height. Это решение может работать, даже если вы не знаете общую высоту вашего представления, если вы знаете, насколько большим должен быть градиент.

Этот код создает два отдельных градиента, каждый с высотой 60sp, а затем использует android:gravity для перемещения градиентов вверху и внизу представления.

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:height="60sp"
        android:gravity="top">
        <shape android:shape="rectangle">
            <gradient
                android:angle="90"
                android:endColor="@android:color/black"
                android:startColor="@android:color/transparent" />
        </shape>
    </item>
    <item
        android:height="65sp"
        android:gravity="bottom">
        <shape android:shape="rectangle">
            <gradient
                android:angle="90"
                android:endColor="@android:color/transparent"
                android:startColor="@android:color/black" />
        </shape>
    </item>
</layer-list>

Спасибо @Luksprog для справки по коду и @thenaoh для начала идеи.

Приведенные выше решения работают, и приятно, что они представляют собой чистый XML. Если ваш градиент отображается с полосами, вы можете попробовать программное решение, как показано в ответе @lelloman, чтобы создать более плавный градиент.

person Tot Zam    schedule 17.03.2017

Вот как это можно сделать с помощью пользовательского файла Drawable. Вы можете настроить LinearGradient по своему усмотрению, а затем установить его в качестве фона представления с помощью view.setBackground(new CustomDrawable());.

public class CustomDrawable extends Drawable {

    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int[] colors;
    private float[] positions;

    public CustomDrawable() {
        paint.setStyle(Paint.Style.FILL);
        this.colors = new int[]{0xff000000, 0xffaaaaaa, 0xffffffff, 0xffaaaaaa, 0xff000000};
        this.positions = new float[]{.0f, .2f, .5f, .8f, 1.f};
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);    

        LinearGradient linearGradient = new LinearGradient(left, top,left, bottom, colors, positions, Shader.TileMode.CLAMP);
        paint.setShader(linearGradient);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {    
        canvas.drawRect(getBounds(), paint);
    }

    @Override
    public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
        paint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        paint.setColorFilter(colorFilter);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSPARENT;
    }
}
person lelloman    schedule 16.03.2017

Есть решение, предполагающее, что вы заранее знаете высоту своего вида (скажем, здесь 60dp):

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
    android:bottom="40dp">
    <shape android:shape="rectangle">
        <gradient
            android:type="linear"
            android:angle="90"
            android:startColor="#FFFFFF"
            android:endColor="#000000"/>
    </shape>
</item>
<item
    android:top="20dp"
    android:bottom="20dp"
    android:gravity="center_vertical">
    <shape android:shape="rectangle">
        <solid android:color="#FFFFFF"/>
    </shape>
</item>
<item
    android:top="40dp"
    android:gravity="bottom">
    <shape android:shape="rectangle">
        <gradient
            android:type="linear"
            android:angle="90"
            android:startColor="#000000"
            android:endColor="#FFFFFF"/>
    </shape>
</item>
</layer-list>

Но если вы не знаете высоту заранее, другим решением будет создание собственного пользовательского вида, например:

public class MyView extends ImageView
{
    private Paint paint = null;

    public MyView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        paint.setShader(getLinearGradient(0, getHeight()));
        canvas.drawPaint(paint);
    }

    private LinearGradient getLinearGradient(float y0, float y1)
    {
        // colors :
        int[] listeColors = new int[3];
        listeColors[0] = 0xFF000000;
        listeColors[1] = 0xFFFFFFFF;
        listeColors[2] = 0xFFFFFFFF;

        // positions :
        float[] listPositions = new float[3];
        listPositions[0] = 0;
        listPositions[1] = 0.25F;
        listPositions[2] = 1;

        // gradient :
        return new LinearGradient(0, y0, 0, y0 + (y1 - y0) / 2, listeColors, listPositions, Shader.TileMode.MIRROR);
    }
}

Надеюсь, поможет.

person matteoh    schedule 16.03.2017
comment
Высота моего представления не является постоянным размером, поэтому первое решение не сработает. Есть ли способ использовать это решение с процентами? - person Tot Zam; 16.03.2017
comment
Я так не думаю, как вы можете видеть здесь: stackoverflow.com/a/6061494/3527024. Поэтому я предлагаю свой второй вариант. - person matteoh; 16.03.2017
comment
второй вариант выглядит хорошо, однако вместо создания подкласса ImageView вы можете сделать то же самое, создав подкласс Drawable и установив его в качестве фона для любого вида, который вы хотите - person lelloman; 16.03.2017
comment
Я только что попробовал ваше первое решение, и оно не отображает правильный градиент. Чернота распространяется по всей площади. - person Tot Zam; 17.03.2017