Material ProgressBar для pre-Lollipop

от автора

На момент написания статьи, я работаю с отличными ребятами в Novoda над приложением для трансляции видео для телевидения в Великобритании Channel 4. Один из элементов дизайна, которые мне приходилось реализовывать был бесконечный ProgressBar в стиле Material Design. Для Android Lollipop и выше создание подобного дизайна не составляет труда, но вот поддержка устройств более ранних версий ОС стала для нас испытанием. В этой статье мы рассмотрим решение данной проблемы.

Для начала посмотрим как работает на Lollipop бесконечный ProgressBar:

В то время как стиль виджета выглядел довольно легко реализуем, то корень проблемы лежал в анимации с неопределенным временем. Короткая линия направляется с лева на право, но длина ее варьируется на протяжении путешествия.

Первым делом, я решил сделать бекпорт существующего решения. Но возникли кое-какие трудности с реализацией, так как там использовался AnimatedVectorDrawable, который не доступен до Lollipop. Решение, которое я нашел, немного коварно, но все же дало мне на удивление очень похожий результат.

Решение включает в себя создание нашей собственной реализации ProgressBar (что является наследником стандартного ProgressBar) который полностью обходит стандартную логику неопределенного времени и реализует свою собственную на основе стандартной первичного и вторичного поведения, что уже встроены в ProgressBar. Трюк заключается в методе его обработки — сначала задний план, потом второй прогресс и затем первый. Если задний план и первичный прогресс одного цвета, а вторичный прогресс другого цвета, то создается эффект изменения длины.

Посмотрим на примере для лучшего понимания. Если мы поставим цвет заднего плана светло-зеленым, вторичный прогресс средне-зеленым, а первичный прогресс — темно-зеленым, то получим такой результат:

image

Тем не менее, если мы установим первичный цвет такой же как и задний план, то получим нужный эффект:

image

Мы можем настраивать начальные и конечные точки просто задавая значение 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *