Программирование приложений на android
Создана: 31 Октября 2011 Пон 13:25:33.
Раздел: "Компьютерный раздел"
Сообщений в теме: 223, просмотров: 35098
-
-
Ввод данных (касание экрана и нажатие на клавишу) это системные события.
Приложение может обратится к системе и получить код события. Затем обработать его с помощью переключателя и таким образом отреагировать на полученную информацию
Обрабатываем события от touchpad и keypad
Надо снабдить игрока курсором (ячейка выделенная цветом), который будет показывать, какая из ячеек в настоящее время выбрана. Эта ячейка будет изменяться, когда игрок нажмет на цифру. За это отвечает код в методе onDraw( ):
Код: // Draw the selection...
Paint selected = new Paint();
selected.setColor(getResources().getColor(
R.color.puzzle_selected));
canvas.drawRect(selRect, selected);
Прямоугольник для курсора вычисляем в самом начале в методе onSizeChanged(), прямоугольник будем рисовать полупрозрачным цветом поверх выбранной ячейки.
Перемещение курсора при нажатии клавиш управления курсором выполняем с помощью метода onKeyDown( ):
Внутри метода select( ), мы высчитываем новую координату x и y для курсора и после этого используем метод getRect() для вычисления нового курсора:
Код: private void select(int x, int y) {
invalidate(selRect);
selX = Math.min(Math.max(x, 0), 8);
selY = Math.min(Math.max(y, 0), 8);
getRect(selX, selY, selRect);
invalidate(selRect);
}
Рисунок. Перемещение курсора
Тут два обращения к методу invalidate(). Первое сообщает Android о необходимости перерисовывания места старого прямоугольника (на левой стороне рисунка). Второе обращение к invalidate() необходимо для перерисовки нового положения курсора.
Менеджер окон запомнит испорченный прямоугольник на экране и в будущем в методе onDraw( ) перерисует его для вас.
При нажатии на цифровую клавишу вызывается метод setTileIfValid. Сначала проверяем введенное значение на предмет его отсутствия в списке использованных значений. Если проверка пройдена то записываем значение в массив puzzle[] с помощью метода setTile и выполняем пересчет значений элементов массивов used и used_hs -
Фиксируем наши идеи относительно ввода данных в коде
В класс Ira (активити) добавляем три метода
Код: // 3. Ввод данных
// Записываем значение в ячейку если оно не противоречит Судоку
protected boolean setTileIfValid(int x, int y, int value){
int tiles[] = getUsedTiles(x, y);
// Метод getUsedTilesErbol возвращает массив занятых значений
// для ячейки с координатами x и y
if (value != 0) {
for (int tile : tiles) {
if (tile == value)
return false;
}
}
// Записываем введенное пользователем значение в элемент массива puzzle
setTile(x, y, value);
// Пересчитываем все элементы массива
calculateUsedTiles();
return true;
}
// Записываем введенное пользователем значение в элемент массива puzzle
private void setTile(int x, int y, int value) {
puzzle[y * 9 + x] = value;
}
/** Создать keypad если сущесвует несколько правильных вариантов */
public void showKeypadOrError(int x, int y) {
int tiles[] = getUsedTiles(x, y);
if (tiles.length == 9) {
Toast toast = Toast.makeText(this,R.string.no_moves_label,Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
} else {
Dialog v = new Keypad(this, tiles, iraView);
v.show();
}
}
В методе showKeypadOrError проверяем сначала наличие не занятых цифр для активной ячейки. Если их нет , то выводим сообщение (оповещение) - типа мест нет. Это делается с помощью объекта Toast.
Если есть не занятые значения, то для ориентировки (подсказки) даем игроку диалоговое окно в котором он может выбрать нужную цифру.
Выбранная с помощью диалога цифра помещается в ячейку.
Сам диалог может быть оформлен как решетка из девяти или менее элементов (типа кнопок) в зависимости от ситуации для текущей ячейки
То есть класс который поддерживает диалог должен уметь отображать нужные кнопки и реагировать на выбор одной из них выработкой кода нажатой кнопки.
Реализация поставленной задачи происходит следующим образом. Сначала с помощью инструкций XML определяем геометрию и положение на экране решетки из девяти одинаковых прямоугольников (кнопки с изображением цифр на них). Потом в классе keypad создаем для каждой из кнопок "слушателя", который реагирует на касание экрана в области заданной с помощью макета.
В класс IraView добавляем методы которые реагируют на ввод и рисуют изменения в ячейках Судоку
Код: // 2. Ввод данных
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN)
return super.onTouchEvent(event);
select((int) (event.getX() / width),
(int) (event.getY() / height));
ira.showKeypadOrError(selX, selY);
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
select(selX, selY - 1);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
select(selX, selY + 1);
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
select(selX - 1, selY);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
select(selX + 1, selY);
break;
case KeyEvent.KEYCODE_0:
case KeyEvent.KEYCODE_SPACE: setSelectedTile(0); break;
case KeyEvent.KEYCODE_1: setSelectedTile(1); break;
case KeyEvent.KEYCODE_2: setSelectedTile(2); break;
case KeyEvent.KEYCODE_3: setSelectedTile(3); break;
case KeyEvent.KEYCODE_4: setSelectedTile(4); break;
case KeyEvent.KEYCODE_5: setSelectedTile(5); break;
case KeyEvent.KEYCODE_6: setSelectedTile(6); break;
case KeyEvent.KEYCODE_7: setSelectedTile(7); break;
case KeyEvent.KEYCODE_8: setSelectedTile(8); break;
case KeyEvent.KEYCODE_9: setSelectedTile(9); break;
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_DPAD_CENTER:
ira.showKeypadOrError(selX, selY);
break;
default:
return super.onKeyDown(keyCode, event);
}
return true;
}
// Внутри метода select( ), мы высчитываем новую координату x и y
// для курсора и после этого используем метод getRect()
// для вычисления нового курсора:
private void select(int x, int y) {
invalidate(selRect);
selX = Math.min(Math.max(x, 0), 8);
selY = Math.min(Math.max(y, 0), 8);
getRect(selX, selY, selRect);
invalidate(selRect);
}
public void setSelectedTile(int tile) {
if (ira.setTileIfValid(selX, selY, tile)) {
invalidate();// may change hints
} else {
// Number is not valid for this tile
}
}
А это класс Keypad который осуществляет прослушивание кнопок в окне диалога и передачу выбранного значения методу setSelectedTile(tile) класса IraView
Код: package com.ira;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
public class Keypad extends Dialog {
protected static final String TAG = "Sudoku";
private final View keys[] = new View[9];
private View keypad;
private final int useds[];
private final IraView iraView;
public Keypad(Context context, int useds[], IraView iraView) {
super(context);
this.useds = useds;
this.iraView = iraView;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.keypad_title);
setContentView(R.layout.keypad);
findViews();
for (int element : useds) {
if (element != 0)
keys[element - 1].setVisibility(View.INVISIBLE);
}
setListeners();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
int tile = 0;
switch (keyCode) {
case KeyEvent.KEYCODE_0:
case KeyEvent.KEYCODE_SPACE: tile = 0; break;
case KeyEvent.KEYCODE_1: tile = 1; break;
case KeyEvent.KEYCODE_2: tile = 2; break;
case KeyEvent.KEYCODE_3: tile = 3; break;
case KeyEvent.KEYCODE_4: tile = 4; break;
case KeyEvent.KEYCODE_5: tile = 5; break;
case KeyEvent.KEYCODE_6: tile = 6; break;
case KeyEvent.KEYCODE_7: tile = 7; break;
case KeyEvent.KEYCODE_8: tile = 8; break;
case KeyEvent.KEYCODE_9: tile = 9; break;
default:
return super.onKeyDown(keyCode, event);
}
if (isValid(tile)) {
returnResult(tile);
}
return true;
}
/** Return the chosen tile to the caller */
private void returnResult(int tile) {
// возвращаем выбранное значение
iraView.setSelectedTile(tile);
// выход из диалога
dismiss();
}
private boolean isValid(int tile) {
for (int t : useds) {
if (tile == t)
return false;
}
return true;
}
private void findViews() {
keypad = findViewById(R.id.keypad);
keys[0] = findViewById(R.id.keypad_1);
keys[1] = findViewById(R.id.keypad_2);
keys[2] = findViewById(R.id.keypad_3);
keys[3] = findViewById(R.id.keypad_4);
keys[4] = findViewById(R.id.keypad_5);
keys[5] = findViewById(R.id.keypad_6);
keys[6] = findViewById(R.id.keypad_7);
keys[7] = findViewById(R.id.keypad_8);
keys[8] = findViewById(R.id.keypad_9);
}
//
private void setListeners() {
for (int i = 0; i < keys.length; i++) {
final int t = i + 1;
keys[i].setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
returnResult(t);
}});
}
keypad.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
returnResult(0);
}});
}
}
-
Интересно как все это будет работать. Пока что нам не хватает макета клавиатуры в окне диалога , текста сообщения которое выводится с помощью Оповещения (Toast) в том случае если список возможных значений для ячейки заполнен полностью (все девять значений заняты)
Теория
Все ресурсы создаются и сохраняются в предопределенных подкаталогах в каталоге res/ nроекта
res/anim/ - файлы анимации.
res/drawaЬle/ - графика.
res/layout/ - ХМL-файлы для схем разметки .
res/values/ - ХМL-файлы , которые могут быть откомпилированы во многие виды ресурса. В отличие от других каталогов, res/ может содержать любое число файлов. При создании файлов ресурсов следует соблюдать соглашение об именовании файлов ресурсов по типу элементов, определенных в них:
• arrays .xml - массивы ;
• colors.xml - значения цвета для графики и строк текста;
• dimens.xml - размерности ;
• strings.xml - строковые значения;
• styles.xml - стили .
res/xml/ - произвольные ХМL-файлы , которые могут загружаться во время выполнения.
res/raw/ - произвольные файлы , предназначенные для копирования непосредственно
на устройство.
Значение атрибута ХМL-элемента (или ресурса) может также быть ссылкой на ресурс.
Например, если мы имеем цветовые ресурсы , мы можем записать файл разметки, который устанавливает значение цвета для текста, находящегося в одном из этих ресурсов :
android : textColor= "@color/opaque_red"
Обратите внимание здесь на использование префикса @ для того, чтобы ввести ссылку ресурса - текст после этого префикса - имя ресурса. В этом случае мы не должны были указывать пакет, потому что мы ссылаемся на ресурс в нашем собственном пакете. Для ссылки на системный ресурс мы должны записать :
android : textColor= "@android : color/opaque_red"
Класс R имеет несколько вложенных классов, один для каждого типа ресурса, поддерживаемого системой Android, и для которого в проекте существует файл ресурса.
Класс R может содержать следующие вложенные классы :
R.anim - идентификаторы для файлов из каталога res/an i m/;
R.array - идентификаторы для файлов из каталога res/values/;
R.bool - идентификаторы для битовых массивов в файлах arrays.xml из каталога res/values/;
R.color - идентификаторы для файлов colors.xm l из каталога res/values/;
R.dimen - идентификаторы для файлов dimens.xml из каталога res/values/;
R.drawable - идентификаторы для файлов из каталога res/drawable/;
R.id - идентификаторы представлений и групп представлений для файлов ХМL-разметки из каталога res/layout/;
R.integer - идентификаторы для целочисленных массивов в файлах arrays.xml из каталога res/values/;
R.layout - идентификаторы для файлов разметки из каталога res/layout/;
R.raw - идентификаторы для файлов из каталога res/raw/;
R.string - идентификаторы для файлов strings.xml из каталога res/values/;
R.style - идентификаторы для файлов styles.xml из каталога res/values/;
R.xml - идентификаторы для файлов из каталога res/xml/.
Каждый вложенный класс содержит один или несколько идентификаторов для откомпилированных ресурсов, которые используются в коде программы для загрузки ресурса.
arrays.xml - для массивов строк или целочисленных значений ;
colors.xml - для значений цвета;
dimen .xml - для значений размеров текста;
drawables.xml - для графических примитивов.
Файлы меню сохраняются в отдельном каталоге res/menu/.
В ХМL-файле меню есть три элемента:
<menu> - корневой элемент файла меню;
<group> - контейнерный элемент, определяющий группу меню;
<item> - элемент, определяющий пункт меню.
Элементы < itern> и <group> могут быть дочерними элементами <group>. Корневой узел любого файла должен быть элементом меню.
Подробнее ознакомится с использованием ресурсов можно в книге Голощапова «Google Android программирование для мобильных устройств»
Наши действия
Вставляем в файл strings.xml определение для ресурса no_moves_label
Вот так будет выглядеть файл strings.xml. Он расположен в папке values
Код: <?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Судоку</string>
<string name="no_moves_label">Нет вариантов</string>
<string name="keypad_title">Клавиатура</string>
</resources>
Это файл keypad.xml. Надо его создать в папке layout
Код: <?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keypad"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:stretchColumns="*">
<TableRow>
<Button android:id="@+id/keypad_1"
android:textSize="30sp"
android:text="1">
</Button>
<Button android:id="@+id/keypad_2"
android:textSize="30sp"
android:text="2">
</Button>
<Button android:id="@+id/keypad_3"
android:textSize="30sp"
android:text="3">
</Button>
</TableRow>
<TableRow>
<Button android:id="@+id/keypad_4"
android:textSize="30sp"
android:text="4">
</Button>
<Button android:id="@+id/keypad_5"
android:textSize="30sp"
android:text="5">
</Button>
<Button android:id="@+id/keypad_6"
android:textSize="30sp"
android:text="6">
</Button>
</TableRow>
<TableRow>
<Button android:id="@+id/keypad_7"
android:textSize="30sp"
android:text="7">
</Button>
<Button android:id="@+id/keypad_8"
android:textSize="30sp"
android:text="8">
</Button>
<Button android:id="@+id/keypad_9"
android:textSize="30sp"
android:text="9">
</Button>
</TableRow>
</TableLayout>
Это main.xml (папка layout)
Код: <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Запускаем, смотрим. -
Как результат наших действий игра взаимодействует с игроком. Данные можно вводить с клавиатуры и касанием ячейки на экране.
Я сам не игрок, но мне кажется что с позиции игрока важным свойством является возможность настройки. В нашем случае можно музыку включать/выключать , показывать/скрывать цвет ячеек найденных с помощью логики используемых методов. Также можно вводить новые начальные условия для Судоку, то есть расстановку цифр в начале игры
Таким образом получается что мы буде использовать два checkbox и одно поле TextEdit. Checbox`ы будем использовать для выбора проигрывать музыку или нет во время игры и показывать или не показывать подсказки. TextEdit для ввода строки из 81 цифры, в которой номер элемента строки это номер ячейки Судоку, а значение элемента строки это значение которое надо поместить в эту ячейку.
Создаем макет экрана с установками. Записываем все это в файл settings.xml. Говорят что в папке Layout этот файл себя ведет не понятно, поэтому создаем папку xml и и помещаем его туда.
Код: <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="Музыка"
android:title="@string/music_title"
android:summary="@string/music_summary"
android:defaultValue="true" />
<CheckBoxPreference
android:key="Подсказки"
android:title="@string/hints_title"
android:summary="@string/hints_summary"
android:defaultValue="true" />
<EditTextPreference
android:key="Судоку"
android:title="Комбинация Судоку"
android:summary="Ряд за рядом"
android:textSize="30sp"
android:defaultValue="046050107030400905000001000500000010004907500020000008000300000103005070408
070320"
android:dialogTitle="Введите комбинацию цифр Судоку" />
</PreferenceScreen>
Еще надо дополнить файл strings.xml в папке val
Код: <?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Судоку</string>
<string name="no_moves_label">Нет вариантов</string>
<string name="keypad_title">Клавиатура</string>
<string name="settings_label">Установки...</string>
<string name="settings_title">Установки Судоку</string>
<string name="settings_shortcut">s</string>
<string name="music_title">Музыка</string>
<string name="music_summary">Играть музыку в фоне</string>
<string name="hints_title">Подсказки</string>
<string name="hints_summary">Показывать подсказки в продолжении игры</string>
</resources>
Создаем класс Prefs.java
Код: import android.content.Context;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
public class Prefs extends PreferenceActivity {
// Option names and default values
private static final String OPT_MUSIC = "Музыка";
private static final boolean OPT_MUSIC_DEF = true;
private static final String OPT_HINTS = "Подсказки";
private static final boolean OPT_HINTS_DEF = true;
private static final String OPT_S = "Судоку";
private static final String OPT_S_DEF = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
}
/** Get the current value of the music option */
public static boolean getMusic(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(OPT_MUSIC, OPT_MUSIC_DEF);
}
/** Get the current value of the hints option */
public static boolean getHints(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(OPT_HINTS, OPT_HINTS_DEF);
}
/** Get the current value of the music option */
public static String getSudoku(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getString(OPT_S, OPT_S_DEF);
}
}
Чтобы получить значение настройки используем метод getDefaultSharedPreferences() объекта PreferencesManager. Этот метод возвращает некоторую структуру которая содержит данные о настройках, далее используем методы
• getBoolean(String key, boolean defValue);
• getFloat(String key, float defValue);
• getInt(String key, int defValue);
• getLong(String key, long defValue);
• getString(String key, String defValue)
для получения нужной настройки
Теперь надо эти настройки использовать для выбора нужного действия
В класс Ira добавляем два метода
Код: @Override
protected void onResume() {
super.onResume();
Music.play(this, R.raw.africa);
}
@Override
protected void onPause() {
super.onPause();
Music.stop(this);
}
В папке res создаем новую папку raw и вставляем туда файл в формате mp3. Я назвал песню africa.mp3
Запускаем смотрим
Извиняюсь, наврал
Надо еще один класс создать, который умеет музыку проигрывать
Код: package com.ira;
import android.content.Context;
import android.media.MediaPlayer;
public class Music {
private static MediaPlayer mp = null;
/** Stop old song and start new one */
public static void play(Context context, int resource) {
stop(context);
// Start music only if not disabled in preferences
if (Prefs.getMusic(context)) {
mp = MediaPlayer.create(context, resource);
mp.setLooping(true);
mp.start();
}
}
/** Stop the music */
public static void stop(Context context) {
if (mp != null) {
mp.stop();
mp.release();
mp = null;
}
}
}
Кроме того надо еще создать макет меню. И поместить его в папку res/menu
Код: <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/settings"
android:title="@string/settings_label"
android:alphabeticShortcut="@string/settings_shortcut" />
</menu>
Еще раз извиняюсь. В класс Ira.java добавляем методы для активации меню и пакеты соответствующие
Код: @Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.settings:
startActivity(new Intent(this, Prefs.class));
return true;
// More items go here (if any) ...
}
return false;
}
Вот пакеты которые нужно импортировать в класс
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
На экране приложения должен появиться значок Настроек - прямоугольник заполненный горизонтальными линиями
Для изменения настроек щелкаем на этот значок -
Проблема такая нарисовалась. При запуске приложения на эмуляторе получаем сообщение в консоли eclipse
INSTALL_FAILED_INSUFFICIENT_STORAGE
Решение
Идём в Run configurations, там проставляем дефолтный эмулятор, который надо запускать, идём сюда: "additional emulator command line options" и прописываем
-partition-size 1024
Если на данный момент запущен какой-то эмулятор типа AVD, то убить.
[внешняя ссылка] -
Из трех Настроек - музыка, подсказка и Расклад мы сейчас используем только музыку.
Чтобы подключить Расклад, то есть начальную комбинацию цифр в ячейках Судоку сделаем так, Добавим в метод onCreate класса Ira код
if (Prefs.getSudoku(this).length()==81) {
Log.d("Sudoku", "value :" + Prefs.getSudoku(this));
puz = Prefs.getSudoku(this);
};
Тогда получим
Код: public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Prefs.getSudoku(this).length()==81) {
Log.d("Sudoku", "value :" + Prefs.getSudoku(this));
puz = Prefs.getSudoku(this);
};
// Инициализация массива puzzle
puzzle = getPuzzle(puz);
// Инициализация массивов used и used_hs
calculateUsedTiles();
iraView = new IraView(this);
setContentView(iraView);
iraView.requestFocus();
}
Еще надо убрать модификатор static из объявления puz. Будет так
private String puz = "046050107030400905000001000500000010004907500020000008000300000103005070408
070320";
У метода getSudoku аргумент имеет тип Context. Activity является подклассом Context, поэтому мы можем использовать ее – this.
[внешняя ссылка] -
Последняя настройка это "Подсказка". То есть если мы хотим раскрасить ячейки Судоку в цвета в соответствии с количеством вариантов заполнения то выбираем в Настройках флажок "Подсказка".
Чтобы эта настройка действовала на код надо в методе onDraw использовать условный оператор который будет отслеживать состояние флага
if (Prefs.getHints(getContext())) {
}
Код: if (Prefs.getHints(getContext())) {
// Draw the hints...
// Pick a hint color based on #moves left
Paint hint = new Paint();
int c[] = { getResources().getColor(R.color.puzzle_hint_0),
getResources().getColor(R.color.puzzle_hint_1),
getResources().getColor(R.color.puzzle_hint_2), };
Rect r = new Rect();
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
int movesleft = 9 - ira.getUsedTiles(i, j).length;
if (movesleft < c.length) {
getRect(i, j, r);
hint.setColor(c[movesleft]);
canvas.drawRect(r, hint);
}
}
}
} -
Чтобы иметь возможность работы с кэшем объекта view надо определить его идентификатор. Кэш нам нужен для того чтобы сохранить в нем позицию курсора, то есть номер активной ячейки и текущий расклад цифр по ячейкам Судоку.
Эти данные запоминаются при повороте планшета. Наступление и обработку события поворот планшета фиксируем с помощью метода onSaveInstanceState(). Надо переписать его. Инициализацию объекта View, в частности обращение к кэшу за данными., после поворота обрабатываем с помощью метода onRestoreInstanceState
Вставляем в конец конструктора класса IraView инструкцию
setId(1);
Теперь можем пользоваться кэшем
Вставляем в класс IraView два метода которые сохраняют данные в кэше и восстанавливают таблицу Судоку по данным хранящимся в кэше
Код: // Сохраняем положение курсора в хеше, то есть активной ячейки
// при перевороте планшета
@Override
protected Parcelable onSaveInstanceState() {
Parcelable p = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("selX", selX);
bundle.putInt("selY", selY);
String s0 = toPuzzleStringErbol(puzzle);
bundle.putString("s0", s0);
bundle.putParcelable("viewState", p);
return bundle;
}
@Override
// Забираем из хеша сохраненные данные о координатах
// активной ячейки
protected void onRestoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
selectErbol(bundle.getInt("selX"), bundle.getInt("selY"));
String s0 = bundle.getString("s0");
puzzle = getPuzzleErbol(s0);
calculateUsedTilesErbol();
Log.d("Value",s0);
super.onRestoreInstanceState(bundle.getParcelable("viewState"));
return;
}
Запускаем смотрим. Для того чтобы поменять ориентацию экрана симулятора можно использовать комбинацию Ctrl + F11 -
С Судоку закончили. Но тут на подходе библиотека AndEngine которая позволяет двухмерные игры создавать.
Хочу разобрать статью "Пишем игру для Android c помощью AndEngine".
[внешняя ссылка]
Ваши предложения, замечания ? -
Постановка: Рисуем игрушку "Лазеры и зеркала" наподобие "Mirrors Maze"
[внешняя ссылка]
В данном случае действующими лицами будут рисунки лазера , зеркал и фона (background)
Классы библиотеки AndEngine
Engine - управляет процессом вывода графики, проигрывания звуков и музыки, обрабатывает касания экрана, управляет устройствами (вибратором), обрабатывает информацию от датчиков (акселерометр). Метод onUpdate циклически выполняет все инструкции, позволяя задавать логику поведения в игре.
Camera - отвечает за то, что вы увидите на экране. Всё, что за пределами камеры, на экране не видно. Размер Camera обычно устанавливается равным размеру экрана устройства.
Подклассы Camera:
1. BoundCamera — перемещается по сцене в заданных рамках;
2. ZoomCamera — камера с произвольным увеличением сцены. Расширяет класс BoundCamera;
3. SmoothCamera — расширяет ZoomCamera. Производит плавное увеличение/уменьшение зума при его изменении;
Camera может следовать за каким-нибудь объектом
Scene является контейнером для объектов — других сцен, спрайтов, графических примитивов.
При создании игры обязательно нужно создать хотя бы одну Scene, в методе onLoadScene
Entity (Сущность) Entity содержит методы по изменению цвета, масштабированию, повороту, видимости и прочие.
Entity можно использовать, например, как простой контейнер для спрайтов добавлять к основной Scene (типа слой).
Modifiers (Модификаторы). Вот какие модификаторы предоставлет AndEngine:
1. Меняющие прозрачность;
2. Меняющие цвет;
3. Меняющие положение (x и y), в том числе изменение по заданным точкам (пути);
4. Меняющие угол (вращение спрайта);
5. Меняющие масштаб;
Основные методы класса BaseGameActivity
onLoadEngine
onLoadResources
onLoadScene
onLoadComplete
[внешняя ссылка]
Сама статья состоит из 4 частей. Во второй части автор создает несколько типов данных определяя их через класс Entity.
В начале было отмечено что в игре участвуют разные объекты. Сообразно их поведению определяем методы и свойства для создаваемых типов данных (классов) -
Главное в игре Mirrors Maze это отрисовка пути лазера по точкам. Первая совпадает с пушкой, далее добавляем точки при выходе за экран, остановке и отражении
Если угол движения известен, то найти следующую точку не сложно
Автор предлагает такую схему
Код: private void nextPosition(Point position, final int angle) {
switch (angle) {
case DynamicGameObject.DEG_0:
position.x++;
break;
case DynamicGameObject.DEG_90:
position.y++;
break;
case DynamicGameObject.DEG_180:
position.x—;
break;
case DynamicGameObject.DEG_270:
position.y—;
break;
case DynamicGameObject.DEG_45:
position.x++;
position.y++;
break;
case DynamicGameObject.DEG_135:
position.x—;
position.y++;
break;
case DynamicGameObject.DEG_225:
position.x—;
position.y—;
break;
case DynamicGameObject.DEG_315:
position.x++;
position.y—;
break;
default:
break;
}
}
Для нас важно определить как будет меняться угол при встрече луча с зеркалом. Надо учитывать взаимное расположение луча с зеркалом.
Пусть зеркало неподвижно а луч падает на него под разными углами
1. 0 градусов – нет отражения
2. 45 – 315
3. 90 – нет отражения
4. 135 – 225
5. 180 – нет отражения
6. 225 – 135
7. 270 – нет отражения
8. 315 – 45 -
Решим задачу определения направления отраженного луча в общем случае когда вращаются и зеркало и лазерная пушка.
Зеркало двухстороннее. Введем обозначения. Угол между зеркалом (лучом) и осью Y равный 0 градусов обозначим 0
2. Равный 45 градусам - обозначим 1
3. 90 градусов - 2
4. 135 - 3
5. 180 - 4
6. 225 - 5
7. 270 - 6
8. 315 - 7
Нужно получить такую формулу чтобы полученное значение угла не превышало число 7.
Еще обозначения
b - угол луча с осью Y
a - угол зеркала с осью Y
Из предыдущего рассмотрения видно, что между углами выполняются следующие соотношения
Если b = a + 1, то новое значение b, после отражения будет равно a + 7
Если b = a + 7, то b`= a + 1
Если b = a + 3, то b`= a + 5
Если b = a + 5, то b`= a + 3
В общем можно все это так записать
Код: @Override
int onLaser(int angle) {
int a = getAngle() % 4;
// Операция - остаток от деления
if (angle == (a + 1) % 8) return (a + 7) % 8;
if (angle == (a + 7) % 8) return (a + 1) % 8;
if (angle == (a + 3) % 8) return (a + 5) % 8;
if (angle == (a + 5) % 8) return (a + 3) % 8;
return -1;
}
-
Продолжаем изучать игру "Зеркала и лазеры".
Для случая когда направление луча меняется более плавно, допустим не с шагом 45 градусов, а с шагом 15 градусов можно вывести общие формулы которые бы определяли угол отраженного луча относительно некоторой оси Y в зависимости от угла падения луча и угла наклона зеркала к оси Y, a и b соответственно
Для начала нужно определить угол падения луча по отношению к оси зеркала.
Если a > b , угол луча по отношению к зеркалу , назовем его c , будет равен
c = a - b
Если a < b , угол с равен
c = (a + 360) - b
После того как определен угол с (угол падения на зеркало) можно вычислить угол отраженного луча.
c`= 360 - c
Теперь можно рассчитать угол отраженного луча по отношению к оси Y
d = c` + b