Инструкции

Представляем Cahier: новый пример приложения для Android на GitHub, предназначенный для повышения производительности и творчества на больших экранах.

11 минут чтения
Chris Assigbe
Инженер по связям с разработчиками

API Ink теперь находится в стадии бета-тестирования и готов к интеграции в ваше приложение. Этот этап стал возможен благодаря ценным отзывам разработчиков, что привело к постоянному улучшению производительности, стабильности и визуального качества API.

Приложения Google, такие как Google Docs , Pixel Studio , Google Photos , Chrome PDF , YouTube Effect Maker, а также уникальные функции Android, например Circle to Search , используют новейшие API.

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

Что такое Cahier?

Cahier («блокнот» по-французски) — это пример приложения, демонстрирующий, как можно создать приложение, позволяющее пользователям фиксировать и систематизировать свои мысли, комбинируя текст, рисунки и изображения.

Этот пример может служить отличным справочным пособием для повышения производительности и креативности пользователей на больших экранах. Он демонстрирует лучшие практики создания подобных интерфейсов, ускоряя понимание и внедрение разработчиками соответствующих мощных API и методов. В этой статье мы рассмотрим основные функции Cahier, ключевые API и архитектурные решения, которые делают этот пример отличным справочным материалом для ваших собственных приложений.

Ключевые особенности, продемонстрированные в образце, включают:

  • Универсальное создание заметок: демонстрируется, как реализовать гибкую систему создания контента, поддерживающую несколько форматов в рамках одной заметки, включая текст, произвольные рисунки и прикрепленные изображения.
  • Инструменты для креативного рисования тушью : Реализует высокопроизводительный процесс рисования с низкой задержкой, используя API Ink . Пример демонстрирует практическую интеграцию различных кистей, палитры цветов, функций отмены/повтора действий и инструмента «ластик».
  • Гибкая интеграция контента с помощью перетаскивания : демонстрирует, как обрабатывать входящий и исходящий контент с помощью перетаскивания. Это включает в себя прием изображений, перетаскиваемых из других приложений, и предоставление пользователям возможности перетаскивать контент из вашего приложения для беспрепятственного обмена.
  • Организация заметок : отмечайте заметки как избранные для быстрого доступа. Фильтруйте отображение для поддержания порядка.
  • Архитектура с приоритетом на автономный режим: Приложение создано с использованием архитектуры Room , ориентированной на автономный режим, что гарантирует локальное сохранение всех данных и полную функциональность приложения без подключения к интернету.
  • Мощная поддержка многооконного и многоэкземплярного режима : демонстрирует, как поддерживать многоэкземплярный режим, позволяя запускать приложение в нескольких окнах, чтобы пользователи могли работать над разными заметками одновременно, повышая производительность и креативность на больших экранах.
  • Адаптивный пользовательский интерфейс для всех экранов : пользовательский интерфейс плавно адаптируется к различным размерам и ориентациям экрана с помощью ListDetailPaneScaffold и NavigationSuiteScaffold , обеспечивая оптимизированное взаимодействие с пользователем на телефонах, планшетах и ​​складных устройствах.
  • Глубокая системная интеграция : В этом руководстве описано, как сделать ваше приложение приложением для создания заметок по умолчанию на Android 14 и выше, реагируя на системные интенты Notes, что позволяет быстро фиксировать контент из различных точек входа в систему.

Создан для повышения производительности и креативности на больших экранах.

На начальном этапе запуска мы сосредоточимся на нескольких ключевых функциях, которые делают Cahier важным учебным ресурсом как для повышения производительности, так и для развития творческих способностей.

Основа адаптивности

Cahier изначально разработан с учетом адаптивности. В примере используется библиотека material3-adaptive, а именно ListDetailPaneScaffold и NavigationSuiteScaffold, для плавной адаптации макета приложения к различным размерам и ориентациям экрана. Это важнейший элемент для современного Android-приложения, и Cahier наглядно демонстрирует, как эффективно его реализовать.

Адаптивный пользовательский интерфейс Cahier, созданный с использованием библиотеки Material 3 Adaptive..gif

Адаптивный пользовательский интерфейс Cahier, созданный с использованием библиотеки Material 3 Adaptive.

Демонстрация ключевых API и интеграций.

Данный пример демонстрирует мощные API-интерфейсы для повышения производительности, которые вы можете использовать в своих приложениях, в том числе:

Более подробный обзор ключевых API

Давайте подробнее рассмотрим два ключевых API, которые Cahier интегрирует для обеспечения первоклассного опыта ведения заметок.

Создание естественных интерфейсов для рисования с помощью Ink API

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

API Ink предлагает модульную архитектуру, позволяющую адаптировать его к конкретным потребностям и стеку технологий вашего приложения. Модули API включают в себя:

  • Модули для создания макетов ( Compose - views ): Обработка ввода данных при рукописном вводе в реальном времени для создания плавных штрихов с минимальной задержкой, которую может обеспечить устройство.
    • В DrawingSurface Cahier использует недавно представленный модуль InProgressStrokes для обработки ввода с помощью стилуса или сенсорного экрана в реальном времени. Этот модуль отвечает за захват событий указателя и отрисовку штрихов чернилами с минимально возможной задержкой.
  • Модуль Strokes : представляет собой ввод чернил и его визуальное отображение. Когда пользователь заканчивает рисовать линию, функция обратного вызова onStrokesFinished предоставляет приложению завершенный/сухой объект Stroke . Этот неизменяемый объект, представляющий собой завершенный штрих чернил, затем управляется в DrawingCanvasViewModel .
  • Модуль рендеринга: эффективно отображает штрихи чернил, позволяя комбинировать их с Jetpack Compose или представлениями Android .
    • Для отображения как существующих, так и новых штрихов Cahier использует CanvasStrokeRenderer в DrawingSurface для активного рисования и в DrawingDetailPanePreview для отображения статического предварительного просмотра ноты. Этот модуль эффективно рисует объекты Stroke на холсте .
  • Модули кистей ( Compose - views ): предоставляют декларативный способ определения визуального стиля штрихов. Недавние обновления (с момента выпуска alpha03) включают новую пунктирную кисть , особенно полезную для таких функций, как выделение лассо. DrawingCanvasViewModel хранит состояние текущей кисти. Панель инструментов в DrawingCanvas позволяет пользователям выбирать различные семейства кистей (например, StockBrushes.pressurePen() или StockBrushes.highlighter() ) и изменять цвета. ViewModel обновляет объект Brush , который затем используется компонентом InProgressStrokes для создания новых штрихов.
  • Модули геометрии ( Компоновка - Виды ): Поддерживают манипулирование и анализ обводки для таких функций, как стирание и выделение.
    • Инструмент «Ластик» на панели инструментов и функциональность DrawingCanvasViewModel основаны на модуле геометрии. Когда ластик активен, он создает MutableParallelogram вокруг траектории жеста пользователя. Затем ластик проверяет наличие пересечений между формой и ограничивающими рамками существующих штрихов, чтобы определить, какие штрихи нужно удалить, что делает работу ластика интуитивно понятной и точной.
  • Модуль хранения : обеспечивает эффективную сериализацию и десериализацию данных чернил, что приводит к значительной экономии места на диске и в сети. Для сохранения рисунков Cahier сохраняет объекты Stroke в своей базе данных Room. В Converters пример использует функцию encode модуля хранения для сериализации StrokeInputBatch (исходных данных точек) в ByteArray . Массив байтов вместе со свойствами кисти сохраняется в виде строки JSON. Функция decode используется для восстановления штрихов при загрузке заметки.
orion.png

Помимо этих основных модулей, недавние обновления расширили возможности API Ink:

  • Новые экспериментальные API для пользовательских объектов BrushFamily позволяют разработчикам создавать креативные и уникальные типы кистей, открывая возможности для таких инструментов, как кисти «Карандаш» и «Лазерная указка» .

В Cahier используются специально разработанные кисти, в том числе уникальная музыкальная кисть, представленная ниже, для демонстрации расширенных творческих возможностей.

Радужный лазер, созданный с помощью пользовательских кистей Ink API..gif

Радужный лазер, созданный с помощью пользовательских кистей Ink API.

notes.png

Музыкальная кисть, созданная с помощью пользовательских кистей Ink API.

  • Встроенные модули взаимодействия Jetpack Compose упрощают интеграцию функций рукописного ввода непосредственно в пользовательский интерфейс Compose, обеспечивая более идиоматичный и эффективный процесс разработки.

API Ink предлагает ряд преимуществ, которые делают его идеальным выбором для приложений, повышающих производительность и способствующих творчеству, по сравнению с собственной реализацией:

  • Простота использования: API Ink абстрагирует сложные графические и геометрические элементы, позволяя вам сосредоточиться на основных функциях Cahier.
  • Производительность: Встроенная поддержка низкой задержки и оптимизированный рендеринг обеспечивают плавную и отзывчивую работу с рукописным вводом.
  • Гибкость: модульная конструкция позволяет выбирать необходимые компоненты, что обеспечивает бесшовную интеграцию API Ink в архитектуру Cahier.

API Ink уже используется во многих приложениях Google, в том числе для разметки в документах и ​​для функции Circle to Search, а также в партнерских приложениях, таких как Orion Notes и PDF Scanner .

«Ink API был нашим первым выбором для функции Circle-to-Search (CtS). Благодаря обширной документации интеграция Ink API прошла очень легко, что позволило нам создать первый рабочий прототип всего за одну неделю. Поддержка пользовательских текстур кисти и анимации в Ink позволила нам быстро дорабатывать дизайн штрихов». — Джордан Комода, инженер-программист, Google

Стать приложением для заметок по умолчанию при наличии роли «Заметки»

Создание заметок — это ключевая функция, повышающая производительность пользователей на устройствах с большими экранами. Благодаря функции ролей для заметок пользователи могут получать доступ к совместимым приложениям с экрана блокировки или во время работы других приложений. Эта функция определяет и устанавливает системные приложения для создания заметок по умолчанию и предоставляет им разрешение на запуск для сохранения контента.

Реализация в Cahier

Реализация роли «заметки» включает в себя несколько ключевых шагов, все из которых продемонстрированы в примере:

  1. Объявление в манифесте : Во-первых, приложение должно объявить о своей способности обрабатывать намерения для создания заметок. В файле AndroidManifest.xml Cahier включает <intent-filter> для действия android.intent.action.CREATE_NOTE . Это сигнализирует системе, что приложение является потенциальным кандидатом на роль приложения для создания заметок.
  2. Проверка статуса роли : SettingsViewModel использует RoleManager Android для определения текущего статуса. SettingsViewModel проверяет, доступна ли роль «Заметки» на устройстве ( isRoleAvailable ) и занимает ли Cahier в данный момент эту роль ( isRoleHeld ). Это состояние передается в пользовательский интерфейс с помощью потоков Kotlin.
  3. Запрос роли : В файле Settings.kt пользователю отображается кнопка , если роль доступна, но не занята. При нажатии на кнопку вызывается функция requestNotesRole в ViewModel. Эта функция создает интент для открытия экрана настроек приложения по умолчанию, где пользователь может выбрать Cahier. Процесс управляется с помощью API rememberLauncherForActivityResult , который отвечает за запуск интента и получение результата.
  4. Обновление пользовательского интерфейса : После того, как пользователь возвращается с экрана настроек, функция обратного вызова ActivityResultLauncher запускает функцию в ViewModel для обновления статуса роли, обеспечивая точное отображение в пользовательском интерфейсе того, является ли приложение теперь приложением по умолчанию.

Узнайте, как интегрировать функцию заметок в ваше приложение, в нашем руководстве по созданию приложения для ведения заметок .

helloworld.png

Приложение Cahier запускалось в плавающем окне как приложение для создания заметок по умолчанию на планшете Lenovo.

Значительный шаг вперед: Lenovo включает функцию заметок.

Мы рады объявить о значительном шаге вперед в повышении производительности Android-устройств с большими экранами: Lenovo добавила поддержку функции «Заметки» на планшетах под управлением Android 15 и выше! Благодаря этому обновлению вы можете обновить свои приложения для заметок, чтобы пользователи совместимых устройств Lenovo могли установить их в качестве приложений по умолчанию, обеспечивая беспрепятственный доступ с экрана блокировки и разблокируя функции захвата контента на системном уровне.

Это обязательство ведущего производителя оборудования демонстрирует растущую важность функции заметок для обеспечения по-настоящему интегрированного и продуктивного пользовательского опыта на Android.

Многоэкземплярная, многооконная и оконная работа рабочего стола

Эффективная работа на большом экране – это прежде всего управление информацией и рабочими процессами. Именно поэтому Cahier создан с учетом всех возможностей Android по работе с окнами, предоставляя гибкое рабочее пространство, адаптирующееся к потребностям пользователя. Приложение поддерживает:

  • Многооконный режим : фундаментальная возможность запуска приложения параллельно с другим приложением в режиме разделенного экрана или произвольного форматирования. Это необходимо для таких задач, как просмотр веб-страницы во время ведения заметок в Cahier.
  • Многозадачность : Вот где проявляется настоящая многозадачность. Cahier позволяет пользователям открывать несколько независимых окон приложения одновременно . Представьте, что вы сравниваете две разные заметки рядом или обращаетесь к текстовой заметке в одном окне, работая над рисунком в другом. Cahier демонстрирует, как управлять этими отдельными экземплярами, каждый из которых имеет собственное состояние, превращая ваше приложение в мощный, многофункциональный инструмент.
  • Оконный режим рабочего стола : При подключении к внешнему дисплею режим рабочего стола Android превращает планшет или складной компьютер в рабочую станцию. Благодаря адаптивному пользовательскому интерфейсу и поддержке многоэкземплярной работы, Cahier прекрасно работает в этой среде. Пользователи могут открывать, изменять размер и позиционировать несколько окон Cahier, как на традиционном настольном компьютере, что позволяет выполнять сложные рабочие процессы, ранее недоступные на мобильных устройствах.
cahier-desktop-windowing.webp

Приложение Cahier работает в оконном режиме рабочего стола на планшете Pixel.

Вот как мы реализовали эти функции в Cahier:

Для включения многоэкземплярного режима сначала необходимо было сообщить системе, что приложение поддерживает многократный запуск, добавив свойство PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI в объявление MainActivity в AndroidManifest :

<activity

    android:name="com.example.cahier.MainActivity"

    android:exported="true"

    android:label="@string/app_name"

    android:theme="@style/Theme.MyApplication"

    android:showWhenLocked="true"

    android:turnScreenOn="true"

    android:resizeableActivity="true"

    android:launchMode="singleInstancePerTask">


    <property

        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"

        android:value="true"/>

    ...

</activity>

Далее мы реализовали логику запуска нового экземпляра приложения. В файле CahierHomeScreen.kt , когда пользователь решает открыть заметку в новом окне, мы создаём новый Intent со специальными флагами, которые указывают системе, как обрабатывать запуск новой активности. Комбинация флагов FLAG_ACTIVITY_NEW_TASK , FLAG_ACTIVITY_MULTIPLE_TASK и FLAG_ACTIVITY_LAUNCH_ADJACENT гарантирует, что заметка откроется в новом, отдельном окне рядом с существующим.

fun openNewWindow(activity: Activity?, note: Note) {

    val intent = Intent(activity, MainActivity::class.java)

    intent.putExtra(AppArgs.NOTE_TYPE_KEY, note.type)

    intent.putExtra(AppArgs.NOTE_ID_KEY, note.id)

    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK or

        Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT


    activity?.startActivity(intent)

}

Для поддержки многооконного режима нам необходимо было сообщить системе, что приложение поддерживает изменение размера, указав в манифесте элемент <activity> или <application> .

<activity

    android:name="com.example.cahier.MainActivity"

    android:resizeableActivity="true"

    ...>

</activity>

Благодаря использованию адаптивной библиотеки Material 3, пользовательский интерфейс плавно адаптируется к многооконным сценариям, таким как режим разделенного экрана в Android.

Для улучшения пользовательского опыта мы добавили поддержку перетаскивания. Ниже вы можете увидеть, как мы реализовали это в Cahier.

Перетаскивание

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

  • Простой импорт : пользователи могут перетаскивать изображения из других приложений — например, веб-браузера, фотогалереи или файлового менеджера — и помещать их непосредственно на холст заметки. Для этого Cahier использует модификатор dragAndDropTarget для определения зоны перетаскивания, проверки на совместимость содержимого (например, image/* ) и обработки входящего URI.
  • Простой обмен : контент внутри Cahier так же легко передавать, как и контент из других приложений. Пользователи могут долго удерживать изображение внутри текстовой заметки или долго удерживать холст композиции из рисунка и изображения и перетащить его в другое приложение.

Технический анализ: Перенос данных с чертежной доски

Реализация жеста перетаскивания на холсте для рисования представляет собой уникальную задачу. В нашем DrawingSurface компоненты, обрабатывающие ввод данных для рисования в реальном времени ( InProgressStrokes из Ink API), и Box , который распознает длительное нажатие для начала перетаскивания, являются соседними компонентами .

По умолчанию система ввода указателя в Jetpack Compose устроена таким образом, что событие получает только один соседний компонент (первый в порядке объявления, перекрывающий точку касания). В случае Cahier нам нужно, чтобы логика обработки ввода при перетаскивании успела выполниться и, возможно, обработать ввод до того, как компонент InProgressStrokes использует весь необработанный ввод для рисования, а затем обработает его. Если мы не расположим элементы в правильном порядке, наш Box не обнаружит жест длительного нажатия для начала перетаскивания, или InProgressStrokes не получит ввод для рисования.

Для решения этой проблемы мы создали собственный модификатор pointerInputWithSiblingFallthrough и разместили наш Box с этим модификатором перед InProgressStrokes в коде компонуемого объекта. Эта утилита представляет собой тонкую обертку над стандартной системой pointerInput , но с одним важным изменением: она переопределяет функцию sharePointerInputWithSiblings() и возвращает true . Это указывает фреймворку Compose разрешить передачу событий указателя соседним компонуемым объектам, даже после их обработки.

internal fun Modifier.pointerInputWithSiblingFallthrough(

    pointerInputEventHandler: PointerInputEventHandler

) = this then PointerInputSiblingFallthroughElement(pointerInputEventHandler)


private class PointerInputSiblingFallthroughModifierNode(

    pointerInputEventHandler: PointerInputEventHandler

) : PointerInputModifierNode, DelegatingNode() {


    var pointerInputEventHandler: PointerInputEventHandler

        get() = delegateNode.pointerInputEventHandler

        set(value) {

            delegateNode.pointerInputEventHandler = value

        }


    val delegateNode = delegate(

        SuspendingPointerInputModifierNode(pointerInputEventHandler)

    )


    override fun onPointerEvent(

        pointerEvent: PointerEvent,

        pass: PointerEventPass,

        bounds: IntSize

    ) {

        delegateNode.onPointerEvent(pointerEvent, pass, bounds)

    }


    override fun onCancelPointerInput() {

        delegateNode.onCancelPointerInput()

    }


    override fun sharePointerInputWithSiblings() = true

}


private data class PointerInputSiblingFallthroughElement(

    val pointerInputEventHandler: PointerInputEventHandler

) : ModifierNodeElement<PointerInputSiblingFallthroughModifierNode>() {


    override fun create() = PointerInputSiblingFallthroughModifierNode(pointerInputEventHandler)


    override fun update(node: PointerInputSiblingFallthroughModifierNode) {

        node.pointerInputEventHandler = pointerInputEventHandler

    }


    override fun InspectorInfo.inspectableProperties() {

        name = "pointerInputWithSiblingFallthrough"

        properties["pointerInputEventHandler"] = pointerInputEventHandler

    }

}

Вот как это используется в DrawingSurface :

Box(

    modifier = Modifier

        .fillMaxSize()

        // Our custom modifier enables this gesture to coexist with the drawing input.

        .pointerInputWithSiblingFallthrough {

            detectDragGesturesAfterLongPress(

                onDragStart = { onStartDrag() },

                onDrag = { _, _ -> /* consume drag events */ },

                onDragEnd = { /* No action needed */ }

            )

        }

) 

// The Ink API's composable for live drawing sits here as a sibling.

InProgressStrokes(...)

Благодаря этому система корректно распознает одновременно и движения при рисовании, и жест перетаскивания с длительным нажатием. После начала перетаскивания мы создаем общий URI content:// с помощью FileProvider и передаем этот URI в систему перетаскивания, используя view.startDragAndDrop() . Это решение обеспечивает надежный и интуитивно понятный пользовательский интерфейс, демонстрируя, как преодолевать сложные конфликты жестов в многоуровневых пользовательских интерфейсах.

Построено в соответствии с современными архитектурными принципами.

Помимо конкретных API, Cahier демонстрирует важнейшие архитектурные шаблоны для создания высококачественных адаптивных приложений.

Уровень представления: Jetpack Compose и адаптивность.

Слой представления полностью построен с использованием Jetpack Compose. Как уже упоминалось, Cahier использует библиотеку material3-adaptive для адаптации пользовательского интерфейса. Управление состоянием осуществляется в строгом соответствии с шаблоном однонаправленного потока данных (UDF), при этом экземпляры ViewModel используются в качестве контейнеров данных, содержащих информацию о заметках и состоянии пользовательского интерфейса.

Слой данных: хранилища и комнаты

Для уровня данных Cahier использует интерфейс NoteRepository , чтобы абстрагировать все операции с данными. Такое проектное решение позволяет приложению легко переключаться между локальным источником данных (Room) и потенциальным удаленным бэкэндом в будущем. Поток данных для такого действия, как редактирование заметки, прост:

  1. Интерфейс Jetpack Compose запускает метод в ViewModel.
  2. ViewModel получает заметку из NoteRepository , обрабатывает логику и передает обновленную заметку обратно в репозиторий.
  3. NoteRepository сохраняет обновление в базу данных Room.

Комплексная поддержка ввода

Чтобы стать по-настоящему мощным инструментом повышения производительности, приложение должно безупречно обрабатывать различные методы ввода. Cahier разработано в соответствии с рекомендациями по вводу данных на больших экранах и поддерживает:

  • Стилус: интеграция с API Ink, защита от случайного касания ладонью, регистрация роли для заметок, ввод текста с помощью стилуса в текстовые поля и иммерсивный режим.
  • Клавиатура: Поддержка большинства распространенных сочетаний клавиш (например, Ctrl+клик, Meta+клик) и наглядная индикация фокуса клавиатуры.
  • Мышь и тачпад: поддержка щелчка правой кнопкой мыши и наведения курсора.

Поддержка расширенных возможностей взаимодействия с клавиатурой, мышью и трекпадом является ключевым направлением для дальнейших улучшений.

Начните сегодня!

Мы надеемся, что Cahier послужит стартовой площадкой для вашего следующего замечательного приложения. Мы создали его как всеобъемлющий ресурс с открытым исходным кодом, демонстрирующий, как сочетать адаптивный пользовательский интерфейс, мощные API, такие как Ink и роль заметок, и современную адаптивную архитектуру.

Готовы погрузиться в это с головой?

  • Изучите код : перейдите в наш репозиторий на GitHub , чтобы изучить кодовую базу Cahier и увидеть принципы проектирования в действии.
  • Создайте свой собственный стиль : используйте Cahier в качестве основы для ведения заметок, разметки документов или творческого применения.
  • Внесите свой вклад : Мы приветствуем ваши предложения! Помогите нам сделать Cahier еще лучшим ресурсом для сообщества разработчиков Android.

Ознакомьтесь с официальными руководствами для разработчиков и начните создавать свое приложение нового поколения для повышения производительности и творчества уже сегодня. Мы с нетерпением ждем, что вы создадите!

    Автор:

    Продолжить чтение