Сила Android – в его открытости. Исходный код любого полюбившегося приложения (если оно не одно из Google Apps) или компонента можно не только изучить, но и прикрутить к своему проекту на радость себе и пользователям. Причем, его дизайн и поведение будут знакомыми и привычными для пользователей. В этой статье я покажу, как это сделать. Мы выберем компонент, который хотим позаимствовать, найдем его в исходниках, изучим его, добавим в свою библиотеку и подключим к нашему приложению.
Для начала – выберем то, что так сильно привлекает наш взгляд в каком-то из стандартных Android-приложений. В моем случае это круговой таймер в приложении часов. Выглядит это так:
Вот только хочу я это использовать не как таймер, а как индикатор прогресса.
Следующий этап – найти исходный код именно этого компонента среди множества других в исходниках Android-приложений. Все эти исходники можно найти по адресу http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android-apps/
Выбираем последнюю версию (на момент написания статьи это 4.2.2_r1), идем в «com/android» и ищем нужное приложение. В моем случае это «deskclock». Пробегаем глазами по списку классов и… нам повезло – есть класс с говорящим названием CircleTimerView. Впрочем, вместо описания класса – «TODO: Insert description here. (generated by isaackatz)» Этот класс наследует View – именно он будет ключевым классом нашей библиотеки.
Этот абзац можно пропустить, если компонент который вы решили добавить в приложение небольшой. Например, как в моем случае. Но для примера все же сделаем это через библиотеку. Запускаем любимую IDE, создаем новый проект, отметив «Mark project as a library».
Копируем найденный класс в библиотеку (или сразу в проект). В классе много критичных ошибок? И правильно! Мы скопировали только класс, который будет являться точкой соприкосновения нашего приложения и всех классов, добавленных в нашу библиотеку из исходников стандартного приложения.
Смотрим – от чего зависел наш класс до того, как мы выдернули его из привычной среды обитания. В моем случае это классы Utils, Stopwatches и коробка констант R.
Копируем класс Utils и видим множество зависимостей от других классов уже в нем. Теперь можно либо подключить все нужные ему классы, а затем все классы, что нужны этим… Но лучше просто оставить от утилитарного класса только те методы, которые нам нужны. Выкидываем все, кроме getTimeNow() и calculateRadiusOffset(), убираем лишние импорты – отлично.
Из класса Stopwatches используется только три константы и, забегая наперед, скажу, что мне они не понадобятся, как и метод, который их использует.
Из коробки R мы берем данные о отступах и цвете и здесь уже просто скопировать класс не получится – он генерируется автоматически, основываясь на ресурсах. Придется добавить ресурсы. Найти их можно в исходниках по адресу «packages/apps», выбираем препарируемое приложение, заходим в папку «res/values», копируем файлы «colors.xml» и «dimens.xml» в аналогичную папку нашего проекта-библиотеки.
Поправим импорты в главном классе и библиотека готова. Почти. Два добавленных файла следует почистить, оставив там только нужные данные, а также добавить другие вариант этих файлов для разных устройств. Для осуществления последнего нужно найти в исходниках все файлы с такими именами в папках с названиями «values-*» и скопировать в библиотеку. Таким образом, например, на планшете отступы будут больше, чем на телефоне.
Теперь самое сладкое – увидеть результат работы в действии. Открываем проект нашего приложение, которому так нужен этот компонент, подключаем созданную библиотеку, кидаем на разметку элемент com.android.deskclock.CircleTimerView и вот что мы видим при запуске приложения:
Отлично, это белый круг, почти то, чего мы добивались. Осталось только привести его в действие – нужен код, который заставит наш круг быть классным.
Опять лезем в исходники, ищем активность или фрагмент, который использует компонент, который мы одолжили. В моем случае это StopwatchFragment. После краткого изучения этого фрагмента стает понятно, что для простой демонстрации работы элемента достаточно написать три строки:
Запускаем приложение еще раз и видим желаемый (по крайней мере, мною) результат:
Теперь все как и должно быть, а значит пришло время сказать спасибо команде разработчиков Android. Речь о добавлении текста лицензионного соглашения Apache License version 2.0 в свое приложение. Например, отображать его по нажатию кнопки в настройках.
Да, все уже работает как надо, но мы почти не смотрели код, который взяли. Конечно, этого можно и не делать – исходники от команды разработчиков Android и чертик из компонента вряд ли выскочит, но, изучив код, его можно улучить, да и самому научиться полезному.
В классе из моего примера все просто: вся отрисовка в методе onDraw(Canvas canvas), для вызова перерисовки — postInvalidate(). Для того, чтобы анимация круга была непрерывной, invalidate() вызывается прямо в onDraw(…).
UPDATE Сейчас испытывал компонент в полевых условиях — опасения по поводу вызова invalidate() из onDraw(…) подтвердились: выполнив пересчет и отрисовку, компонент сразу же принимается повторить это действие и так продолжается непрерывно.
Отрисовка, конечно, получается плавная, но при этом употребляется 700 МГц процессора, телефон быстро расходует энергию и тормозит даже шторка уведомлений. А еще это, пожалуй, первый раз, когда мой Galaxy Nexus начал ощутимо нагреваться.
Исправить это достаточно просто: далеко не всегда нам нужно обновлять таймер непрерывно. Здесь нужно учитывать то, сколько времени должно пройти для того, чтобы изменение было визуально заметно. Например, мы запускаем в этом компоненте отсчет 5 секунд на планшете с высоким разрешением, тогда есть смысл делать непрерывное обновление. Или это таймер на два часа на телефоне с низким разрешением. Мы можем выполнять хоть 10 перерисовок в секунду, но визуально они заметны не будут.
При необходимости, можно переписать компонент под свои нужды (например, под индикатор прогресс) или унаследовать его и переопределить необходимые методы.
Нам повезло – компонент из примера не использовал ничего из того, чего нет в API уровня 7 (Android 2.1), иначе пришлось бы решать эту неприятную проблему. Об этом – в следующий раз.
Если кто-то дочитал эту статью до конца, вспомнил о красивом компоненте из любимого стандартного Android-приложения и добавил его в свой проект – мне приятно, что мой труд не пропал зря.
Выбираем компонент
Для начала – выберем то, что так сильно привлекает наш взгляд в каком-то из стандартных Android-приложений. В моем случае это круговой таймер в приложении часов. Выглядит это так:
Вот только хочу я это использовать не как таймер, а как индикатор прогресса.
Находим исходники
Следующий этап – найти исходный код именно этого компонента среди множества других в исходниках Android-приложений. Все эти исходники можно найти по адресу http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android-apps/
Выбираем последнюю версию (на момент написания статьи это 4.2.2_r1), идем в «com/android» и ищем нужное приложение. В моем случае это «deskclock». Пробегаем глазами по списку классов и… нам повезло – есть класс с говорящим названием CircleTimerView. Впрочем, вместо описания класса – «TODO: Insert description here. (generated by isaackatz)» Этот класс наследует View – именно он будет ключевым классом нашей библиотеки.
Создаем библиотеку
Этот абзац можно пропустить, если компонент который вы решили добавить в приложение небольшой. Например, как в моем случае. Но для примера все же сделаем это через библиотеку. Запускаем любимую IDE, создаем новый проект, отметив «Mark project as a library».
Копируем найденный класс в библиотеку (или сразу в проект). В классе много критичных ошибок? И правильно! Мы скопировали только класс, который будет являться точкой соприкосновения нашего приложения и всех классов, добавленных в нашу библиотеку из исходников стандартного приложения.
Смотрим – от чего зависел наш класс до того, как мы выдернули его из привычной среды обитания. В моем случае это классы Utils, Stopwatches и коробка констант R.
Копируем класс Utils и видим множество зависимостей от других классов уже в нем. Теперь можно либо подключить все нужные ему классы, а затем все классы, что нужны этим… Но лучше просто оставить от утилитарного класса только те методы, которые нам нужны. Выкидываем все, кроме getTimeNow() и calculateRadiusOffset(), убираем лишние импорты – отлично.
Из класса Stopwatches используется только три константы и, забегая наперед, скажу, что мне они не понадобятся, как и метод, который их использует.
Из коробки R мы берем данные о отступах и цвете и здесь уже просто скопировать класс не получится – он генерируется автоматически, основываясь на ресурсах. Придется добавить ресурсы. Найти их можно в исходниках по адресу «packages/apps», выбираем препарируемое приложение, заходим в папку «res/values», копируем файлы «colors.xml» и «dimens.xml» в аналогичную папку нашего проекта-библиотеки.
Поправим импорты в главном классе и библиотека готова. Почти. Два добавленных файла следует почистить, оставив там только нужные данные, а также добавить другие вариант этих файлов для разных устройств. Для осуществления последнего нужно найти в исходниках все файлы с такими именами в папках с названиями «values-*» и скопировать в библиотеку. Таким образом, например, на планшете отступы будут больше, чем на телефоне.
Подключаем библиотеку к нашему приложению
Теперь самое сладкое – увидеть результат работы в действии. Открываем проект нашего приложение, которому так нужен этот компонент, подключаем созданную библиотеку, кидаем на разметку элемент com.android.deskclock.CircleTimerView и вот что мы видим при запуске приложения:
Отлично, это белый круг, почти то, чего мы добивались. Осталось только привести его в действие – нужен код, который заставит наш круг быть классным.
Опять лезем в исходники, ищем активность или фрагмент, который использует компонент, который мы одолжили. В моем случае это StopwatchFragment. После краткого изучения этого фрагмента стает понятно, что для простой демонстрации работы элемента достаточно написать три строки:
final CircleTimerView circleTimerView1 =
(CircleTimerView) findViewById(R.id.circleTimerView1);
circleTimerView1.setIntervalTime(10000);
circleTimerView1.startIntervalAnimation();
Запускаем приложение еще раз и видим желаемый (по крайней мере, мною) результат:
Теперь все как и должно быть, а значит пришло время сказать спасибо команде разработчиков Android. Речь о добавлении текста лицензионного соглашения Apache License version 2.0 в свое приложение. Например, отображать его по нажатию кнопки в настройках.
А что внутри?
Да, все уже работает как надо, но мы почти не смотрели код, который взяли. Конечно, этого можно и не делать – исходники от команды разработчиков Android и чертик из компонента вряд ли выскочит, но, изучив код, его можно улучить, да и самому научиться полезному.
В классе из моего примера все просто: вся отрисовка в методе onDraw(Canvas canvas), для вызова перерисовки — postInvalidate(). Для того, чтобы анимация круга была непрерывной, invalidate() вызывается прямо в onDraw(…).
UPDATE Сейчас испытывал компонент в полевых условиях — опасения по поводу вызова invalidate() из onDraw(…) подтвердились: выполнив пересчет и отрисовку, компонент сразу же принимается повторить это действие и так продолжается непрерывно.
Отрисовка, конечно, получается плавная, но при этом употребляется 700 МГц процессора, телефон быстро расходует энергию и тормозит даже шторка уведомлений. А еще это, пожалуй, первый раз, когда мой Galaxy Nexus начал ощутимо нагреваться.
Исправить это достаточно просто: далеко не всегда нам нужно обновлять таймер непрерывно. Здесь нужно учитывать то, сколько времени должно пройти для того, чтобы изменение было визуально заметно. Например, мы запускаем в этом компоненте отсчет 5 секунд на планшете с высоким разрешением, тогда есть смысл делать непрерывное обновление. Или это таймер на два часа на телефоне с низким разрешением. Мы можем выполнять хоть 10 перерисовок в секунду, но визуально они заметны не будут.
При необходимости, можно переписать компонент под свои нужды (например, под индикатор прогресс) или унаследовать его и переопределить необходимые методы.
Что дальше?
Нам повезло – компонент из примера не использовал ничего из того, чего нет в API уровня 7 (Android 2.1), иначе пришлось бы решать эту неприятную проблему. Об этом – в следующий раз.
Всем спасибо
Если кто-то дочитал эту статью до конца, вспомнил о красивом компоненте из любимого стандартного Android-приложения и добавил его в свой проект – мне приятно, что мой труд не пропал зря.