На момент написания статьи, я работаю с отличными ребятами в Novoda над приложением для трансляции видео для телевидения в Великобритании Channel 4. Один из элементов дизайна, которые мне приходилось реализовывать был бесконечный ProgressBar в стиле Material Design. Для Android Lollipop и выше создание подобного дизайна не составляет труда, но вот поддержка устройств более ранних версий ОС стала для нас испытанием. В этой статье мы рассмотрим решение данной проблемы.
Для начала посмотрим как работает на 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.
Исходный код используемый в статье доступен по ссылке.
ссылка на оригинал статьи http://habrahabr.ru/post/270317/
Добавить комментарий