постепенное заполнение круга снизу вверх android

Я создал круг с обводкой и белым фоном, используя xml. Как это можно заполнить постепенно снизу вверх по действиям пользователя (например, при последовательном нажатии кнопки)?введите здесь описание изображения

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


person Kaps    schedule 21.07.2014    source источник
comment
Проверьте, дает ли это какую-либо подсказку   -  person MysticMagicϡ    schedule 21.07.2014
comment
Пример скорее всего программный, а не из xml. Любые другие предложения?   -  person Kaps    schedule 21.07.2014


Ответы (1)


Я создал класс Custom View, который будет делать то, что вы хотите. Есть четыре настраиваемых атрибута, которые можно установить в макете xml:

  • fillColor, color — устанавливает цвет области заливки. По умолчанию Color.WHITE.
  • strokeColor, color — устанавливает цвет ограничивающего круга. По умолчанию Color.BLACK.
  • strokeWidth, float — задает толщину ограничивающего круга. По умолчанию 1.0.
  • value, целое число: 0–100 – устанавливает значение для области заливки. По умолчанию 0.

Обратите внимание, что эти атрибуты должны иметь префикс custom вместо префикса android в XML макета. Корень View также должен содержать пространство имен custom xml. (См. пример ниже.) Доступны другие стандартные атрибуты View, такие как layout_width, background и т. д.

Во-первых, класс CircleFillView:

public class CircleFillView extends View
{
    public static final int MIN_VALUE = 0;
    public static final int MAX_VALUE = 100;

    private PointF center = new PointF();
    private RectF circleRect = new RectF();
    private Path segment = new Path();  
    private Paint strokePaint = new Paint();
    private Paint fillPaint = new Paint();

    private int radius;

    private int fillColor;
    private int strokeColor;
    private float strokeWidth;
    private int value;

    public CircleFillView(Context context)
    {
        this(context, null);
    }

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

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

        try
        {
            fillColor = a.getColor(R.styleable.CircleFillView_fillColor, Color.WHITE);
            strokeColor = a.getColor(R.styleable.CircleFillView_strokeColor, Color.BLACK);
            strokeWidth = a.getFloat(R.styleable.CircleFillView_strokeWidth, 1f);
            value = a.getInteger(R.styleable.CircleFillView_value, 0);
            adjustValue(value);
        }
        finally
        {
            a.recycle();
        }   

        fillPaint.setColor(fillColor);
        strokePaint.setColor(strokeColor);
        strokePaint.setStrokeWidth(strokeWidth);
        strokePaint.setStyle(Paint.Style.STROKE);
    }

    public void setFillColor(int fillColor)
    {
        this.fillColor = fillColor;
        fillPaint.setColor(fillColor);
        invalidate();
    }

    public int getFillColor()
    {
        return fillColor;
    }

    public void setStrokeColor(int strokeColor)
    {
        this.strokeColor = strokeColor;
        strokePaint.setColor(strokeColor);
        invalidate();
    }

    public int getStrokeColor()
    {
        return strokeColor;
    }

    public void setStrokeWidth(float strokeWidth)
    {
        this.strokeWidth = strokeWidth;
        strokePaint.setStrokeWidth(strokeWidth);
        invalidate();
    }

    public float getStrokeWidth()
    {
        return strokeWidth;
    }

    public void setValue(int value)
    {
        adjustValue(value);
        setPaths();

        invalidate();
    }

    public int getValue()
    {
        return value;
    }

    private void adjustValue(int value)
    {
        this.value = Math.min(MAX_VALUE, Math.max(MIN_VALUE, value));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);

        center.x = getWidth() / 2;
        center.y = getHeight() / 2;
        radius = Math.min(getWidth(), getHeight()) / 2 - (int) strokeWidth;
        circleRect.set(center.x - radius, center.y - radius, center.x + radius, center.y + radius);

        setPaths();
    }

    private void setPaths()
    {
        float y = center.y + radius - (2 * radius * value / 100 - 1);
        float x = center.x - (float) Math.sqrt(Math.pow(radius, 2) - Math.pow(y - center.y, 2));

        float angle = (float) Math.toDegrees(Math.atan((center.y - y) / (x - center.x)));
        float startAngle = 180 - angle;
        float sweepAngle = 2 * angle - 180;

        segment.rewind();
        segment.addArc(circleRect, startAngle, sweepAngle);
        segment.close();
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        canvas.drawPath(segment, fillPaint);
        canvas.drawCircle(center.x, center.y, radius, strokePaint);
    }
}

Теперь, чтобы пользовательские атрибуты xml работали, вам нужно поместить следующий файл в папку /res/values вашего проекта.

attrs.xml:

<resources>
    <declare-styleable name="CircleFillView" >
        <attr name="fillColor" format="color" />
        <attr name="strokeColor" format="color" />
        <attr name="strokeWidth" format="float" />
        <attr name="value" format="integer" />
    </declare-styleable>
</resources>

Ниже приведены файлы для простого демонстрационного приложения, где значение CircleFillView управляется с помощью SeekBar.

Файл макета для наших Activity, main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/com.example.circlefill"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <com.example.circlefill.CircleFillView
        android:id="@+id/circleFillView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#ffffff"
        custom:fillColor="#6bcae2"
        custom:strokeColor="#75b0d0"
        custom:strokeWidth="20"
        custom:value="65" />

    <SeekBar android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

И класс MainActivity:

public class MainActivity extends Activity
{
    CircleFillView circleFill;
    SeekBar seekBar;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        circleFill = (CircleFillView) findViewById(R.id.circleFillView);

        seekBar = (SeekBar) findViewById(R.id.seekBar);
        seekBar.setProgress(circleFill.getValue());
        seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener()
            {
                @Override
                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
                {
                    if (fromUser)
                        circleFill.setValue(progress);
                }

                @Override
                public void onStartTrackingTouch(SeekBar seekBar) {}

                @Override
                public void onStopTrackingTouch(SeekBar seekBar) {}
            }
        );
    }   
}

И скриншот демонстрационного приложения:

скриншот

person Mike M.    schedule 21.07.2014
comment
Я отказался голосовать за ваш код. Однако мне нужна поддержка Jelly Bean и выше. - person Kaps; 22.07.2014
comment
Хорошее решение, Майк. Любая идея, как это было бы сделано, если бы это было изображение вместо круга? - person SoH; 16.01.2015
comment
Проверено и работает нормально с версиями: 2.2, 2.3.4, 4.2.2, 4.4.4. Большое спасибо @MikeM. за простое и понятное решение. - person Omar; 22.08.2015
comment
Как я могу перенести этот ответ, чтобы заполнить холст в форме сердца, я в основном использую путь к нему, поэтому нет круга или каких-либо заранее определенных форм? Любые идеи ? - person San; 21.09.2015
comment
Если кому-то нужно изменить цвет заливки фона (часть круга, которая не является синей), вам просто нужно поменять местами порядок пути рисования и drawCircle и передать какой-либо цвет для рисования drawCircle - person Vito Valov; 09.12.2015
comment
@2943 первые несколько версий моего ответа показывают метод, который можно использовать с произвольным Path. Однако, как уже отмечалось, это хорошо только для API ›= 19. До этого вы, возможно, могли использовать Canvas#clipPath(), но есть некоторые проблемы с этим методом и аппаратным ускорением в более старых версиях, которые вам необходимо учитывать. - person Mike M.; 18.11.2016
comment
Отличное решение! Я люблю это! Можно ли заполнить круг эффектом градиента? - person user2529173; 02.05.2017
comment
@user2529173 user2529173 Конечно, я думаю, это не должно быть слишком сложно. Например, вы можете установить шейдер LinearGradient для объекта fillPaint. Однако вы хотели бы сделать это в методе onSizeChanged(), а не в конструкторе, чтобы у вас были правильные значения x, y. Немедленно попробуйте что-нибудь вроде fillPaint.setShader(new LinearGradient(0, center.y + radius, 0, center.y - radius, Color.BLACK, Color.WHITE, Shader.TileMode.CLAMP)) в конце этого метода. Должен дать вам представление о том, как действовать дальше. - person Mike M.; 02.05.2017