Advertisement
  1. Code
  2. Mobile Development
  3. Android Development

Создание Android Wear Watch Face

Scroll to top
Read Time: 13 min

() translation by (you can also view the original English article)

Одной из особенностей, которая делает Android такой особенной, является способность настраивать каждый аспект пользовательского интерфейса. Когда Android Wear впервые был запущен в Google I / O 2014, многие разработчики и пользователи узнали, что это не совсем верно для смарт-часов, поскольку официальный API для создания часовых лиц заметно отсутствовал. Учитывая, что способность создавать персонализированные ручные часы была одним из ключевых потребностей пользователей, неудивительно, что разработчики обнаружили способ создания собственных часов с недокументированным взломом в Android Wear.

К счастью, Google быстро сообщил всем, что официальный API уже на пути, а в декабре 2014 года API был наконец выпущен сообществу разработчиков. В этой статье вы узнаете о официальном интерфейсе Watch Faces для Android Wear и реализуете простое цифровое зеркало, которое вы сможете расширить для своих нужд. Реализация собственных часов может быть немного подробным, но вы можете найти пример приложения для этой статьи на GitHub.

1. Настройка вашего IDE

Первое, что вам нужно сделать, чтобы себя зарекомендовать, - это настроить ваш проект в Android Studio. При создании своего проекта выберите «Телефон и планшет» с минимальным SDK API 18, поскольку Android 4.3 является самой низкой версией операционной системы для поддержки входящих приложений Android Wear. Вам также нужно будет проверить коробку Wear с минимальным SDK API 21. Вы можете увидеть пример того, как должен выглядеть экран целевых Android-устройств.

Когда вы перейдете к экрану Add an Activity, выберите Add No Activity для обоих экранов.

После того, как вы нажмете «Готово», ваша среда проекта должна построить и иметь модуль для мобильных устройств, а другой - для ношения.

2. Создание службы Wear Watch

Android Wear реализует сторожевые поверхности с помощью WatchFaceService. В этой статье вы создадите расширение класса CanvasWatchFaceService, которое представляет собой реализацию WatchFaceService, которая также предоставляет Canvas для вытягивания вашего часового лица. Начните с создания нового класса Java под модулем износа в Android Studio, который расширяет CanvasWatchFaceService.

1
public class WatchFaceService extends CanvasWatchFaceService

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

1
private class WatchFaceEngine extends Engine

Когда ваш код-заглушка для WatchFaceEngine включен, вернитесь к внешнему классу и переопределите метод onCreateEngine, чтобы вернуть новый внутренний класс. Это свяжет вашу службу собственных часов с кодом, который будет управлять дисплеем.

1
@Override
2
public Engine onCreateEngine() {
3
    return new WatchFaceEngine();
4
}

После того, как у вас есть услуга «Голые кости», вы можете перейти к общим задачам по уборке обновленных файлов AndroidManifest, чтобы ваш сервис был доступен для Android Wear. Имейте в виду, что ваш текущий код еще ничего не сделает. Мы вернемся к этому классу и выставим двигатель после выполнения некоторых работ по ведению проекта.

3. Обновление файлов AndroidManifest

Откройте файл AndroidManifest.xml в модуле износа. В верхней части вы должны увидеть строку, которая гласит:

1
<uses-feature android:name="android.hardware.type.watch" />

Ниже этой строки нам нужно добавить два требуемых разрешения для экрана часов. Вот эти требования:

1
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
2
<uses-permission android:name="android.permission.WAKE_LOCK" />

Как только ваши разрешения будут установлены, вам нужно будет добавить узел для вашей службы в узле application с разрешением BIND_WALLPAPER, несколькими наборами метаданных, содержащих ссылочные изображения вашего часового пояса для экрана выбора (в этом примере мы просто используя значок пусковой установки)чтобы система узнала, что ваш сервис предназначен для отображения экрана часов.

1
<service android:name=".service.CustomWatchFaceService"
2
    android:label="Tuts+ Wear Watch Face"
3
    android:permission="android.permission.BIND_WALLPAPER">
4
5
    <meta-data
6
        android:name="android.service.wallpaper"
7
        android:resource="@xml/watch_face" />
8
    <meta-data
9
        android:name="com.google.android.wearable.watchface.preview"
10
        android:resource="@mipmap/ic_launcher" />
11
    <meta-data
12
        android:name="com.google.android.wearable.watchface.preview_circular"
13
        android:resource="@mipmap/ic_launcher" />
14
    <intent-filter>
15
        <action android:name="android.service.wallpaper.WallpaperService" />
16
        <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
17
    </intent-filter>
18
19
</service>

После того, как ваш манифест будет завершен, вам нужно будет открыть файл AndroidManifest.xml в мобильном модуле и добавить два разрешения, которые мы использовали в модуле износа для PROVIDE_BACKGROUND и WAKE_LOCK,потому что Android Wear требует, чтобы как изнашиваемые, так и мобильные модули запрашивали те же разрешения для износа APK, которые устанавливались на часы пользователя. Как только оба файла манифеста заполнены, вы можете вернуться в CustomWatchFaceService.java, чтобы начать внедрение механизма.

4. Запустите ваш двигатель

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

Шаг 1: Определение необходимых значений и переменных

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

1
//Member variables

2
private Typeface WATCH_TEXT_TYPEFACE = Typeface.create( Typeface.SERIF, Typeface.NORMAL );
3
4
private static final int MSG_UPDATE_TIME_ID = 42;
5
private long mUpdateRateMs = 1000;
6
7
private Time mDisplayTime;
8
9
private Paint mBackgroundColorPaint;
10
private Paint mTextColorPaint;
11
12
private boolean mHasTimeZoneReceiverBeenRegistered = false;
13
private boolean mIsInMuteMode;
14
private boolean mIsLowBitAmbient;
15
16
private float mXOffset;
17
private float mYOffset;
18
19
private int mBackgroundColor = Color.parseColor( "black" );
20
private int mTextColor = Color.parseColor( "red" );

Как вы можете видеть, мы определяем TypeFace, который мы будем использовать для нашего цифрового текстового текста, а также цвет фона и цвет текста экрана часов. Объект Time используется, как вы догадались, отслеживая текущее время устройства. mUpdateRateMs используется для управления таймером, который нам потребуется реализовать, чтобы обновлять нашу часовую панель каждую секунду (следовательно, значение 1000 миллисекунд для mUpdateRateMs), поскольку стандартный WatchFaceService отслеживает время только с шагом в одну минуту. mXOffset и mYOffset определяются после того, как двигатель знает физическую форму часов, чтобы можно было нарисовать наше часовое лицо, не находясь слишком близко к верхнему или левому краю экрана или обрезав закругленный угол. Три булевых значения используются для отслеживания различных состояний устройства и приложения.

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

1
final BroadcastReceiver mTimeZoneBroadcastReceiver = new BroadcastReceiver() {
2
    @Override
3
    public void onReceive(Context context, Intent intent) {
4
        mDisplayTime.clear( intent.getStringExtra( "time-zone" ) );
5
        mDisplayTime.setToNow();
6
    }
7
};

После того, как ваш приемник будет определен, конечным объектом, который вам нужно создать в верхней части вашего движка, является Handler, который заботится о том, чтобы обновлять часы каждую секунду. Это необходимо из-за ограничений WatchFaceService, о которых говорилось выше. Если вы хотите,чтобы время обновлялось каждую минуту, то можете смело игнорировать этот раздел.

1
private final Handler mTimeHandler = new Handler() {
2
    @Override
3
    public void handleMessage(Message msg) {
4
        switch( msg.what ) {
5
            case MSG_UPDATE_TIME_ID: {
6
                invalidate();
7
                if( isVisible() && !isInAmbientMode() ) {
8
                    long currentTimeMillis = System.currentTimeMillis();
9
                    long delay = mUpdateRateMs - ( currentTimeMillis % mUpdateRateMs );
10
                    mTimeHandler.sendEmptyMessageDelayed( MSG_UPDATE_TIME_ID, delay );
11
                }
12
                break;
13
            }
14
        }
15
    }
16
};

Реализация Handler довольно проста. Сначала он проверяет идентификатор сообщения. Если соответствует MSG_UPDATE_TIME_ID, он продолжает отменять текущее представление для перерисовки. После того, как представление было недействительным, Handler проверяет, отображается ли экран, а не в потухшем режиме. Если он виден, он отправляет запрос повторения через секунду. Причина, по которой мы повторяем действие в Handler, когда лицо часов видно, а не в окружающем режиме, заключается в том, что он может быть немного интенсивным, чтобы обновлять каждую секунду. Если пользователь не смотрит на экран, мы просто возвращаемся к реализации WatchFaceService, которая обновляется каждую минуту.

Шаг 2: Инициализация двигателя

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

1
@Override
2
public void onCreate(SurfaceHolder holder) {
3
    super.onCreate(holder);
4
5
    setWatchFaceStyle( new WatchFaceStyle.Builder( CustomWatchFaceService.this )
6
                    .setBackgroundVisibility( WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE )
7
                    .setCardPeekMode( WatchFaceStyle.PEEK_MODE_VARIABLE )
8
                    .setShowSystemUiTime( false )
9
                    .build()
10
    );
11
12
    mDisplayTime = new Time();
13
14
    initBackground();
15
    initDisplayText();
16
}

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

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

После того, как ваш WatchFaceStyle установлен, вы можете инициализировать mDisplayTime как новый объект Time.

initBackground и initDisplayText выделяют два объекта Paint, которые вы определили в верхней части движка. Затем фон и текст имеют свой набор цветов, а текст имеет свой шрифт и размер шрифта, а также включает сглаживание.

1
private void initBackground() {
2
    mBackgroundColorPaint = new Paint();
3
    mBackgroundColorPaint.setColor( mBackgroundColor );
4
}
5
6
private void initDisplayText() {
7
    mTextColorPaint = new Paint();
8
    mTextColorPaint.setColor( mTextColor );
9
    mTextColorPaint.setTypeface( WATCH_TEXT_TYPEFACE );
10
    mTextColorPaint.setAntiAlias( true );
11
    mTextColorPaint.setTextSize( getResources().getDimension( R.dimen.text_size ) );
12
}

Шаг 3: Управление состоянием устройства

Затем вам необходимо реализовать различные методы из класса Engine, вызванные изменениями состояния устройства. Мы начнем с рассмотрения метода onVisibilityChanged, который вызывается, когда пользователь скрывает или показывает лицо часов.

1
@Override
2
public void onVisibilityChanged( boolean visible ) {
3
    super.onVisibilityChanged(visible);
4
5
    if( visible ) {
6
        if( !mHasTimeZoneReceiverBeenRegistered ) {
7
8
            IntentFilter filter = new IntentFilter( Intent.ACTION_TIMEZONE_CHANGED );
9
            CustomWatchFaceService.this.registerReceiver( mTimeZoneBroadcastReceiver, filter );
10
11
            mHasTimeZoneReceiverBeenRegistered = true;
12
        }
13
14
        mDisplayTime.clear( TimeZone.getDefault().getID() );
15
        mDisplayTime.setToNow();
16
    } else {
17
        if( mHasTimeZoneReceiverBeenRegistered ) {
18
            CustomWatchFaceService.this.unregisterReceiver( mTimeZoneBroadcastReceiver );
19
            mHasTimeZoneReceiverBeenRegistered = false;
20
        }
21
    }
22
23
    updateTimer();
24
}

Когда этот метод вызывается, он проверяет, видно ли лицо часов или нет. Если лицо часового пояса видно, оно проверяет, зарегистрирован ли BroadcastReceiver, который вы определили в верхней части Engine. Если это не так, метод создает IntentFilter для ACTION_TIMEZONE_CHANGED и регистрирует BroadcastReceiver для его прослушивания.

Если лицо часового пояса не видно, этот метод проверяет, может ли BroadcastReceiver быть незарегистрированным. Как только BroadcastReceiver будет обработан, updateTimer вызывается, чтобы вызвать недействительность лица часов и перерисовать лицо часов. updateTimer останавливает любые действия Handler, которые ожидаются, и проверяет, нужно ли отправлять другой.

1
private void updateTimer() {
2
    mTimeHandler.removeMessages( MSG_UPDATE_TIME_ID );
3
    if( isVisible() && !isInAmbientMode() ) {
4
        mTimeHandler.sendEmptyMessage( MSG_UPDATE_TIME_ID );
5
    }
6
}

Шаг 4: Сотрудничество с носимым оборудованием

Когда ваша служба связана с Android Wear, вызывается onApplyWindowInsets. Это используется, чтобы определить, закруглено или квадратично устройство, на котором работает ваше лицо. Это позволяет вам изменить ваше лицо для часов, чтобы оно соответствовало аппаратным средствам.

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

1
@Override
2
public void onApplyWindowInsets(WindowInsets insets) {
3
    super.onApplyWindowInsets(insets);
4
5
    mYOffset = getResources().getDimension( R.dimen.y_offset );
6
7
    if( insets.isRound() ) {
8
        mXOffset = getResources().getDimension( R.dimen.x_offset_round );
9
    } else {
10
        mXOffset = getResources().getDimension( R.dimen.x_offset_square );
11
    }
12
}

Следующий метод, который вам нужно переопределить, - onPropertiesChanged. Этот метод вызывается, когда определяются аппаратные свойства устройства Wear, например, если устройство поддерживает защиту от записи или режим с низким уровнем бит.

В этом методе вы проверяете, применяются ли эти атрибуты к устройству, работающему с вашими часами, и сохраняют его в переменной-члене, определенной в верхней части вашего Engine.

1
@Override
2
public void onPropertiesChanged( Bundle properties ) {
3
    super.onPropertiesChanged( properties );
4
5
    if( properties.getBoolean( PROPERTY_BURN_IN_PROTECTION, false ) ) {
6
        mIsLowBitAmbient = properties.getBoolean( PROPERTY_LOW_BIT_AMBIENT, false );
7
    }
8
}

Шаг 5: Сохранение батареи в режимах Ambient и Muted Modes

После того, как вы обработаете начальные состояния устройства, вы захотите реализовать onAmbientModeChanged и onInterruptionFilterChanged. Как следует из названия, onAmbientModeChanged вызывается, когда устройство перемещается в или из внешнего режима.

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

1
@Override
2
public void onAmbientModeChanged(boolean inAmbientMode) {
3
    super.onAmbientModeChanged(inAmbientMode);
4
5
    if( inAmbientMode ) {
6
        mTextColorPaint.setColor( Color.parseColor( "white" ) );
7
    } else {
8
        mTextColorPaint.setColor( Color.parseColor( "red" ) );
9
    }
10
11
    if( mIsLowBitAmbient ) {
12
        mTextColorPaint.setAntiAlias( !inAmbientMode );
13
    }
14
15
    invalidate();
16
    updateTimer();
17
}


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

1
@Override
2
public void onInterruptionFilterChanged(int interruptionFilter) {
3
    super.onInterruptionFilterChanged(interruptionFilter);
4
5
    boolean isDeviceMuted = ( interruptionFilter == android.support.wearable.watchface.WatchFaceService.INTERRUPTION_FILTER_NONE );
6
    if( isDeviceMuted ) {
7
        mUpdateRateMs = TimeUnit.MINUTES.toMillis( 1 );
8
    } else {
9
        mUpdateRateMs = DEFAULT_UPDATE_RATE_MS;
10
    }
11
12
    if( mIsInMuteMode != isDeviceMuted ) {
13
        mIsInMuteMode = isDeviceMuted;
14
        int alpha = ( isDeviceMuted ) ? 100 : 255;
15
        mTextColorPaint.setAlpha( alpha );
16
        invalidate();
17
        updateTimer();
18
    }
19
}

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

1
@Override
2
public void onTimeTick() {
3
    super.onTimeTick();
4
5
    invalidate();
6
}

Шаг 6: Рисование часов

Когда все ваши непредвиденные обстоятельства будут покрыты, пришло время, показаться экрану часов. CanvasWatchFaceService использует стандартный объект Canvas, поэтому вам нужно будет добавить onDraw в свой Engine и вручную вытащить экран ваших часов.

В этом уроке мы просто собираемся нарисовать текстовое представление времени, хотя вы можете изменить свой onDraw, чтобы легко поддерживать аналоговый экран часов. В этом методе вам нужно будет убедиться, что вы показываете правильное время, обновляя свой объект Time, а затем можете начать применять свой экран для часов.

1
@Override
2
public void onDraw(Canvas canvas, Rect bounds) {
3
    super.onDraw(canvas, bounds);
4
5
    mDisplayTime.setToNow();
6
7
    drawBackground( canvas, bounds );
8
    drawTimeText( canvas );
9
}

drawBackground применяет сплошной цвет к фону устройства Wear.

1
private void drawBackground( Canvas canvas, Rect bounds ) {
2
    canvas.drawRect( 0, 0, bounds.width(), bounds.height(), mBackgroundColorPaint );
3
}

drawTimeText, однако, создает текст времени, который будет отображаться с помощью нескольких вспомогательных методов, а затем применяет его к холсту в точках смещения x и y, которые вы определили в onApplyWindowInsets.

1
private void drawTimeText( Canvas canvas ) {
2
    String timeText = getHourString() + ":" + String.format( "%02d", mDisplayTime.minute );
3
    if( isInAmbientMode() || mIsInMuteMode ) {
4
        timeText += ( mDisplayTime.hour < 12 ) ? "AM" : "PM";
5
    } else {
6
        timeText += String.format( ":%02d", mDisplayTime.second);
7
    }
8
    canvas.drawText( timeText, mXOffset, mYOffset, mTextColorPaint );
9
}
10
11
private String getHourString() {
12
    if( mDisplayTime.hour % 12 == 0 )
13
        return "12";
14
    else if( mDisplayTime.hour <= 12 )
15
        return String.valueOf( mDisplayTime.hour );
16
    else
17
        return String.valueOf( mDisplayTime.hour - 12 );
18
}

Заключение

После того, как вы внедрили методы рисования своего экрана, вы должны быть настроены на базовые знания, необходимые для выхода и создания собственных часов. Приятная вещь о Android Wear заключается в том, что это только малая часть того, что возможно.

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

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

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.