При разработке мобильных приложений часто возникает задача отображения графиков и диаграмм на основе собранных данных. В стандартном Android SDK отсутствуют встроенные средства для визуализации. Разработчик может реализовать отрисовку вручную через Canvas, однако проще использовать готовую библиотеку. Беглый поиск выдаёт несколько решений: AnyChart, HelloCharts, WilliamChart, AndroidPlot и MPAndroidChart. Мы выбрали библиотеку MPAndroidChart версии 3.1.0. Это наиболее популярное решение для построения графиков в экосистеме Android. Библиотека поддерживает линейные, столбчатые, круговые, пузырьковые, финансовые (свечные), радарные и комбинированные диаграммы. Код распространяется под лицензией Apache License 2.0, что разрешает коммерческое использование без ограничений. Проект активно поддерживается и имеет подробную документацию.
Для демонстрации возможностей библиотеки реализуем приложение-трекер воды. Оно будет отображать количество выпитых стаканов за каждый день недели с помощью линейной диаграммы. Мы покажем, как подключить библиотеку к проекту, загрузить данные, настроить внешний вид графика и обновлять его в реальном времени при изменении данных. Изложенные принципы применимы к другим типам диаграмм, поддерживаемым библиотекой. В конце статьи содержится ссылки на исходные файлы проекта.
Самая частая ошибка при старте звучит как «Failed to resolve: com.github.PhilJay:MPAndroidChart». Причина в том, что библиотека размещена на JitPack, а не в стандартном репозитории Google Maven, поэтому Gradle не может её найти без дополнительной настройки репозиториев. Для современных проектов на Gradle 7.0 и выше с Kotlin DSL откройте файл settings.gradle.kts. Найдите блок dependencyResolutionManagement. Внутри секции repositories добавьте адрес репозитория. Параметр FAIL_ON_PROJECT_REPOS запрещает объявлять репозитории в модульных build.gradle файлах. Это заставляет использовать только централизованное объявление в settings.gradle для единообразия конфигурации.
// settings.gradle.kts dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url = uri("https://jitpack.io") } // В Kotlin DSL обязательно url = uri(...) } }
Затем перейдите в файл build.gradle.kts уровня app. Добавьте строку реализации в секцию dependencies. Указывайте актуальную стабильную версию v3.1.0. Эта версия проверена временем и содержит исправления критических ошибок. Если после синхронизации появляются ошибки, связанные с версиями, убедитесь, что ваш проект использует совместимую версию Android Gradle Plugin. Для v3.1.0 это обычно не проблема.
// build.gradle.kts (app) dependencies { implementation("com.github.PhilJay:MPAndroidChart:v3.1.0") }
Если у вас legacy проект на Groovy, то настройка выглядит иначе. Откройте build.gradle корневой директории проекта. Добавьте URL в блок allprojects repositories. В Gradle 7.0 и выше этот блок может быть объявлен в settings.gradle через dependencyResolutionManagement, но для простоты мы показываем legacy-способ, который работает во всех версиях. В Groovy DSL допускаются как одинарные, так и двойные кавычки для URL.
// build.gradle (project level) allprojects { repositories { google() mavenCentral() maven { url 'https://jitpack.io' } } }
После внесения изменений нажмите кнопку Sync Now в правом верхнем углу Android Studio. Убедитесь, что процесс синхронизации завершился успешно без красных сообщений. Для полной проверки запустите сборку через терминал командой ./gradlew assembleDebug. Отсутствие ошибок компиляции подтверждает успешное подключение библиотеки к проекту.
Важное предупреждение касается доступа к сети. Репозиторий JitPack требует стабильного интернет соединения. Если синхронизация не удалась из-за ошибки соединения, повторите попытку позже или временно используйте другую сеть. Проверьте доступность jitpack.io через браузер. Если зависимость всё равно не находится после правильной настройки, попробуйте выполнить File → Invalidate Caches / Restart в Android Studio для очистки кэша Gradle.
Теперь библиотека готова к использованию в коде. В следующем разделе мы добавим график на экран и настроим его отображение для трекера питья воды.
Давайте для примера добавим график, на котором будем отображать сколько пользователь выпил воды за день. Начнём с разметки интерфейса в XML-файле.
Откройте layout-файл вашей Activity (app\res\layout\activity_main.xml). Добавьте компонент LineChart с достаточными размерами для читаемости. Критически важно установить высоту минимум 300dp. Меньшая высота сделает график нечитаемым на большинстве экранов. Разместите кнопку для добавления данных ниже графика.
Обратите внимание на полное имя класса com.github.mikephil.charting.charts.LineChart. Этот класс принадлежит подключённой библиотеке MPAndroidChart, а не стандартному Android SDK. Перед вводом этого класса убедитесь, что вы выполнили синхронизацию проекта после добавления зависимости на Шаге 1. Без синхронизации автодополнение не распознает класс библиотеки.Помимо LineChart мы создадим кнопку, при нажатии на которую количество выпитых сегодня стаканов воды будет увеличиваться на единицу.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <com.github.mikephil.charting.charts.LineChart android:id="@+id/lineChart" android:layout_width="match_parent" android:layout_height="300dp" /> <Button android:id="@+id/btnAddData" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="10dp" android:text="Добавить стакан" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/lineChart" /> </LinearLayout>
В MainActivity объявите переменные для графика и хранения данных. Каждая точка представляет собой объект Entry с координатами X и Y. В нашем примере координата X обозначает день недели, а Y показывает количество стаканов воды. Используйте ArrayList для динамического управления данными. В примере для хранения данных используется ArrayList<Entry>(). В современных проектах на Kotlin чаще применяют mutableListOf<Entry>(). Оба варианта допустимы и работают одинаково. Для простоты будем отображать статичные данные. Заполнение списка waterData реализовано в методе initChartData().
//Подключаем необходимые библиотеки import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.data.Entry class MainActivity : AppCompatActivity() { private lateinit var lineChart: LineChart private val waterData = ArrayList<Entry>() private val days = arrayOf("Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс") private var currentDayIndex = 6 // Текущий день override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) lineChart = findViewById(R.id.lineChart) findViewById<Button>(R.id.btnAddData).setOnClickListener { addWaterData() } initChartData() setupChart() } private fun initChartData() { waterData.add(Entry(0f, 5f)) waterData.add(Entry(1f, 7f)) waterData.add(Entry(2f, 6f)) waterData.add(Entry(3f, 8f)) waterData.add(Entry(4f, 4f)) waterData.add(Entry(5f, 9f)) waterData.add(Entry(6f, 7f)) }
Координата X начинается с 0 и соответствует понедельнику. Это значение совпадает с индексом в массиве days. Entry(0f, 5f) означает понедельник и 5 стаканов воды. Entry(6f, 7f) означает воскресенье и 7 стаканов. Такая привязка обеспечивает правильное отображение дней недели на оси X.
Метод setupChart() отвечает за визуальное оформление графика. Там задается цвет и стиль точек, цвет линии, параматры отображения осей, диапазон значений. Приведенный ниже текст метода подробно документирован.
private fun setupChart() {
// Создаём набор данных для линейного графика
// Параметры: список точек (waterData) и описание линии для легенды
val dataSet = LineDataSet(waterData, "Стаканы воды")
// === Настройка цветов точек (кружков) ===
// В библиотеке цвета кружков задаются списком, где каждый элемент соответствует точке по индексу
val circleColors = ArrayList<Int>()
for (i in 0 until waterData.size) {
if (i == currentDayIndex) {
// Текущий день выделяем красным цветом для визуального акцента
circleColors.add(Color.RED)
} else {
// Остальные дни - голубой цвет (RGB: 110, 200, 255)
circleColors.add(Color.rgb(110, 200, 255))
}
}
// Применяем список цветов к набору данных
// Важно: длина списка должна точно соответствовать количеству точек в waterData
dataSet.setCircleColors(circleColors)
// === Настройка внешнего вида линии ===
// Основной цвет линии графика (синий)
dataSet.color = Color.BLUE
// Толщина линии в пикселях
dataSet.lineWidth = 3f
// Включаем отображение кружков в точках данных
dataSet.setDrawCircles(true)
// Радиус кружков в пикселях
dataSet.circleRadius = 5f
// Отображать числовые значения над каждой точкой
dataSet.setDrawValues(true)
// Цвет текста значений
dataSet.valueTextColor = Color.BLACK
// Размер шрифта значений в sp
dataSet.valueTextSize = 12f
// === Создание объекта данных графика ===
// LineData объединяет один или несколько наборов данных (в нашем случае один)
val lineData = LineData(dataSet)
// Устанавливаем данные в компонент графика
lineChart.data = lineData
// === Настройка горизонтальной оси X ===
val xAxis = lineChart.xAxis
// Позиционируем ось внизу графика
xAxis.position = XAxis.XAxisPosition.BOTTOM
// Форматируем значения оси X: заменяем числа на дни недели из массива days
xAxis.valueFormatter = IndexAxisValueFormatter(days)
// Минимальный шаг между метками (1 = каждая точка имеет свою метку)
xAxis.granularity = 1f
// Скрываем вертикальные линии сетки для упрощения восприятия
xAxis.setDrawGridLines(false)
// === Настройка вертикальной оси Y (левая) ===
val yAxisLeft = lineChart.axisLeft
// Устанавливаем минимальное значение оси (0 стаканов)
yAxisLeft.axisMinimum = 0f
// Динамический расчёт максимального значения оси
// Ищем точку с наибольшим значением Y в списке данных
// ?: 0f - если список пустой, используем 0 как значение по умолчанию
val max = waterData.maxOfOrNull { it.y } ?: 0f
// Устанавливаем максимум с запасом в 2 единицы для визуального комфорта
// Без запаса максимальная точка будет «прилипать» к верхней границе графика
yAxisLeft.axisMaximum = max + 2f
// === Дополнительные настройки ===
// Отключаем правую ось Y, так как она не используется в нашем примере
lineChart.axisRight.isEnabled = false
// Добавляем анимацию появления графика по вертикали (1000 мс = 1 секунда)
lineChart.animateY(1000)
// Запускаем перерисовку компонента. Без этого вызова график не отобразится на экране
lineChart.invalidate()
}
Обратите внимание на вызов invalidate() в конце метода. Это критически важный шаг для отображения графика на экране. Метод сообщает компоненту о необходимости перерисовки. Без этого вызова вы увидите пустой экран вместо диаграммы. Это самая распространённая ошибка новичков при работе с MPAndroidChart.
Важное уточнение касается поведения invalidate(). При замене набора данных через lineChart.data = newLineData метод invalidate() вызывается автоматически. Однако при изменении существующего DataSet без замены объекта требуется явный вызов invalidate(). В нашем примере мы вызываем его явно для гарантии отображения.
После создания LineDataSet и LineData важно понимать, что при изменении данных в waterData потребуется обновить набор данных. В следующем разделе мы подробно разберём правильный порядок действий: изменение списка, создание нового DataSet и вызов invalidate(). Простое добавление Entry в ArrayList не обновит график автоматически.
Запустите приложение и проверьте результат. Вы должны увидеть линейный график с семью точками данных. Ось X отображает дни недели вместо чисел. Линия имеет синий цвет с кружками на каждой точке. Значения отображаются над каждой точкой. Анимация плавно показывает график при запуске. В следующем разделе мы добавим возможность обновления данных в реальном времени через кнопку с последующим обновлением графика.

Статичный график демонстрирует данные, но пользователь хочет видеть прогресс в реальном времени. Давайте реализуем функционал кнопки "Добавить стакан", которая обновляет данные и перерисовывает диаграмму без перезапуска Activity.
Создайте метод addWaterData(), который вызывается при нажатии кнопки. Мы будем обновлять данные для текущего дня. В примере используется фиксированный индекс 6 для воскресенья. В реальном приложении индекс должен определяться динамически на основе текущей даты через класс Calendar или LocalDate. Пример упрощён для демонстрации механизма обновления.
fun addWaterData() { if (waterData.isNotEmpty() && currentDayIndex < waterData.size) { val currentValue = waterData[currentDayIndex].y val newValue = currentValue + 1f // Модифицируем существующий объект Entry, а не создаём новый // Библиотека хранит ссылки на объекты, поэтому изменения отразятся в графике waterData[currentDayIndex].y = newValue updateChart() } }
Обратите внимание на проверку списка на пустоту. Это обработка краевого случая. Если данных нет, то попытка доступа по индексу вызовет исключение. В реальном приложении добавьте проверку границ массива для защиты от ошибок. После того как данные изменены, вызывается метод обновления графика updateChart()
Критически важно соблюдать правильную последовательность вызовов при обновлении: сначала lineChart.data.notifyDataChanged() для уведомления данных об изменении значений, затем lineChart.notifyDataSetChanged() для пересчёта границ осей и легенды, и только потом lineChart.invalidate() для перерисовки. Прямые вызовы методов у LineDataSet не требуются в версии 3.1.0.
private fun updateChart() { // Обновляем свойства осей val max = waterData.maxOfOrNull { it.y } ?: 0f lineChart.axisLeft.axisMaximum = max + 2f // Уведомляем ГРАФИК (не данные!) о необходимости пересчёта // Этот метод пересчитывает границы осей, легенду и внутренние кэши lineChart.notifyDataSetChanged() // Запускаем перерисовку lineChart.invalidate() }
При работе с несколькими наборами данных используйте цикл:
lineChart.data.dataSets.forEach { set -> if (set is LineDataSet) { set.notifyDataChanged() } } lineChart.data.notifyDataSetChanged() lineChart.invalidate()
Последовательность notifyDataChanged, notifyDataSetChanged и invalidate обеспечивает корректное обновление. Метод notifyDataChanged сообщает набору данных об изменении значений. notifyDataSetChanged обновляет внутренние кэши графика. invalidate запускает процесс отрисовки на экране.
При каждом обновлении не нужно пересоздавать весь объект LineData или DataSet. Это экономит ресурсы процессора и памяти. Создание новых объектов при каждом нажатии кнопки приведёт к лишней нагрузке на сборщик мусора. Использование существующего экземпляра предотвращает мерцание графика и обеспечивает плавную анимацию.
Создание нового DataSet оправдано при полной замене данных. Например, при смене недели в календаре или загрузке нового набора из сети. При создании нового DataSet теряются все предыдущие настройки. Цвет линии, толщина, видимость кружков и другие параметры придётся настраивать заново. Это дополнительный аргумент в пользу описанного подхода.
Простое добавление Entry в ArrayList не обновит график автоматически. Библиотека не отслеживает изменения в списке и явный вызов методов уведомления обязателен. В следующем разделе мы разберём некоторые типичные ошибки.
Даже при следовании пошаговой инструкции возникают проблемы. Рассмотрим шесть частых ошибок и способы их решения. Для каждой проблемы приведена причина и конкретное решение.
Причина: забыли вызвать метод invalidate() после настройки данных или внешнего вида.
Решение: добавьте вызов lineChart.invalidate() в конце метода настройки графика. Без этого вызова компонент не перерисует себя на экране. Вызывайте invalidate() не только после первоначальной настройки, но и после любых последующих изменений данных или внешнего вида. Проверьте, что метод вызывается после всех изменений данных и свойств.
Причина: цвет линии совпадает с цветом фона, особенно при включённой тёмной теме устройства.
Решение: установите контрастный цвет через свойство цвета линии. Используйте ресурсы цветов из файла colors.xml для поддержки тёмной темы. Проверьте отображение в обеих темах через настройки эмулятора. Пример использования цвета из ресурсов:
dataSet.color = ContextCompat.getColor(this, R.color.chart_line)
Цвет с именем chart_line должен быть предварительно определён в файле ресурсов res/values/colors.xml. Если читатель адаптирует код для работы во фрагменте, вместо this используйте requireContext() для получения корректного контекста.
Причина: отсутствует репозиторий JitPack в файлах настройки Gradle. Библиотека не находится в стандартных репозиториях Maven Central или Google.
Решение: добавьте репозиторий JitPack в settings.gradle.kts или в блок allprojects файла проекта. Выполните синхронизацию проекта через кнопку Sync в правом верхнем углу. Убедитесь, что подключение к интернету стабильно. Если ошибка остаётся после добавления репозитория, попробуйте очистить кэш Gradle через File → Invalidate Caches / Restart в меню Android Studio.
Причина: пропущены методы уведомления об изменении данных. Простое изменение списка не приводит к автоматическому обновлению графика.
Решение: вызывайте методы уведомления в правильной последовательности. Сначала dataSet.notifyDataChanged(), затем lineChart.data.notifyDataSetChanged(), и только потом lineChart.invalidate(). Проверьте, что DataSet не равен null перед вызовом методов.
Важное уточнение: если вы обновляете существующий набор данных, требуется полная цепочка уведомлений и вызов invalidate(). Если создаёте новый объект данных и устанавливаете его через свойство данных графика, всё равно рекомендуется явно вызвать метод перерисовки для гарантированной отрисовки. Не полагайтесь на автоматические вызовы.
Причина: отключено аппаратное ускорение, необходимое для отрисовки сложной графики в компоненте графика.
Решение: добавьте в файл манифеста атрибут hardwareAccelerated со значением true. Это включит аппаратное ускорение для всего приложения или конкретной активности. Аппаратное ускорение требуется только для старых версий Android до 4.4. На современных устройствах оно включено по умолчанию, и добавление атрибута не навредит. Пример включения для всего приложения:
<application
android:hardwareAccelerated="true"
android:label="@string/app_name"
... >
Для включения только для конкретной активности используйте атрибут в теге activity:
<activity
android:name=".MainActivity"
android:hardwareAccelerated="true" />
Атрибут можно задать и для отдельного элемента интерфейса в коде, но в контексте нашей разметки это не требуется.
Причина: неправильные размеры в XML-разметке или наложение других элементов интерфейса.
Решение: установите минимальную высоту 300dp для компонента графика. Проверьте layout_constraints или атрибуты веса в родительском контейнере. Убедитесь, что другие элементы не перекрывают график через атрибуты расположения. Проверьте свойство видимости компонента и порядок элементов в контейнере. Во вложенных контейнерах типа элементы могут накладываться друг на друга в порядке объявления.
Эти решения покрывают большинство проблем, с которыми сталкиваются разработчики.
Мы прошли полный цикл интеграции библиотеки MPAndroidChart в Android-приложение. Сначала настроили Gradle для подключения зависимости через репозиторий JitPack. Затем добавили компонент LineChart в разметку и подготовили данные в виде списка объектов Entry. Настроили внешний вид графика: цвет линии, кружки точек, форматирование осей и анимацию. Реализовали механизм обновления данных в реальном времени с правильной последовательностью вызовов: изменение значений, уведомление графика через notifyDataSetChanged() и перерисовка через invalidate(). Рассмотрели типичные ошибки.
Стоит сказать, что библиотека поддерживает не только линейные графики. Для других сценариев визуализации доступны следующие типы диаграмм:
Синтаксис работы с этими диаграммами аналогичен рассмотренному примеру. Отличается только класс компонента в разметке (например, <com.github.mikephil.charting.charts.BarChart>) и тип набора данных (BarDataSet вместо LineDataSet). Полный список возможностей и примеров кода доступен в официальной документации библиотеки на GitHub.
Экспериментируйте с типами диаграмм и настройками внешнего вида. Попробуйте реализовать столбчатую диаграмму для отображения ежедневной статистики или круговую для показа процента выполнения недельной цели. Каждый новый тип визуализации расширяет возможности вашего приложения без необходимости писать собственный рендеринг с нуля.
Вопрос к читателям: какой тип диаграммы вы используете чаще всего в своих проектах и почему? Поделитесь опытом в комментариях.