Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

ProgressBar - Индикатор прогресса

Основы
Отдельный поток
Вертикальный индикатор прогресса
Стилизация
Индикатор с текстом
Программное создание индикатора

Основы

Компонент ProgressBar (Индикатор прогресса) применяется в тех случаях, когда пользователю нужно показать, что программа не зависла, а выполняет продолжительную работу.

Находится в категории Widgets.

Студия предлагает несколько видов индикаторов: ProgressBar и ProgressBar (Horizontal). В свою очередь компонент имеет несколько модификаций и их отличия заложены в стилях, если посмотреть XML-описание компонентов. Стили постоянно меняются, поэтому проще выбрать атрибут style и посмотреть предлагаемые варианты из выпадающего списка на текущий момент.

Методы для работы с ProgressBar:

  • setProgress() — устанавливает заданное значение индикатора прогресса;
  • getProgress() — возвращает текущее значение индикатора прогресса;
  • incrementProgressBy() — устанавливает величину дискретизации приращения значения индикатора;
  • setMax() — устанавливает максимальное значение величины прогресса.

Круговые индикаторы Normal, Small и Large можно просто разместить на экране, и они будут бесконечно выводить анимацию вращения без единой строчки кода.

Как правило, "бесконечный" индикатор выводят в нужный момент, а когда задача выполнена, то его скрывают. Видимость можно установить через XML и программно.


<ProgressBar
  android:id="@+id/progressBar"
  android:visibility="invisible"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" />

В коде используется метод setVisibility()


ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setVisibility(ProgressBar.VISIBLE);
// запускаем длительную операцию
progressBar.setVisibility(ProgressBar.INVISIBLE);

Сейчас проекты в студии используют Material Design, поэтому цвет индикатора будет соответствовать ресурсу colorAccent.

Можно переопределить цвет, если вы хотите отказаться от этой связи с colorAccent, задав свой стиль.


<style name="CircularProgress" parent="Theme.AppCompat.Light">
    <item name="colorAccent">@android:color/holo_green_light</item>
</style>

Подключаем через тему.


<ProgressBar
    ...
    android:theme="@style/CircularProgress" />

Другая настройка связана с Tint - оттенками цвета (Android 5.0).


<ProgressBar
    android:indeterminateTintMode="src_in"
    android:indeterminateTint="@color/my_color"/>

Горизонтальный индикатор

Горизонтальный индикатор обычно используют, чтобы показать индикатор в действии. При длительной операции пользователь видит, что полоска индикатора постоянно увеличивается.

Существует режим неопределённости (indeterminate), когда индикатор работает постоянно. Для этого в окне свойств нужно установить флажок у нужного свойства. Или через XML


<ProgressBar
    android:id="@+id/progressBar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:indeterminate="true" />

Если нужно показать увеличение прогресса, то данному свойству следует присвоить false. А также задать верхнюю границу диапазона max и начальное значение progress.


<ProgressBar
    android:id="@+id/progressBar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:indeterminate="false"
    android:max="100"
    android:progress="20" />

Атрибут android:secondaryProgress="50" создаёт эффект буфера, когда рисуется ещё одна полоска, опережающая основной цвет. Ей можно управлять через метод setSecondaryProgress().

Переключение между режимами - метод setIndeterminate(boolean indeterminate).

Индикатор прогресса работает в стиле Material Design на Lollipop-устройствах и выше. На старых устройствах выводится другой цвет.

Отдельный поток

Выполнение длительных операций предпочтительнее производить в отдельном потоке. Android имеет специальный класс Handler для создания фоновых потоков и их взаимодействия с пользовательским интерфейсом. Либо используется AsyncTask и другие способы выполнения в другом потоке.

Настройка через XML

Для тонкой настройки компонента под себя следует использовать файл в ресурсах с использованием LayerDrawable. Файл ресурсов должен использовать системный идентификатор @android:id/background для фона, а также идентификатор @android:id/progress для самого индикатора. Также можно использовать ещё один необязательный идентификатор @android:id/secondaryProgress для дополнительного индикатора. Пример ниже.

Вертикальный индикатор прогресса

По умолчанию используется горизонтальный индикатор. Можно создать собственный индикатор с вертикальной ориентацией. В папке res/drawable подготовим два файла:

verticalprogressbar.xml


<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">
        <shape>
            <corners android:radius="5dip" />

            <solid android:color="#FFFFFF" />

            <stroke
                android:width="1dp"
                android:color="#FF000000" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip
            android:clipOrientation="vertical"
            android:gravity="bottom" >
            <shape>
                <corners android:radius="5dip" />

                <solid android:color="#FFAADD" />

                <stroke
                    android:width="1dp"
                    android:color="#FF000000" />
            </shape>
        </clip>
    </item>

</layer-list>

verticalprogressbar2.xml


<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">
        <shape>
            <corners android:radius="5dip" />

            <solid android:color="#FFFFFF" />

            <stroke
                android:width="1dp"
                android:color="#FF000000" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip
            android:clipOrientation="vertical"
            android:gravity="bottom" >
            <shape>
                <corners android:radius="5dip" />

                <solid android:color="#00FF55" />

                <stroke
                    android:width="1dp"
                    android:color="#FF000000" />
            </shape>
        </clip>
    </item>

</layer-list>	

Разница между двумя файлами только в цвете, которым закрашивается индикатор во время работы. Я создал второй файл только для демонстрации программного изменения индикатора - когда значение индикатора достигнет отметки 50, то цвет поменяется на зелёный.

Код можно вставить после строчки из предыдущего примера:


// код из предыдущего примера
Thread.sleep(1000);
// вставим код для изменения цвета
if(myProgress >= 50) myProgressBar.setProgressDrawable(getResources().getDrawable(R.drawable.verticalprogressbar2));

ProgressBar ProgressBar

Также можно использовать атрибут XML.


<ProgressBar
    ...
    android:progressDrawable="@drawable/myprogressbar"/>

Стилизация

Если вам не нравится внешний вид стандартных индикаторов, то можете придумать свой. Есть два варианта для реализации этой цели. В первом случае можно подготовить готовый png-файл (spin_custom.png) и поместить его в ресурс, например, в папку res/drawable.

Custom Spin

Затем в этой же папке создаём xml-файл spinner_png.xml с таким содержанием:


<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/spin_custom"
    android:pivotX="50%"
    android:pivotY="50%"
    />

Теперь в разметке активности можно определять ProgressBar так:


<ProgressBar
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:indeterminateDrawable="@drawable/spinner_png"
    android:indeterminateOnly="true" />

Если запустить проект, то увидим такой индикатор:

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

Второй вариант выглядит предпочтительней. На этот раз мы обойдёмся без графических файлов, а будем использовать только XML-разметку. В этой же папке поместим файл spinner_ring.xml следующего содержания:


<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:pivotX="50%"
    android:pivotY="50%" >

    <shape
        android:innerRadiusRatio="4"
        android:shape="ring"
        android:thicknessRatio="5.333"
        android:useLevel="false" >
        <size
            android:height="18dip"
            android:width="18dip" />

        <gradient
            android:centerColor="#886688cc"
            android:centerY="0.50"
            android:endColor="#ff6688cc"
            android:startColor="#006688cc"
            android:type="sweep"
            android:useLevel="false" />
    </shape>

</animated-rotate>

Теперь в разметке активности пропишем строки:


<ProgressBar
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="20dip"
    android:indeterminateDrawable="@drawable/spinner_ring"
    android:indeterminateOnly="true" />

Получим следующий результат:

Источник

Ещё несколько вариантов индикаторов можно найти здесь.

Для примера с обратным отсчётом я использовал индикатор с двумя drawable-ресурсами, используя shape.

EggTimer

Индикатор с текстом

К сожалению, индикатор не выводит текст во время работы, что не очень удобно. Можно создать свой ProgressBar из TextView или создать свой компонент на основе ProgressBar. Другой простой способ - наложить текстовый блок прямо на индикатор, используя RelativeLayout. Пример для пояснения.


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <RelativeLayout
        android:id="@+id/rl_progress_bar_set"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btn_start"
        android:visibility="visible" >

        <ProgressBar
            android:id="@+id/pb_horizontal"
            style="@android:style/Widget.ProgressBar.Horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:max="100" />

        <TextView
            android:id="@+id/tv_progress_horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/pb_horizontal"
            android:background="@android:color/transparent"
            android:gravity="center" />

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@id/pb_horizontal"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp" >

            <ProgressBar
                style="@android:style/Widget.ProgressBar.Large"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:background="@android:color/background_dark" />

            <TextView
                android:id="@+id/tv_progress_circle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:background="@android:color/transparent"
                android:gravity="center"
                android:textColor="@android:color/white" />
        </RelativeLayout>
    </RelativeLayout>

    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="15dp"
        android:onClick="onClick"
        android:text="Поехали"
        android:textSize="25dp" />

</RelativeLayout>

Код, чтобы увидеть работу индикатора. При каждом щелчке кнопки значение индикаторов увеличиваются на 10 процентов.


package ru.alexanderklimov.test;

import ...

public class MainActivity extends Activity {

	private int progress = 0;
	private ProgressBar pbHorizontal;
	private TextView tvProgressHorizontal;
	private TextView tvProgressCircle;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_main);

		pbHorizontal = (ProgressBar) findViewById(R.id.pb_horizontal);
		tvProgressHorizontal = (TextView) findViewById(R.id.tv_progress_horizontal);
		tvProgressCircle     = (TextView)findViewById(R.id.tv_progress_circle); 
	}

	public void onClick(View v) {
		progress = progress + 10;
		postProgress(progress);
	}

	private void postProgress(int progress) {
		String strProgress = String.valueOf(progress) + " %";
		pbHorizontal.setProgress(progress);
		
		if (progress == 0) {
			pbHorizontal.setSecondaryProgress(0);
		} else {
			pbHorizontal.setSecondaryProgress(progress + 5);
		}
		tvProgressHorizontal.setText(strProgress);
		tvProgressCircle.setText(strProgress);
	}
}

ProgressBar

Программное создание индикатора

Данный способ уже устарел, так как через XML намного удобнее. Для общего развития.


// Зададим цвета для фона и самого индикатора
Drawable background = new ColorDrawable(0xFF373737);
Drawable progress = new ColorDrawable(0xFF00B51C);
ClipDrawable clipProgress = new ClipDrawable(progress, Gravity.LEFT,
		ClipDrawable.HORIZONTAL);

// Слои для индикатора
LayerDrawable layerlist = new LayerDrawable(new Drawable[] {
		background, clipProgress });
layerlist.setId(0, android.R.id.background);
layerlist.setId(1, android.R.id.progress);

// Создаем новый индикатор с горизонтальным стилем и заданным фоном
ProgressBar progressBar = new ProgressBar(this, null,
		android.R.attr.progressBarStyleHorizontal);
progressBar.setLayoutParams(new LinearLayout.LayoutParams(
		LinearLayout.LayoutParams.MATCH_PARENT,
		LinearLayout.LayoutParams.WRAP_CONTENT));
progressBar.setProgressDrawable(layerlist);
progressBar.setProgress(76);

LinearLayout linearlayout = (LinearLayout) findViewById(R.id.linearlayout);

linearlayout.addView(progressBar);

Индикаторы прогресса также можно использовать в заголовках приложения у старых устройств, а также в ActionBar и ToolBar у новых устройств.

Компонент постоянно дорабатывается. Например, в Android 8 появились новые методы isAnimating(), getMin(), setMin().

Анимация ObjectAnimator

Можно подключить готовую анимацию для визуализации компонента.


private static final TimeInterpolator GAUGE_ANIMATION_INTERPOLATOR = new DecelerateInterpolator(2);
private static final int MAX_LEVEL = 100;
private static final long GAUGE_ANIMATION_DURATION = 5000;
private ProgressBar mProgressBar;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mProgressBar = findViewById(R.id.progressBar);
}

// По щелчку кнопки
public void onClick(View view) {
    ObjectAnimator animator = ObjectAnimator.ofInt(mProgressBar, "progress", 0, MAX_LEVEL);
    animator.setInterpolator(GAUGE_ANIMATION_INTERPOLATOR);
    animator.setDuration(GAUGE_ANIMATION_DURATION);
    animator.start();
}

Дополнительное чтение

Меняем стандартный индикатор через drawable - простой и быстрый способ.

Material ProgressBar - статья про создание собственного индикатора в стиле Material Design

Indeterminate - серия статей на тему создания индикаторов прогресса, ссылка на первую часть.

Reducing User Frustration With the release of Capitai... - анимация рельс для движения поезда. Под картинку с поездом подкладывается ProgressBar с ресурсами анимации рельс со шпалами.

I had quite a lot of fun making a circular indeterminate progress bar and wan... - бесконечная анимация.

How to create your own progressBar in Android. – Medium

Make an android custom view, publish and open source.

Библиотеки

yuriy-budiyev/circular-progress-bar: Circular progress bar

timqi/SectorProgressView: a simple progress prompt or chart widget of android using circle and a sector

kofigyan/StateProgressBar: Android library to realize the various states and transitions in a ProgressBar.

JustZak/DilatingDotsProgressBar: A customizable indeterminate progress bar

DreaminginCodeZH/MaterialProgressBar: Material Design ProgressBar with consistent appearance

castorflex/SmoothProgressBar

Todd-Davies/ProgressWheel

passsy/android-HoloCircularProgressBar

daimajia/NumberProgressBar

lsjwzh/MaterialLoadingProgressBar

amanjeetsingh150/CoolProgressViews: Android library with collection of cool progress views.

Подборка индикаторов

glomadrian/loading-balls: A highly configurable library to do loading progress with animated balls - анимация для длительных операций.
loading-balls

droidchef/tinglingsquares: A delightful progress animation

Индикатор прогресса в виде пирога. Домашняя страница FilipPudak/ProgressPieView

cdeange/RopeProgressBar: Android ProgressBar that "bends" under its own weight. Inspired by http://drbl.in/nwih

biodunalfet/SlidingSquaresLoader: A simple progress loader

Реклама