Пишем свой Drawable для ProgressBar

в 23:02, , рубрики: Песочница, метки: ,

Все началось с того, что необходимо было стилизовать ProgressBar в одном из приложений. Как известно в андройде для этого используются Drawable объекты, описанные либо в виде XML, либо унаследованные от абстрактного класса Drawable, который входит в состав AndroidSDK. Про первый способ информации довольно много, а вот о втором — пойдет речь в данном посте.

Для начала создадим два класса CustomProgressDrawable и CustomProgressIndeterminateDrawable, для determinate и indeterminate состояний нашего прогресс бара соотвественно.

Начнем с CustomProgressDrawable:
В конструкторе созданим объект класса Paint, для того, чтобы в дальнейшем мы могли с помощью него рисовать на Canvas.

public CustomProgressDrawable(int color) {
    super();
    mPaint = new Paint();
    mPaint.setColor(color);
}

Далее переопределяем метод onLevelChange, именно этот метод вызывется, когда мы вызываем метод setProgress у нашего объекта ProgressBar. В конце метода вызываем метод invalidateSelf, который заставит наш Drawable отрисоваться.

@Override
protected boolean onLevelChange(int level) {
    final  float drawTo = START_ANGLE + HALF_CIRCLE_DEGREES * level / (float)MainActivity.PROGRESS_MAX_VALUE;
    boolean update = drawTo != mDrawTo;
    mDrawTo = drawTo;
    invalidateSelf();

    return update;
}

Так как класс Drawable является абстрактным, нам также будет необходимо реализовать все абстрактные методы данного класса, но их реализация довольно простая и ее в статье я опустил. Ниже есть ссылка на полный код примера.

Логику отрисовки реализуем в методе draw:

@Override
public void draw(Canvas canvas) {
    canvas.rotate(CANVAS_1ST_ARC_ROTATE_DEGREES, getBounds().centerX(), getBounds().centerY());
    mPaint.setStyle(Paint.Style.STROKE);
    canvas.drawOval(mBoundsF, mPaint);
    mPaint.setStyle(Paint.Style.FILL);
    canvas.drawArc(mInnerBoundsF, START_ANGLE, mDrawTo, true, mPaint);
    canvas.rotate(CANVAS_2ND_ARC_ROTATE_DEGREES, getBounds().centerX(), getBounds().centerY());
    canvas.drawArc(mInnerBoundsF, START_ANGLE, mDrawTo, true, mPaint);
}

Переходим к CustomProgressIndeterminateDrawable:
В целом логика реализации аналогична предыдущему классу, за исключением того, что вместо переопределения метода onLevelChange, нам необходимо имплементировать два интерфейса Animatable и Runnable.

@Override
public void run() {
    invalidateSelf();
    scheduleSelf(this, AnimationUtils.currentAnimationTimeMillis());
}

@Override
public boolean isRunning() {
    return mIsRunning;
}

@Override
public void start() {
    if (!isRunning()) {
        mIsRunning = true;
        mStartTicks = AnimationUtils.currentAnimationTimeMillis();
        run();
    }
}

@Override
public void stop() {
    if (isRunning()) {
        unscheduleSelf(this);
        mIsRunning = false;
    }
}

Сама анимация происходит в методе run, сначала мы перерисовываем наш Drawable, вызывая метод invalidateSelf. Затем, используя метод scheduleSelf класса Drawable, вновь вызываем метод run.

Теперь, там где нам необходимо, инициализируем наши Drawable и устанавливаем их обычному элементу ProgressBar, и далее работаем с ним через методы setProgress и setIndeterminate.

final int color = getResources().getColor(R.color.green);

// initialize drawables
CustomProgressDrawable progressDrawable = new 
CustomProgressDrawable(color);
CustomProgressIndeterminateDrawable barIndeterminateDrawable = new 
CustomProgressIndeterminateDrawable(color);

// set drawables and max value
mCustomProgressBar = (ProgressBar) 
findViewById(R.id.custom_progress_bar);
mCustomProgressBar.setIndeterminateDrawable(barIndeterminateDrawable);
mCustomProgressBar.setProgressDrawable(progressDrawable);
mCustomProgressBar.setMax(PROGRESS_MAX_VALUE);

И вот, что в итоге у нас получилось:

image

Весь код на github тыц.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js