Создание минимального жизнеспособного приложения для продуктов на сыторе Android с использованием основных конструкций Android.

VectorRelativeLayout , ConstraintLayout , LinearLayout ]ListView и ArrayAdapter ListView и Adapter LogicНекоторое время назад я попал в этот настольный особняк безумия. Игра немного похожа на классическую подсказку, где игроки бродят по дому, пытаясь решить какую -то загадку. Это потрясающая игра, которую я очень рекомендую. В любом случае, игра использует Dice Rolls для разрешения действий и других игровых событий. Как ни странно, игрокам иногда приходится бросить больше кубиков, чем игра включает в себя (6)! Я решил, что это прекрасная возможность построить приложение на заказ в кости. В этом уроке я буду использовать базовые компоненты Android, чтобы построить особняки безумного ролика.

Это приложение Dice специально разработано для игрового процесса Mansions of Madness, так что сначала я расскажу, как игра использует кости.
Чтобы все было просто, приложение будет вертикальным, прокручиваемым списком кубиков. Приложение будет иметь 3 кнопки для запуска функций «Roll Dice», «добавить кости» и «Удалить кости». У каждого кости будет соответствующее «удержание» и «изменение».

Платформа Android всегда меняется, что со временем устарела такими, как они устарели. Для справки, моя среда разработки:
Среда Android


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

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

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

Диалог создания проекта Android инициализирует проект с основными конструкциями:
Gradle - это структура для облегчения строительных проектов. На Википедии
Манифест Android описывает приложение для Android. Свойства приложения, такие как разрешения и активисты. Подробности можно найти на странице документации Android.
Для начала я использовал простой онлайн -редактор SVG под названием Clker, чтобы вытянуть лиц Dice как SVG.



Затем я импортирую их в свой проект, используя Asset Studio Android Studio.


Устройства имеют разные решения и размеры. Прогнозировать разрешение и размер устройства, которое запускает приложение, сложно. Масштабирование JPEG может привести к размытой или зернистой графике. Один из способов заняться этим - это использовать SVG. В Интернете есть множество статей, обсуждающих SVG, но для справки, пожалуйста, ознакомьтесь с статьей Wikipedia SVG для получения дополнительной информации.
Android предлагает два способа описания макетов: программные и XML -макеты. Описание сложных макетов программно довольно сложно, поэтому большинство людей обычно избегают этого. Написание XML может быть не так уж и весело, но Android Studio предлагает несколько инструментов для облегчения боли. Инструмент предварительного просмотра и редактор макетов Wsywig. Но даже с редактором, погружение в XML почти неизбежно.


Пустой шаблон Android Studio пустой активности запускает нас с элемента макета корня ConstraintLayout . Нам понадобится два компонента в этом приложении: зона кости и область контроллера. Область кубиков будет прокручиваемым списком кубиков, а контроллер будет 3 кнопки «добавить» «удалить» «рулон».

activity_main.xml <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/dice_list"
android:layout_height="0dp"
android:layout_width="match_parent"
app:layout_constraintBottom_toTopOf="@id/button_bar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ListView>
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/control_bar_height"
android:orientation="horizontal"
android:weightSum="3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/dice_list">
<Button
android:id="@+id/add_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_button_label"
android:onClick="addDice"/>
<Button
android:id="@+id/rem_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rem_button_label"
android:onClick="removeDice"/>
<Button
android:id="@+id/roll_dice_button"
android:layout_weight="1"
android:layout_gravity = "center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/roll_button_label"
android:onClick="rollDice"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
Для тех, кто не знаком с XML, вышеупомянутое может выглядеть как тампы. Объяснение XML выходит за рамки этого урока, но Google, YouTube и Wikipedia - отличные ресурсы для тех, кто ищет дополнительную информацию. Для этого макета я использую классы ListView , Button , LinearLayout и ConstraintLayout . Детали вокруг их атрибутов можно найти на странице документации Android.
Я искал Layout , которая легко описывает область с фиксированной высотой (для кнопок) и верхнюю область (для кости), которая заполняла доступное пространство экрана. LinearLayout Space Out свои подразделения, используя веса, делая его неподходящими. RelativeLayout не предлагает возможность «заполнить оставшееся пространство», также что делает его непригодным.
Визуально, LinearLayout выглядит довольно близко к тому, что мне нужно. Тем не менее, LinearLayout предназначен для статического списка элементов, а не динамических списков. Для этого приложения список кубиков может иметь от 0 до 25 кубиков, создание ListView является лучшим кандидатом.
Значение 0dp является специфичным для ConstraintLayout , что указывает на то, что оно должно заполнить оставшееся пространство родителя.
Как описано выше, каждая строка в кости включает 2 кнопки и изображение костей.

row.xml <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/row_height">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/row_height"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:padding="@dimen/row_padding">
<Button
android:id="@+id/dice_change_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/change_button_label">
</Button>
</FrameLayout>
<ImageView
android:id="@+id/dice_icon"
android:layout_centerInParent="true"
android:layout_width="@dimen/image_width"
android:layout_height="@dimen/image_height"
android:src="@drawable/blank_dice"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/row_height"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="@dimen/row_padding">
<Button
android:id="@+id/dice_hold_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hold_button_label">
</Button>
</FrameLayout>
</RelativeLayout>
Кнопка находится в вертикальном центре ряда и на некотором расстоянии от каждого края. Я чувствовал, что дизайн будет чище, если дизайн отделяет фактический элемент кнопки и его положение в макете. Поэтому для моего приложения я использую FrameLayout , чтобы указать положение и центрировать кнопку в этом макете.
Значения строки и измерений позволяют нам не записывать строки конфигурации и целые числа непосредственно в код. Для нашего маленького приложения, может быть, не важно.
string.xml <resources>
<string name="app_name">DiceRoller</string>
<string name="hold_button_label">Hold</string>
<string name="change_button_label">Change</string>
<string name="add_button_label">ADD</string>
<string name="rem_button_label">REM</string>
<string name="roll_button_label">ROLL</string>
</resources>
dimens.xml <?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="row_height">72dp</dimen>
<dimen name="row_padding">16dp</dimen>
<dimen name="control_bar_height">72dp</dimen>
<dimen name="image_width">72dp</dimen>
<dimen name="image_height">72dp</dimen>
</resources>
ListView и ArrayAdapter На данный момент я инициализировал наш проект Android с пустой магистратностью и издевался на некоторых макетах. Далее я попаду в логику и код. Для начала я хотел бы заняться еще некоторыми классами Java, специфичными для Android. ListView - это базовый класс макета для визуальных списков. Android Framework отделяет визуальные компоненты ( ListView ) и компоненты данных ( List<Dice> ), используя шаблон адаптера. В нашем случае все, что делает адаптер, это сопоставляется данные ( Dice ) с какой -то визуальной компоновкой ( dice_row.xml ). В этом случае файл XML макета описывает макет.
MainActivity.java public class MainActivity extends AppCompatActivity {
DiceAdapter diceAdapter;
List <Dice> diceList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//associating activity to layout
setContentView(R.layout.activity_main);
//Setup ListView and Adapter
ListView listView = findViewById(R.id.dice_list);
diceAdapter = new DiceAdapter(this, R.layout.dice_row, diceList);
listView.setAdapter(diceAdapter);
//Initialize Data
diceAdapter.add(new Dice());
}
public class DiceAdapter extends ArrayAdapter<Dice> {
public DiceAdapter(@NonNull Context context, int resource, List<Dice> list) {
super(context, resource, list);
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.dice_row, parent, false);
}
return convertView;
}
}
}
Приложение будет представлять состояние кости с объектами костей. Объект кости имеет два свойства: значение кости [пусто, увеличение, звезда], и является ли кубик «удерживается». Функционально, в кости есть метод броска, который случайным образом выберет лицо кости. Наконец, я добавляю метод, который изменяет значение кости на следующий в списке.
MainActivity.java ....
public static class Dice {
public enum Face {
BLANK,
MAGNIFY,
STAR
}
public static Random random = new Random();
boolean hold = false;
Face diceVal;
Dice() {
roll();
}
public void roll() {
int num = random.nextInt(4);
if(num == 0) { //25% magify
this.diceVal = Face.MAGNIFY;
} else {
//37.5% star, 37.5% blank
if(random.nextBoolean()) {
this.diceVal = Face.BLANK;
} else {
this.diceVal = Face.STAR;
}
}
}
public void toggleHold() {
hold = !hold;
}
public void nextValue() {
int index = diceVal.ordinal();
index = (index+1) % Face.values().length;
diceVal = Face.values()[index];
}
}
На этом шаге кнопка карта нажимает на логику. Платформа Android предлагает несколько способов сделать это. Одним из способов является указание атрибута из файла макета. Другой - программно установить onClickListener . В нашем приложении используйте подход атрибута для трех кнопок верхнего уровня и программно установите слушатель для кнопок строки.
addDice Если количество кубиков составляет менее 25, добавляет новый объект Dice в список костей.
Дизайн
activity_main.xml ....
<Button
android:id="@+id/add_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_button_label"
android:onClick="addDice"/>
....
Логика
MainActivity.java ....
public void addDice(View view) {
if(diceList.size()< MAX_DICE_COUNT) {
diceAdapter.add(new Dice());
}
}
....
removeDice если список кубиков не пуст, удаляет последние кубики из списка
Дизайн
activity_main.xml ....
<Button
android:id="@+id/rem_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rem_button_label"
android:onClick="removeDice"/>
....
Логика
MainActivity.java ....
public void removeDice(View view) {
if(!diceList.isEmpty()) {
int lastIndex = diceList.size() - 1;
diceAdapter.remove(diceAdapter.getItem(lastIndex));
}
}
....
rollDice повторяет ценность каждой кости в списке, который не был помечен для удержания.
Дизайн
activity_main.xml ....
<Button
android:id="@+id/roll_dice_button"
android:layout_weight="1"
android:layout_gravity = "center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/roll_button_label"
android:onClick="rollDice"/>
....
Логика
MainActivity.java ....
public void rollDice(View view) {
//roll all dice
for(Dice dice : diceList) {
if(!dice.hold)
dice.roll();
}
//notify adapter to update view
diceAdapter.notifyDataSetChanged();
}
....
notifyDataSetChanged ? Кнопка рулона изменяет дискейку для соответствующего объекта Dice. В связи с разделением шаблона адаптера просмотра/адаптера данных, макет строки костей не автоматически повторно повторно видится, если не запускается. Вызов notifyDataSetChanged перерисовал вид.
Кнопка нажатия удержания установит флаг удержания костей.
MainActivity.java ....
Button holdButton = convertView.findViewById(R.id.dice_hold_button);
holdButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Dice dice = diceList.get(position);
dice.toggleHold();
}
});
....
Кнопка нажатия удержания изменит значение кости и обновление интерфейса.
MainActivity.java ....
Button changeButton = convertView.findViewById(R.id.dice_change_button);
changeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Dice dice = diceList.get(position);
dice.nextValue();
diceAdapter.notifyDataSetChanged();
}
});
....