В этом уроке я покажу вам, как использовать Dolby Audio API для улучшения звука в ваших приложениях Android. Чтобы показать вам, как использовать Dolby Audio API, мы создадим простую, но веселую игру-головоломку.
Вступление
На сегодняшнем многолюдном рынке мобильной связи важно сделать ваши приложения максимально привлекательными. Повышение качества звука приложения может быть столь же привлекательным для пользователя, как и потрясающий пользовательский интерфейс.
Звук, создаваемый приложением, является формой взаимодействия между пользователем и приложением, которая слишком часто упускается из виду. Тем не менее, это означает, что предоставление отличного слухового опыта может помочь вашему приложению выделиться из толпы.
Dolby Digital Plus — это продвинутый аудиокодек, который можно использовать в мобильных приложениях с помощью простого в использовании аудио API Dolby. Dolby сделал свой API доступным для нескольких платформ, включая Android и Kindle Fire . В этом уроке мы рассмотрим реализацию API для Android.
Dolby Audio API для Android совместим с широким спектром устройств Android. Это означает, что ваши приложения и игры для Android могут наслаждаться высококачественным, захватывающим звуком всего за несколько минут работы, интегрируя Dolby Audio API. Давайте рассмотрим, что нужно для интеграции API, создав игру-головоломку.
1. Обзор
В первой части этого урока я покажу вам, как создать веселую головоломку. Поскольку основное внимание в этом руководстве уделяется интеграции Dolby Audio API, я не буду вдаваться в подробности и надеюсь, что вы уже знакомы с основами разработки под Android. Во второй части этой статьи мы рассмотрим интеграцию Dolby Audio API в приложение для Android.
Мы собираемся сделать традиционную головоломку для Android. Цель игры состоит в том, чтобы вставить часть головоломки в пустое место на доске, чтобы перемещать части головоломки. Игрок должен повторить этот процесс, пока каждый кусочек головоломки не окажется в правильном порядке. Как вы можете видеть на скриншоте ниже, я добавил число к каждой части головоломки. Это облегчит отслеживание фрагментов головоломки и их порядок.
Чтобы сделать игру более привлекательной, я покажу вам, как использовать пользовательские изображения, а также как делать фотографии для создания собственных уникальных головоломок. Мы также добавим кнопку перемешивания, чтобы переставить части головоломки, чтобы начать новую игру.
2. Начало работы
Шаг 1
Не важно, какую IDE вы используете, но для этого урока я буду использовать JetBrains IntelliJ Idea . Откройте нужную среду IDE и создайте новый проект для своего приложения Android. Обязательно создайте основной класс Activity
и макет XML
.
Шаг 2
Давайте сначала настроим файл манифеста приложения. В узле application
файла манифеста установите для hardwareAccelerated
значение true
. Это повысит производительность рендеринга вашего приложения даже для 2D-игр, подобных той, которую мы собираемся создать.
1
|
android:hardwareAccelerated=»true»
|
На следующем шаге мы указываем размеры экрана, которые будет поддерживать наше приложение. Для игр я обычно ориентируюсь на устройства с большими экранами, но этот выбор полностью зависит от вас.
1
2
3
4
5
6
|
<supports-screens
android:largeScreens=»true»
android:anyDensity=»true»
android:normalScreens=»true»
android:smallScreens=»false»
android:xlargeScreens=»true»/>
|
В узле activity
файла манифеста добавьте узел с именем configChanges
и установите для его значения orientation
как показано ниже. Вы можете найти больше информации об этой настройке на веб-сайте разработчика .
1
|
android:configChanges=»orientation»
|
Прежде чем двигаться дальше, добавьте два узла uses-permission
на использование, чтобы включить вибрацию и доступ для записи в нашей игре. Вставьте следующий фрагмент перед узлом application
в файле манифеста.
1
2
|
<uses-permission android:name=»android.permission.VIBRATE» />
<uses-permission android:name=»android.permission.WRITE_EXTERNAL_STORAGE» />
|
Шаг 3
Давайте также добавим ресурсы, которые мы будем использовать позже в этом руководстве. Начните с добавления изображения, которое вы хотите использовать для головоломки. Добавьте его в папку drawable
вашего проекта. Я решил добавить изображение в папку drawable-hdpi
моего проекта.
И последнее, но не менее важное: добавьте звуковые файлы, которые вы хотите использовать в своей игре. В папке res
вашего проекта создайте новый каталог с именем raw
и добавьте звуковые файлы в эту папку. Для целей этого урока я добавил два аудиофайла. Первый звук воспроизводится, когда игрок перемещает кусок головоломки, в то время как второй звук воспроизводится по окончании игры, то есть, когда игрок завершает головоломку. Оба звука доступны в SoundBible . Первый звук лицензирован по лицензии Creative Commons Attribution 3.0 и записан Майком Кенигом.
3. Создание мозга игры
Как я уже упоминал ранее, я не буду подробно описывать процесс создания игры, поскольку в этом руководстве рассматривается интеграция Dolby Audio API. На следующих шагах я проведу вас через шаги, которые необходимо предпринять, чтобы создать игру-головоломку.
Мы начнем с создания нового класса SlidePuzzle
, который станет мозгом игры. Каждый ход, сделанный в головоломке, обрабатывается и отслеживается экземпляром этого класса с использованием простой математики.
Это важная часть игры, так как она определяет, какие плитки можно перемещать и в каком направлении. Класс также уведомит нас, когда загадка будет завершена.
1
2
3
4
5
|
package com.dolby.DolbyPuzzle;
public class SlidePuzzle {
}
|
Начнем с объявления количества переменных, которые нам понадобятся чуть позже. Взгляните на следующий фрагмент кода, в котором я объявляю переменные для четырех возможных направлений, в которых кусочки головоломки могут двигаться, два массива целых чисел для горизонтального и вертикального направлений и массив для плиток головоломки. Мы также объявляем и создаем экземпляр класса Random
, который мы будем использовать позже в этом руководстве.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
public static final int DIRECTION_LEFT = 0;
public static final int DIRECTION_UP = 1;
public static final int DIRECTION_RIGHT = 2;
public static final int DIRECTION_DOWN = 3;
public static final int[] DIRECTION_X = {-1, 0, +1, 0};
public static final int[] DIRECTION_Y = {0, -1, 0, +1};
private int[] tiles;
private int handleLocation;
private Random random = new Random();
private int width;
private int height;
|
Следующим шагом является создание метода init
для класса SlidePuzzle
. Метод init
принимает два аргумента, которые определяют width
и height
объекта SlidePuzzle
. Используя переменные экземпляра width
и height
, мы создаем массив tiles
и устанавливаем handleLocation
как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
|
public void init(int width, int height) {
this.width = width;
this.height = height;
tiles = new int[width * height];
for(int i = 0; i < tiles.length; i++)
{
tiles[i] = i;
}
handleLocation = tiles.length — 1;
}
|
Класс SlidePuzzle
также нуждается в методе установки и получения свойства tiles
. Их реализации не так сложны, как вы можете видеть ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public void setTiles(int[] tiles) {
this.tiles = tiles;
for(int i = 0; i < tiles.length; i++)
{
if(tiles[i] == tiles.length — 1)
{
handleLocation = i;
break;
}
}
}
public int[] getTiles() {
return tiles;
}
|
В дополнение к методам доступа к свойству tiles
, я также создал несколько удобных методов, которые пригодятся позже в этом уроке. getRowAt
методы getColumnAt
и getRowAt
возвращают столбец и строку определенного местоположения в головоломке.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public int getColumnAt(int location) {
return location % width;
}
public int getRowAt(int location) {
return location / width;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
|
Метод distance
, еще один вспомогательный метод, который мы будем использовать через несколько минут, вычисляет расстояние между tiles
с использованием простой математики и массива tiles
.
01
02
03
04
05
06
07
08
09
10
|
public int distance() {
int dist = 0;
for(int i = 0; i < tiles.length; i++)
{
dist += Math.abs(i — tiles[i]);
}
return dist;
}
|
Следующий метод — getPossibleMoves
, который мы будем использовать для определения возможных позиций, на которые могут перемещаться части головоломки. На следующем скриншоте четыре части головоломки, которые можно переместить в пустое место на доске. Фишки, которые игрок может переместить, это 5
, 2
, 8
и 4
. Разве я не говорил вам, что цифры пригодятся?
getPossibleMoves
реализация getPossibleMoves
может показаться сложной, но это не более чем базовая математика.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
public int getPossibleMoves() {
int x = getColumnAt(handleLocation);
int y = getRowAt(handleLocation);
boolean left = x > 0;
boolean right = x < width — 1;
boolean up = y > 0;
boolean down = y < height — 1;
return (left ? 1 << DIRECTION_LEFT : 0) |
(right ? 1 << DIRECTION_RIGHT : 0) |
(up ? 1 << DIRECTION_UP : 0) |
(down ? 1 << DIRECTION_DOWN : 0);
}
|
В методе pickRandomMove
мы используем объект Random
мы создали ранее. Как видно из pickRandomMove
метод pickRandomMove
перемещает случайную часть головоломки. Random
объект используется для генерации случайного целого числа, которое возвращается методом pickRandomMove
. Метод также принимает один аргумент, целое число, которое является местоположением, которое мы игнорируем, то есть пустое место на доске пазлов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
private int pickRandomMove(int exclude) {
List<Integer> moves = new ArrayList<Integer>(4);
int possibleMoves = getPossibleMoves() & ~exclude;
if((possibleMoves & (1 << DIRECTION_LEFT)) > 0)
{
moves.add(DIRECTION_LEFT);
}
if((possibleMoves & (1 << DIRECTION_UP)) > 0)
{
moves.add(DIRECTION_UP);
}
if((possibleMoves & (1 << DIRECTION_RIGHT)) > 0)
{
moves.add(DIRECTION_RIGHT);
}
if((possibleMoves & (1 << DIRECTION_DOWN)) > 0)
{
moves.add(DIRECTION_DOWN);
}
return moves.get(random.nextInt(moves.size()));
}
|
Метод invertMove
, который используется чуть позже в методе shuffle
, инвертирует целое число, используемое для выбранного направления.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private int invertMove(int move) {
if(move == 0)
{
return 0;
}
if(move == 1 << DIRECTION_LEFT)
{
return 1 << DIRECTION_RIGHT;
}
if(move == 1 << DIRECTION_UP)
{
return 1 << DIRECTION_DOWN;
}
if(move == 1 << DIRECTION_RIGHT)
{
return 1 << DIRECTION_LEFT;
}
if(move == 1 << DIRECTION_DOWN)
{
return 1 << DIRECTION_UP;
}
return 0;
}
|
Метод moveTile
принимает два целых числа, которые используются для вычисления шагов, необходимых с использованием базовой математики. Метод возвращает true
или false
.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public boolean moveTile(int direction, int count) {
boolean match = false;
for(int i = 0; i < count; i++)
{
int targetLocation = handleLocation + DIRECTION_X[direction] + DIRECTION_Y[direction] * width;
tiles[handleLocation] = tiles[targetLocation];
match |= tiles[handleLocation] == handleLocation;
tiles[targetLocation] = tiles.length — 1;
handleLocation = targetLocation;
}
return match;
}
|
Метод shuffle
используется, чтобы перемешать части головоломки, когда начинается новая игра. Потратьте немного времени, чтобы осмотреть его реализацию, поскольку это важная часть игры. В shuffle
мы определяем предел на основе height
и width
головоломки. Как видите, мы используем метод distance
чтобы определить количество плиток, которые нужно переместить.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public void shuffle() {
if(width < 2 || height < 2)
{
return;
}
int limit = width * height * Math.max(width, height);
int move = 0;
while(distance() < limit)
{
move = pickRandomMove(invertMove(move));
moveTile(move, 1);
}
}
|
Нам нужно реализовать еще два вспомогательных метода: getDirection
и getHandleLocation
. Метод getDirection
возвращает направление, в которое перемещается кусок головоломки в определенном location
а getHandleLocation
возвращает пустой слот доски головоломки.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public int getDirection(int location) {
int delta = location — handleLocation;
if(delta % width == 0)
{
return delta < 0 ?
}
else if(handleLocation / width == (handleLocation + delta) / width)
{
return delta < 0 ?
}
else
{
return -1;
}
}
public int getHandleLocation() {
return handleLocation;
}
|
4. Создание доски головоломок
Создайте новый класс и назовите его SlidePuzzleView
. Этот класс является видом на панель головоломки, он расширяет класс View
и будет занимать весь экран устройства. Класс отвечает за рисование частей головоломки, а также за обработку сенсорных событий.
В дополнение к объекту Context
, конструктор SlidePuzzleView
также принимает экземпляр класса SlidePuzzle
как вы можете видеть ниже.
01
02
03
04
05
06
07
08
09
10
11
|
package com.dolby.DolbyPuzzle;
import android.content.Context;
import android.view.View;
public class SlidePuzzleView extends View {
public SlidePuzzleView(Context context, SlidePuzzle slidePuzzle) {
super(context);
…
}
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
public static enum ShowNumbers { NONE, SOME, ALL };
private static final int FRAME_SHRINK = 1;
private static final long VIBRATE_DRAG = 5;
private static final long VIBRATE_MATCH = 50;
private static final long VIBRATE_SOLVED = 250;
private static final int COLOR_SOLVED = 0xff000000;
private static final int COLOR_ACTIVE = 0xff303030;
private Bitmap bitmap;
private Rect sourceRect;
private RectF targetRect;
private SlidePuzzle slidePuzzle;
private int targetWidth;
private int targetHeight;
private int targetOffsetX;
private int targetOffsetY;
private int puzzleWidth;
private int puzzleHeight;
private int targetColumnWidth;
private int targetRowHeight;
private int sourceColumnWidth;
private int sourceRowHeight;
private int sourceWidth;
private int sourceHeight;
private Set<Integer> dragging = null;
private int dragStartX;
private int dragStartY;
private int dragOffsetX;
private int dragOffsetY;
private int dragDirection;
private ShowNumbers showNumbers = ShowNumbers.SOME;
private Paint textPaint;
private int canvasWidth;
private int canvasHeight;
private Paint framePaint;
private boolean dragInTarget = false;
private int[] tiles;
private Paint tilePaint;
public SlidePuzzleView(Context context, SlidePuzzle slidePuzzle) {
super(context);
sourceRect = new Rect();
targetRect = new RectF();
this.slidePuzzle = slidePuzzle;
tilePaint = new Paint();
tilePaint.setAntiAlias(true);
tilePaint.setDither(true);
tilePaint.setFilterBitmap(true);
textPaint = new Paint();
textPaint.setARGB(0xff, 0xff, 0xff, 0xff);
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(20);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setShadowLayer(1, 2, 2, 0xff000000);
framePaint = new Paint();
framePaint.setARGB(0xff, 0x80, 0x80, 0x80);
framePaint.setStyle(Style.STROKE);
}
|
Мы переопределяем метод класса onSizeChanged
и в этом методе мы устанавливаем для puzzleWidth
и puzzleHeight
значение 0
.
1
2
3
4
|
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
puzzleWidth = puzzleHeight = 0;
}
|
Метод refreshDimensions
вызывается, когда размеры представления меняются, и головоломку необходимо перестраивать. Этот метод вызывается в методе класса onDraw
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
private void refreshDimensions() {
targetWidth = canvasWidth;
targetHeight = canvasHeight;
sourceWidth = bitmap.getWidth();
sourceHeight = bitmap.getHeight();
double targetRatio = (double) targetWidth / (double) targetHeight;
double sourceRatio = (double) sourceWidth / (double) sourceHeight;
targetOffsetX = 0;
targetOffsetY = 0;
if(sourceRatio > targetRatio)
{
int newTargetHeight = (int) (targetWidth / sourceRatio);
int delta = targetHeight — newTargetHeight;
targetOffsetY = delta / 2;
targetHeight = newTargetHeight;
}
else if(sourceRatio < targetRatio)
{
int newTargetWidth = (int) (targetHeight * sourceRatio);
int delta = targetWidth — newTargetWidth;
targetOffsetX = delta / 2;
targetWidth = newTargetWidth;
}
puzzleWidth = slidePuzzle.getWidth();
puzzleHeight = slidePuzzle.getHeight();
targetColumnWidth = targetWidth / puzzleWidth;
targetRowHeight = targetHeight / puzzleHeight;
sourceColumnWidth = sourceWidth / puzzleWidth;
sourceRowHeight = sourceHeight / puzzleHeight;
}
|
В методе SlidePuzzleView
класса SlidePuzzleView
происходит фактическое рисование головоломки, в том числе рисование линий на доске головоломки, но мы также устанавливаем размеры частей головоломки, чтобы они точно помещались на экране устройства. Экземпляр SlidePuzzle
помогает нам SlidePuzzle
представление, как вы можете видеть в реализации onDraw
ниже.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
@Override
protected void onDraw(Canvas canvas) {
if(slidePuzzle == null || bitmap == null)
{
return;
}
if(puzzleWidth != slidePuzzle.getWidth() || puzzleHeight != slidePuzzle.getHeight())
{
refreshDimensions();
}
boolean solved = slidePuzzle.isSolved();
canvas.drawColor(solved ? COLOR_SOLVED : COLOR_ACTIVE);
int[] originalTiles = slidePuzzle.getTiles();
if(tiles == null || tiles.length != originalTiles.length)
{
tiles = new int[originalTiles.length];
}
for(int i = 0; i < tiles.length; i++)
{
if(originalTiles[i] == originalTiles.length — 1)
{
continue;
}
if(dragInTarget && dragging.contains(i))
{
tiles[i — SlidePuzzle.DIRECTION_X[dragDirection] — puzzleWidth * SlidePuzzle.DIRECTION_Y[dragDirection]] = originalTiles[i];
}
else
{
tiles[i] = originalTiles[i];
}
}
int delta = !dragInTarget ?
int shownHandleLocation = slidePuzzle.getHandleLocation() + delta;
tiles[shownHandleLocation] = tiles.length — 1;
int emptyTile = tiles.length — 1;
for(int i = 0; i < tiles.length; i++)
{
if(!solved && originalTiles[i] == emptyTile)
{
continue;
}
int targetColumn = slidePuzzle.getColumnAt(i);
int targetRow = slidePuzzle.getRowAt(i);
int sourceColumn = slidePuzzle.getColumnAt(originalTiles[i]);
int sourceRow = slidePuzzle.getRowAt(originalTiles[i]);
targetRect.left = targetOffsetX + targetColumnWidth * targetColumn;
targetRect.top = targetOffsetY + targetRowHeight * targetRow;
targetRect.right = targetColumn < puzzleWidth — 1 ?
targetRect.bottom = targetRow < puzzleHeight — 1 ?
sourceRect.left = sourceColumnWidth * sourceColumn;
sourceRect.top = sourceRowHeight * sourceRow;
sourceRect.right = sourceColumn < puzzleWidth — 1 ?
sourceRect.bottom = sourceRow < puzzleHeight — 1 ?
boolean isDragTile = dragging != null && dragging.contains(i);
boolean matchLeft;
boolean matchRight;
boolean matchTop;
boolean matchBottom;
int di = i;
if(dragInTarget && dragging.contains(i))
{
di = di — SlidePuzzle.DIRECTION_X[dragDirection] — puzzleWidth * SlidePuzzle.DIRECTION_Y[dragDirection];
}
if(di == tiles[di])
{
matchLeft = matchRight = matchTop = matchBottom = true;
}
else
{
matchLeft = (di — 1) >= 0 && di % puzzleWidth > 0 && tiles[di] % puzzleWidth > 0 && tiles[di — 1] == tiles[di] — 1;
matchRight = tiles[di] + 1 < tiles.length — 1 && (di + 1) % puzzleWidth > 0 && (tiles[di] + 1) % puzzleWidth > 0 && (di + 1) < tiles.length && (di + 1) % puzzleWidth > 0 && tiles[di + 1] == tiles[di] + 1;
matchTop = (di — puzzleWidth) >= 0 && tiles[di — puzzleWidth] == tiles[di] — puzzleWidth;
matchBottom = tiles[di] + puzzleWidth < tiles.length — 1 && (di + puzzleWidth) < tiles.length && tiles[di + puzzleWidth] == tiles[di] + puzzleWidth;
}
if(!matchLeft)
{
sourceRect.left += FRAME_SHRINK;
targetRect.left += FRAME_SHRINK;
}
if(!matchRight)
{
sourceRect.right -= FRAME_SHRINK;
targetRect.right -= FRAME_SHRINK;
}
if(!matchTop)
{
sourceRect.top += FRAME_SHRINK;
targetRect.top += FRAME_SHRINK;
}
if(!matchBottom)
{
sourceRect.bottom -= FRAME_SHRINK;
targetRect.bottom -= FRAME_SHRINK;
}
if(isDragTile)
{
targetRect.left += dragOffsetX;
targetRect.right += dragOffsetX;
targetRect.top += dragOffsetY;
targetRect.bottom += dragOffsetY;
}
canvas.drawBitmap(bitmap, sourceRect, targetRect, tilePaint);
if(!matchLeft)
{
canvas.drawLine(targetRect.left, targetRect.top, targetRect.left, targetRect.bottom, framePaint);
}
if(!matchRight)
{
canvas.drawLine(targetRect.right — 1, targetRect.top, targetRect.right — 1, targetRect.bottom, framePaint);
}
if(!matchTop)
{
canvas.drawLine(targetRect.left, targetRect.top, targetRect.right, targetRect.top, framePaint);
}
if(!matchBottom)
{
canvas.drawLine(targetRect.left, targetRect.bottom — 1, targetRect.right, targetRect.bottom — 1, framePaint);
}
if(!solved && (showNumbers == ShowNumbers.ALL || (showNumbers == ShowNumbers.SOME && di != tiles[di])))
{
canvas.drawText(String.valueOf(originalTiles[i] + 1), (targetRect.left + targetRect.right) / 2, (targetRect.top + targetRect.bottom) / 2 — (textPaint.descent() + textPaint.ascent()) / 2, textPaint);
}
}
}
|
Для обработки сенсорных событий нам необходимо переопределить метод класса onTouchEvent
. Чтобы сделать onTouchEvent
лаконичным и читабельным, я также объявил несколько вспомогательных методов: finishDrag
, doMove
, startDrag
и updateDrag
. Эти методы помогают реализовать поведение перетаскивания.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
@Override
public boolean onTouchEvent(MotionEvent event) {
if(slidePuzzle == null || bitmap == null)
{
return false;
}
if(slidePuzzle.isSolved())
{
return false;
}
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
return startDrag(event);
}
else if(event.getAction() == MotionEvent.ACTION_MOVE)
{
return updateDrag(event);
}
else if(event.getAction() == MotionEvent.ACTION_UP)
{
return finishDrag(event);
}
else
{
return false;
}
}
private boolean finishDrag(MotionEvent event) {
if(dragging == null)
{
return false;
}
updateDrag(event);
if(dragInTarget)
{
doMove(dragDirection, dragging.size());
}
else
{
vibrate(VIBRATE_DRAG);
}
dragInTarget = false;
dragging = null;
invalidate();
return true;
}
private void doMove(int dragDirection, int count) {
playSlide();
if(slidePuzzle.moveTile(dragDirection, count))
{
vibrate(slidePuzzle.isSolved() ? VIBRATE_SOLVED : VIBRATE_MATCH);
}
else
{
vibrate(VIBRATE_DRAG);
}
invalidate();
if(slidePuzzle.isSolved())
{
onFinish();
}
}
private boolean startDrag(MotionEvent event) {
if(dragging != null)
{
return false;
}
int x = ((int) event.getX() — targetOffsetX) / targetColumnWidth;
int y = ((int) event.getY() — targetOffsetY) / targetRowHeight;
if(x < 0 || x >= puzzleWidth || y < 0 || y >= puzzleHeight)
{
return false;
}
int direction = slidePuzzle.getDirection(x + puzzleWidth * y);
if(direction >= 0)
{
dragging = new HashSet<Integer>();
while(x + puzzleWidth * y != slidePuzzle.getHandleLocation())
{
dragging.add(x + puzzleWidth * y);
dragStartX = (int) event.getX();
dragStartY = (int) event.getY();
dragOffsetX = 0;
dragOffsetY = 0;
dragDirection = direction;
x -= SlidePuzzle.DIRECTION_X[direction];
y -= SlidePuzzle.DIRECTION_Y[direction];
}
}
dragInTarget = false;
vibrate(VIBRATE_DRAG);
return true;
}
private boolean updateDrag(MotionEvent event) {
if(dragging == null)
{
return false;
}
int directionX = SlidePuzzle.DIRECTION_X[dragDirection] * -1;
int directionY = SlidePuzzle.DIRECTION_Y[dragDirection] * -1;
if(directionX != 0)
{
dragOffsetX = (int) event.getX() — dragStartX;
if(Math.signum(dragOffsetX) != directionX)
{
dragOffsetX = 0;
}
else if(Math.abs(dragOffsetX) > targetColumnWidth)
{
dragOffsetX = directionX * targetColumnWidth;
}
}
if(directionY != 0)
{
dragOffsetY = (int) event.getY() — dragStartY;
if(Math.signum(dragOffsetY) != directionY)
{
dragOffsetY = 0;
}
else if(Math.abs(dragOffsetY) > targetRowHeight)
{
dragOffsetY = directionY * targetRowHeight;
}
}
dragInTarget = Math.abs(dragOffsetX) > targetColumnWidth / 2 ||
Math.abs(dragOffsetY) > targetRowHeight / 2;
invalidate();
return true;
}
|
Я также объявил методы получения для targetWidth
и targetHeight
и targetHeight
доступа для bitmap
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public int getTargetWidth() {
return targetWidth;
}
public int getTargetHeight() {
return targetHeight;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
puzzleWidth = 0;
puzzleHeight = 0;
}
public Bitmap getBitmap() {
return bitmap;
}
|
5. Создание класса деятельности
Когда реализация классов SlidePuzzle
и SlidePuzzleView
завершена, настало время сосредоточиться на основном классе Activity
, созданном для вас вашей IDE. Основной класс Activity
в этом примере называется SlidePuzzleMain
, но ваш класс может называться по-другому. Класс SlidePuzzleMain
объединит все, что мы создали до сих пор.
01
02
03
04
05
06
07
08
09
10
11
|
package com.dolby.DolbyPuzzle;
import android.app.Activity;
public class SlidePuzzleMain extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
…
}
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
protected static final int MENU_SCRAMBLE = 0;
protected static final int MENU_SELECT_IMAGE = 1;
protected static final int MENU_TAKE_PHOTO = 2;
protected static final int RESULT_SELECT_IMAGE = 0;
protected static final int RESULT_TAKE_PHOTO = 1;
protected static final String KEY_SHOW_NUMBERS = «showNumbers»;
protected static final String KEY_IMAGE_URI = «imageUri»;
protected static final String KEY_PUZZLE = «slidePuzzle»;
protected static final String KEY_PUZZLE_SIZE = «puzzleSize»;
protected static final String FILENAME_DIR = «dolby.digital.plus»;
protected static final String FILENAME_PHOTO_DIR = FILENAME_DIR + «/photo»;
protected static final String FILENAME_PHOTO = «photo.jpg»;
protected static final int DEFAULT_SIZE = 3;
private SlidePuzzleView view;
private SlidePuzzle slidePuzzle;
private Options bitmapOptions;
private int puzzleWidth = 1;
private int puzzleHeight = 1;
private Uri imageUri;
private boolean portrait;
private boolean expert;
|
В методе onCreate
мы создаем экземпляр объекта bitmapOptions
, устанавливая для его атрибута inScaled
значение false
. Мы также создаем экземпляр класса SlidePuzzle
и экземпляр класса SlidePuzzleView
, передавая действие в качестве контекста представления. Затем мы устанавливаем вид действия, вызывая setContentView
и передавая объект view
.
1
2
3
4
5
6
7
|
bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inScaled = false;
slidePuzzle = new SlidePuzzle();
view = new SlidePuzzleView(this, slidePuzzle);
setContentView(view);
|
В loadBitmap
мы загружаем изображение, которое вы добавили в проект в начале этого урока, и которое мы будем использовать для головоломки. Метод принимает местоположение изображения в качестве единственного аргумента, который он использует для извлечения изображения.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
protected void loadBitmap(Uri uri) {
try
{
Options o = new Options();
o.inJustDecodeBounds = true;
InputStream imageStream = getContentResolver().openInputStream(uri);
BitmapFactory.decodeStream(imageStream, null, o);
int targetWidth = view.getTargetWidth();
int targetHeight = view.getTargetHeight();
if(o.outWidth > o.outHeight && targetWidth < targetHeight)
{
int i = targetWidth;
targetWidth = targetHeight;
targetHeight = i;
}
if(targetWidth < o.outWidth || targetHeight < o.outHeight)
{
double widthRatio = (double) targetWidth / (double) o.outWidth;
double heightRatio = (double) targetHeight / (double) o.outHeight;
double ratio = Math.max(widthRatio, heightRatio);
o.inSampleSize = (int) Math.pow(2, (int) Math.round(Math.log(ratio) / Math.log(0.5)));
}
else
{
o.inSampleSize = 1;
}
o.inScaled = false;
o.inJustDecodeBounds = false;
imageStream = getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(imageStream, null, o);
if(bitmap == null)
{
Toast.makeText(this, getString(R.string.error_could_not_load_image), Toast.LENGTH_LONG).show();
return;
}
int rotate = 0;
Cursor cursor = getContentResolver().query(uri, new String[] {MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
if(cursor != null)
{
try
{
if(cursor.moveToFirst())
{
rotate = cursor.getInt(0);
if(rotate == -1)
{
rotate = 0;
}
}
}
finally
{
cursor.close();
}
}
if(rotate != 0)
{
Matrix matrix = new Matrix();
matrix.postRotate(rotate);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
setBitmap(bitmap);
imageUri = uri;
}
catch(FileNotFoundException ex)
{
Toast.makeText(this, MessageFormat.format(getString(R.string.error_could_not_load_image_error), ex.getMessage()), Toast.LENGTH_LONG).show();
return;
}
}
|
В loadBitmap
мы также вызываем setBitmap
. Реализация setBitmap
показана ниже.
1
2
3
4
5
6
7
8
|
private void setBitmap(Bitmap bitmap) {
portrait = bitmap.getWidth() < bitmap.getHeight();
view.setBitmap(bitmap);
setPuzzleSize(Math.min(puzzleWidth, puzzleHeight), true);
setRequestedOrientation(portrait ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
|
Чтобы сделать игру-головоломку более привлекательной для игрока, мы добавим возможность настроить игру, позволив игроку выбрать изображение для головоломки из фотогалереи пользователя или взять его с помощью камеры устройства. Мы также создадим опцию меню для каждого метода.
Чтобы заставить все это работать, мы реализуем два новых метода, selectImage
и takePicture
, в которых мы создаем намерение извлечь takePicture
изображение. Метод onActivityResult
обрабатывает результат выбора пользователя. Посмотрите на фрагмент кода ниже, чтобы понять полную картину.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
private void selectImage() {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType(«image/*»);
startActivityForResult(photoPickerIntent, RESULT_SELECT_IMAGE);
}
private void takePicture()
{
File dir = getSaveDirectory();
if(dir == null)
{
Toast.makeText(this, getString(R.string.error_could_not_create_directory_to_store_photo), Toast.LENGTH_SHORT).show();
return;
}
File file = new File(dir, FILENAME_PHOTO);
Intent photoPickerIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
photoPickerIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
startActivityForResult(photoPickerIntent, RESULT_TAKE_PHOTO);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent)
{
super.onActivityResult(requestCode, resultCode, imageReturnedIntent);
switch(requestCode)
{
case RESULT_SELECT_IMAGE:
{
if(resultCode == RESULT_OK)
{
Uri selectedImage = imageReturnedIntent.getData();
loadBitmap(selectedImage);
}
break;
}
case RESULT_TAKE_PHOTO:
{
if(resultCode == RESULT_OK)
{
File file = new File(getSaveDirectory(), FILENAME_PHOTO);
if(file.exists())
{
Uri uri = Uri.fromFile(file);
if(uri != null)
{
loadBitmap(uri);
}
}
}
break;
}
}
}
|
Нам осталось только создать пункт меню для каждого метода. Приведенная ниже реализация иллюстрирует, как вы можете создать меню с параметрами, которое отображается пользователю, когда они нажимают кнопку параметров устройства.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, v, menuInfo);
onCreateOptionsMenu(menu);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
boolean hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
menu.add(0, MENU_SELECT_IMAGE, 0, R.string.menu_select_image);
if(hasCamera)
{
menu.add(0, MENU_TAKE_PHOTO, 0, R.string.menu_take_photo);
}
menu.add(0, MENU_SCRAMBLE, 0, R.string.menu_scramble);
return true;
}
@Override
public boolean onContextItemSelected(MenuItem item)
{
return onOptionsItemSelected(item);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId())
{
case MENU_SCRAMBLE:
shuffle();
return true;
case MENU_SELECT_IMAGE:
selectImage();
return true;
case MENU_TAKE_PHOTO:
takePicture();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
|
Меню параметров должно выглядеть примерно так, как показано ниже.
Нажав Выбрать изображение или Сделать фото , вы сможете выбрать изображение из фотогалереи вашего устройства или взять его с помощью камеры, и использовать его в игре-головоломке.
Вы, возможно, заметили, что я также добавил третий пункт меню, чтобы перетасовать кусочки головоломки. Эта опция меню вызывает метод shuffle
который мы реализовали в классе SlidePuzzle
чуть ранее в этом уроке.
Прежде чем мы реализуем Dolby Audio API, давайте создадим два метода, которые будут запускать воспроизведение аудио файлов, которые мы добавили ранее. Вы можете пока оставить реализации этих методов пустыми. Метод onFinish
вызывается, когда игра заканчивается, когда playSound
вызывается всякий раз, когда часть головоломки перемещается.
1
2
3
4
5
6
7
|
public void onFinish() {
}
public void playSound() {
}
|
Все, что нам осталось сделать, это вызвать loadBitmap
из метода onCreate
и передать ему местоположение изображения, которое вы хотите использовать для головоломки.
1
2
3
|
Uri path = Uri.parse(«android.resource://com.dolby.DolbyPuzzle/» + R.drawable.dolby);
loadBitmap(path);
|
Посмотрите на изображение ниже для примера того, как должна выглядеть ваша игра, в зависимости от изображения, которое вы использовали для головоломки.
6. Реализация Dolby Audio API
Шаг 1
Как я упоминал в начале этого урока, интеграция Dolby Audio API проста и занимает всего несколько минут. Давайте посмотрим, как мы можем использовать Dolby Audio API в нашей игре.
Начните с загрузки Dolby Audio API с сайта разработчика Dolby . Для этого создайте бесплатную учетную запись разработчика или войдите в систему, если она у вас уже есть. После загрузки API добавьте библиотеку в свой проект.
Шаг 2
Прежде чем интегрировать Dolby Audio API, рекомендуется добавить регуляторы громкости в свое приложение. Это легко сделать и занимает всего одну строку кода. Добавьте следующий фрагмент кода в метод onCreate
вашей деятельности
1
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
Шаг 3
Следующим шагом является объявление двух переменных в вашем классе Activity
, экземпляра класса MediaPlayer
и экземпляра класса DolbyAudioProcessing
. Не забудьте добавить требуемый импорт вверху.
1
2
3
4
5
|
import android.media.MediaPlayer;
import com.dolby.dap.*;
MediaPlayer mPlayer;
DolbyAudioProcessing mDolbyAudioProcessing;
|
Шаг 4
Теперь мы заставим класс Activity
принять интерфейсы OnDolbyAudioProcessingEventListener
и MediaPlayer.OnCompletionListener
.
1
2
3
4
|
public class SlidePuzzleMain extends Activity implements MediaPlayer.OnCompletionListener,
OnDolbyAudioProcessingEventListener {
…
}
|
Чтобы принять эти интерфейсы, нам нужно реализовать несколько методов, как показано в фрагменте кода ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
// MediaPlayer.OnCompletionListener
@Override
public void onCompletion(MediaPlayer mp) {}
// OnDolbyAudioProcessingEventListener
@Override
public void onDolbyAudioProcessingClientConnected() {}
@Override
public void onDolbyAudioProcessingClientDisconnected() {}
@Override
public void onDolbyAudioProcessingEnabled(boolean b) {}
@Override
public void onDolbyAudioProcessingProfileSelected(DolbyAudioProcessing.PROFILE profile) {}
|
Мы DolbyAudioProcessing
объект DolbyAudioProcessing
при onDolbyAudioProcessingClientConnected
и снова отключаем его при onDolbyAudioProcessingClientDisconnected
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Override
public void onCompletion(MediaPlayer mp) {
if(mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
@Override
public void onDolbyAudioProcessingClientConnected() {
}
@Override
public void onDolbyAudioProcessingClientDisconnected() {
}
@Override
public void onDolbyAudioProcessingEnabled(boolean b) {}
@Override
public void onDolbyAudioProcessingProfileSelected(DolbyAudioProcessing.PROFILE profile) {}
|
Как вы можете видеть в предыдущем фрагменте кода, мы освобождаем объект MediaPlayer
когда он заканчивает воспроизведение аудиофайла.
Чтобы воспроизводить звук, когда игрок перемещает кусок головоломки, нам нужно реализовать метод playSound
. Прежде чем сосредоточиться на playSound
, мы сначала создаем экземпляр SlidePuzzleMain
в классе SlidePuzzleView
а в методе представления playSlide
мы вызываем playSound
SlidePuzzleMain
экземпляра SlidePuzzleMain
.
1
2
3
4
|
private void playSlide() {
SlidePuzzleMain activity = (SlidePuzzleMain) getContext();
activity.playSound();
}
|
В методе playSound
мы создаем экземпляр класса MediaPlayer
и используем API-интерфейс Dolby Audio, чтобы инициировать обработку аудио. Если Dolby Audio API не поддерживается устройством пользователя, метод getDolbyAudioProcessing
вернет значение null
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public void playSound()
{
if(mPlayer == null) {
mPlayer = MediaPlayer.create(
SlidePuzzleMain.this,
R.raw.slide);
mPlayer.start();
} else {
mPlayer.release();
mPlayer = null;
mPlayer = MediaPlayer.create(
SlidePuzzleMain.this,
R.raw.slide);
mPlayer.start();
}
mDolbyAudioProcessing = DolbyAudioProcessing.getDolbyAudioProcessing(this, DolbyAudioProcessing.PROFILE.GAME, this);
if (mDolbyAudioProcessing == null) {
return;
}
}
|
Как вы можете видеть ниже, реализация onFinish
метода очень похожа на реализацию playSound
. Основным отличием является то, что мы показываем сообщение пользователю, если Dolby Audio API не доступен. Как вы, возможно, помните, onFinish
метод используется, когда игра закончена, и игрок завершил головоломку.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public void onFinish() {
if (mPlayer == null ) { mPlayer = MediaPlayer.create( SlidePuzzleMain. this , R.raw.fireworks); mPlayer.start(); } else {
mPlayer.release(); mPlayer = null ; mPlayer = MediaPlayer.create( SlidePuzzleMain. this , R.raw.fireworks); mPlayer.start(); }
mDolbyAudioProcessing = DolbyAudioProcessing.getDolbyAudioProcessing( this , DolbyAudioProcessing.PROFILE.GAME, this ); if (mDolbyAudioProcessing == null ) { Toast.makeText( this , "Dolby Audio Processing not available on this device." , Toast.LENGTH_SHORT).show(); shuffle(); }
}
|
Мы также призываем shuffle
в конце onFinish
начать новую игру, когда игрок закончил головоломку.
Шаг 5
Очень важно , что мы выпускаем DolbyAudioProcessing
и MediaPlayer
объекты , когда они больше не нужны. Освобождение этих объектов гарантирует, что мы не снизим время автономной работы устройства и не окажем негативного влияния на производительность устройства.
Мы начнем с объявления трех методов. Первый метод releaseDolbyAudioProcessing
, освобождает DolbyAudioProcessing
объект и устанавливает его mDolbyAudioProcessing
в null
. Второй метод, restartSession
перезапускает сеанс, управляемый DolbyAudioProcessing
объектом, а в третьем методе suspendSession
аудиосеанс приостанавливается, и текущая конфигурация сохраняется для дальнейшего использования.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
public void releaseDolbyAudioProcessing() { if (mDolbyAudioProcessing != null ) { try {
mDolbyAudioProcessing.release(); mDolbyAudioProcessing = null ; } catch (IllegalStateException ex) { handleIllegalStateException(ex); } catch (RuntimeException ex) { handleRuntimeException(ex); }
}
}
// Backup the system-wide audio effect configuration and restore the application configuration public void restartSession() { if (mDolbyAudioProcessing != null ) { try{
mDolbyAudioProcessing.restartSession(); } catch (IllegalStateException ex) { handleIllegalStateException(ex); } catch (RuntimeException ex) { handleRuntimeException(ex); }
}
}
// Backup the application Dolby Audio Processing configuration and restore the system-wide configuration public void suspendSession() { if (mDolbyAudioProcessing != null ) { try{
mDolbyAudioProcessing.suspendSession(); } catch (IllegalStateException ex) { handleIllegalStateException(ex); } catch (RuntimeException ex) { handleRuntimeException(ex); }
}
}
/** Generic handler for IllegalStateException */ private void handleIllegalStateException(Exception ex) {
Log.e( "Dolby processing" , "Dolby Audio Processing has a wrong state" ); handleGenericException(ex); }
/** Generic handler for IllegalArgumentException */ private void handleIllegalArgumentException(Exception ex) {
Log.e( "Dolby processing" , "One of the passed arguments is invalid" ); handleGenericException(ex); }
/** Generic handler for RuntimeException */ private void handleRuntimeException(Exception ex) {
Log.e( "Dolby processing" , "Internal error occurred in Dolby Audio Processing" ); handleGenericException(ex); }
private void handleGenericException(Exception ex) {
Log.e( "Dolby processing" , Log.getStackTraceString(ex)); }
|
Как вы можете видеть в приведенном выше фрагменте кода, я также создал несколько методов для обработки исключений , которые могут быть отброшены в releaseDolbyAudioProcessing
, restartSession
и suspendSession
.
Три метода, которые мы только что создали, должны вызываться в несколько ключевых моментов жизненного цикла приложения. Мы добиваемся этого путем переопределения onStop
, onStart
, onDestroy
, onResume
и onPause
метод в нашем SlidePuzzleMain
классе.
В onStop
, мы говорим MediaPlayer
объект , чтобы сделать паузу и onStart
на MediaPlayer
объекте продолжает воспроизведение , если это не null
. onDestroy
Метод вызывается , когда приложение закрыто. В этом методе мы освобождаем MediaPlayer
объект, устанавливаем mPlayer
в null
и вызываем releaseDolbyAudioProcessing
, что мы реализовали ранее.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
@Override
protected void onStop() {
super.onStop();
if (mPlayer != null ) { mPlayer.pause(); }
}
@Override
protected void onStart() {
super.onStart();
if (mPlayer != null ) {
mPlayer.start(); }
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d( "Dolby processing" , "onDestroy()" ); // Release Media Player instance if (mPlayer != null ) { mPlayer.release(); mPlayer = null ; }
this .releaseDolbyAudioProcessing(); }
@Override
protected void onResume() {
super.onResume();
restartSession(); }
@Override
protected void onPause() {
super.onPause();
Log.d( "Dolby processing" , "The application is in background, supsendSession" ); //
// If audio playback is not required while your application is in the background, restore the Dolby audio processing system // configuration to its original state by suspendSession(). // This ensures that the use of the system-wide audio processing is sandboxed to your application. suspendSession(); }
|
Наконец, в onPause
и onResume
мы приостанавливаем и перезапускаем аудио сеанс, вызывая suspendSession
и restartSession
соответственно.
Если вы выполнили шаги, описанные в этом руководстве, то ваша игра теперь должна быть полностью функциональной с интегрированным Dolby Audio API. Постройте проект, чтобы поиграть с конечным результатом.
7. Резюме
Я уверен, что вы согласны с тем, что интеграция Dolby Audio API проста и занимает не более пяти минут. Давайте кратко суммируем шаги, которые мы предприняли для интеграции API.
- импортировать библиотеку Dolby Audio API
- создать экземпляр
DolbyAudioProcessing
класса - реализовать
OnDolbyAudioProcessingEventListener
интерфейс.
- включить
DolbyAudioProcessing
экземпляр вonDolbyAudioProcessingClientConnected
- отключить
DolbyAudioProcessing
экземпляр вonDolbyAudioProcessingClientDisconnected
- после запуска медиаплеера инициализируйте
DolbyAudioProcessing
экземпляр с помощьюGAME
профиля - проверить, должен ли
DolbyAudioProcessing
объектnull
проверять, поддерживается ли Dolby Audio API устройством - Чтобы продлить срок службы батареи и оптимизировать производительность, приостановите и отпустите
DolbyAudioProcessing
экземпляр, когда приложение уничтожено или перемещено в фоновый режим.
Вывод
Несмотря на то, что игра, которую мы создали, довольно проста, важно помнить об этом уроке — Dolby Audio API. Мобильный рынок — многолюдное место, и отличаться от других игр непросто. Добавление превосходного звука в вашу игру не останется незамеченным для ваших пользователей, и это сделает вашу игру заметной. Зайдем на сайт разработчиков Dolby в дать ему попробовать.