Для начала посмотрим, как работает на Lollipop бесконечный
ProgressBar
:В то время как стиль виджета выглядел довольно легко реализуем, то корень проблемы лежал в анимации с неопределенным временем. Короткая линия направляется с лева на право, но длина ее варьируется на протяжении путешествия.
Первым делом, я решил сделать бекпорт существующего решения. Но возникли кое-какие трудности с реализацией, так как там использовался
AnimatedVectorDrawable
, который не доступен до Lollipop. Решение, которое я нашел, немного коварно, но все же дало мне на удивление очень похожий результат.Решение включает в себя создание нашей собственной реализации
ProgressBar
(что является наследником стандартного ProgressBar
) который полностью обходит стандартную логику неопределенного времени и реализует свою собственную на основе стандартной первичного и вторичного поведения, что уже встроены в ProgressBar
. Трюк заключается в методе его обработки — сначала задний план, потом второй прогресс и затем первый. Если задний план и первичный прогресс одного цвета, а вторичный прогресс другого цвета, то создается эффект изменения длины.Посмотрим на примере для лучшего понимания. Если мы поставим цвет заднего плана светло-зеленым, вторичный прогресс средне-зеленым, а первичный прогресс — темно-зеленым, то получим такой результат:
Тем не менее, если мы установим первичный цвет такой же как и задний план, то получим нужный эффект:
Мы можем настраивать начальные и конечные точки просто задавая значение secondaryProgress и значение ProgressBar соответственно.
А теперь посмотрим, как же мы все-таки это реализовали в коде:
public class MaterialProgressBar extends ProgressBar {
private static final int INDETERMINATE_MAX = 1000;
private static final String SECONDARY_PROGRESS = "secondaryProgress";
private static final String PROGRESS = "progress";
private Animator animator = null;
private final int duration;
public MaterialProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MaterialProgressBar, defStyleAttr, 0);
int backgroundColour;
int progressColour;
try {
backgroundColour = ta.getColor(R.styleable.MaterialProgressBar_backgroundColour, 0);
progressColour = ta.getColor(R.styleable.MaterialProgressBar_progressColour, 0);
int defaultDuration = context.getResources().getInteger(android.R.integer.config_mediumAnimTime);
duration = ta.getInteger(R.styleable.MaterialProgressBar_duration, defaultDuration);
} finally {
ta.recycle();
}
Resources resources = context.getResources();
setProgressDrawable(resources.getDrawable(android.R.drawable.progress_horizontal));
createIndeterminateProgressDrawable(backgroundColour, progressColour);
setMax(INDETERMINATE_MAX);
super.setIndeterminate(false);
this.setIndeterminate(true);
}
private void createIndeterminateProgressDrawable(@ColorInt int backgroundColour, @ColorInt int progressColour) {
LayerDrawable layerDrawable = (LayerDrawable) getProgressDrawable();
if (layerDrawable != null) {
layerDrawable.mutate();
layerDrawable.setDrawableByLayerId(android.R.id.background, createShapeDrawable(backgroundColour));
layerDrawable.setDrawableByLayerId(android.R.id.progress, createClipDrawable(backgroundColour));
layerDrawable.setDrawableByLayerId(android.R.id.secondaryProgress, createClipDrawable(progressColour));
}
}
private Drawable createClipDrawable(@ColorInt int colour) {
ShapeDrawable shapeDrawable = createShapeDrawable(colour);
return new ClipDrawable(shapeDrawable, Gravity.START, ClipDrawable.HORIZONTAL);
}
private ShapeDrawable createShapeDrawable(@ColorInt int colour) {
ShapeDrawable shapeDrawable = new ShapeDrawable();
setColour(shapeDrawable, colour);
return shapeDrawable;
}
private void setColour(ShapeDrawable drawable, int colour) {
Paint paint = drawable.getPaint();
paint.setColor(colour);
}
.
.
.
}
Основной метод здесь —
createIndeterminateProgressDrawable()
который заменяет слой в LayerDrawable
(который будет обрабатываться как ProgressBar
) с подходящими цветами.Еще хотелось бы отметить то, что мы жестко прописываем это, как
ProgressBar
с неопределенным временем в конструкторе – для того чтобы сохранить простоту и легкость понимания примера. На самом деле существует дополнительный код для переключения стандартного ProgressBar
в режим с неопределенным временем.Теперь мы можем прорисовывать сегмент, но как его анимировать? Это на удивление просто — мы анимируем прогресс и вторичный прогресс значением ProgressBar, но используя разные интерполяторы:
public class MaterialProgressBar extends ProgressBar {
.
.
.
@Override
public synchronized void setIndeterminate(boolean indeterminate) {
if (isStarted()) {
return;
}
animator = createIndeterminateAnimator();
animator.setTarget(this);
animator.start();
}
private boolean isStarted() {
return animator != null && animator.isStarted();
}
private Animator createIndeterminateAnimator() {
AnimatorSet set = new AnimatorSet();
Animator progressAnimator = getAnimator(SECONDARY_PROGRESS, new DecelerateInterpolator());
Animator secondaryProgressAnimator = getAnimator(PROGRESS, new AccelerateInterpolator());
set.playTogether(progressAnimator, secondaryProgressAnimator);
set.setDuration(duration);
return set;
}
@NonNull
private ObjectAnimator getAnimator(String propertyName, Interpolator interpolator) {
ObjectAnimator progressAnimator = ObjectAnimator.ofInt(this, propertyName, 0, INDETERMINATE_MAX);
progressAnimator.setInterpolator(interpolator);
progressAnimator.setDuration(duration);
progressAnimator.setRepeatMode(ValueAnimator.RESTART);
progressAnimator.setRepeatCount(ValueAnimator.INFINITE);
return progressAnimator;
}
}
Сделав наш
ProgressBar
чуть больше обычного, и немного замедлив анимацию мы увидим следующее:Все же вернем его к нормальным размерам и скоростям и сравним со стандартным виджетом ProgressBar неопределенного времени, реализованным в Lollipop:
Конечно же, они не являются идентичными — реализация в Lollipop имеет анимацию на секунду короче. Не смотря на это, данная реализация достаточно близка к исходной чтобы быть использованной на устройствах до Lollipop.
Исходный код используемый в статье доступен по ссылке.
Комментарии (10)
withkittens
07.11.2015 16:12+1Ещё различие в том, что у нативного прогрессбара две полосы: одна медленная, вторая быстрая, которая как бы не успевает догнать первую:
withkittens
07.11.2015 17:01+1Я объясняю ещё хуже, чем рисую, но попробую донести свою мысль:
Заголовок спойлераAStefanovskiy
07.11.2015 17:56Да, похоже на правду.
withkittens
07.11.2015 18:01+2А ещё есть же замечательный Polymer Project, оттуда можно стянуть параметры анимации ;)
Antares19
08.11.2015 04:37+1Кстати где-нибудь говорилось — есть ли в этом какой-то скрытый смысл кроме интересного визуального решения? (ну может по исследования в таком виде ожидание проходит веселее:)
grishkaa
07.11.2015 20:29+1Есть ещё вот такая библиотека, которая реализует горизонтальные и круглые прогрессбары в стиле material. Как раз недавно использовал у себя.
cmepthuk
08.11.2015 21:23Было бы здорово, если бы простая анимация в теле статьи была всё таки гифками, а не ифреймами на youtube. Быстрее, легче
выше, сильнее. Понимаю, что это лишь перевод и этот комментарий скорее просто возглас в пустоту, но пользователей на мобильных гаджетах сограниченным запасом ресурсов и по сей день достаточно :)
FLashM
С лева на право, говорите?