Erstellen einer minimal lebensfähigen Produkt Würfelrolle Android -App mit den grundlegenden Android -Konstrukten.

VectorRelativeLayout , ConstraintLayout , LinearLayout ]ListView und ArrayAdapter ListView und AdapterVor einiger Zeit bin ich in dieses Brettspiel -Villen des Wahnsinns eingestiegen. Das Spiel ist ein bisschen wie der klassische Hinweis, in dem die Spieler in einem Haus herumlaufen und versuchen, etwas Rätsel zu lösen. Es ist ein großartiges Spiel, das ich sehr empfehle. Wie auch immer, das Spiel verwendet Dice Rolls, um Aktionen und andere Spielereignisse zu lösen. Seltsamerweise müssen Spieler manchmal mehr Würfel rollen als das Spiel (6)! Ich entschied, dass dies eine perfekte Gelegenheit war, eine benutzerdefinierte Würfel -Roller -App zu erstellen. In diesem Tutorial werde ich grundlegende Android -Komponenten verwenden, um eine Villen von Wahnsinnswürfelrolle zu bauen.

Diese Würfel -App wurde speziell für Villen des Madness -Gameplays entwickelt. Zuerst werde ich skizzieren, wie das Spiel Würfel verwendet.
Um die Dinge einfach zu halten, wird die App eine vertikale, scrollbare Liste von Würfeln sein. Die App verfügt über 3 Tasten, um Funktionen "Roll Dice", "Dice hinzufügen" und "Dice entfernen" auszulösen. Jedes Würfel hat einen entsprechenden "Halt" und "Veränderung".

Die Android -Plattform verändert sich immer und macht im Laufe der Zeit Tutorials wie diese veraltet. Als Referenz, meine Entwicklerumgebung:
Android -Umgebung


Alle diese Namen können nach der Erstellung von Projekten geändert werden, obwohl es umständlich werden kann, alle Namensreferenzen zu verfolgen, wenn das Projekt komplex wird.

Android hat viele Versionen. Bei jeder Version ändert sich die Plattform. Dies bedeutet im Grunde, dass es in der Welt viele Android -Geräte mit verschiedenen Versionen gibt. Dies wird für App -Entwickler Kopfschmerzen, da die App abhängig davon, welche Bibliotheken die App verwendet, mit bestimmten Geräten möglicherweise nicht kompatibel ist. Der Kompromiss hier ist, dass die App mit den neuen Android -Bibliotheken nicht auf älteren Geräten ausgeführt wird. Wenn die App auf älteren Geräten ausgeführt wird, muss die App einige der älteren Android -Konstrukte verwenden.

Die anfängliche Vorlage spielt für diese App eigentlich nicht zu viel. Der Vorlagencode ist manchmal nützlich, da er das Layout und die ersten Klassen mit einem Code vorpopuliert. Da ich nichts davon verwenden werde, habe ich mich für leere Aktivitäten entschieden.

Das Dialogfeld "Android Project Creation" initialisiert das Projekt mit grundlegenden Konstrukten:
Gradle ist ein Rahmen, um Bauprojekte zu erleichtern. Auf Wikipedia
Das Android -Manifest beschreibt die App Android. Anwendungseigenschaften wie Berechtigungen und Aktivitäten. Details finden Sie auf der Android -Dokumentationsseite.
Zu Beginn habe ich einen einfachen Online -SVG -Editor namens Clker verwendet, um die Würfelgesichter als SVG zu zeichnen.



Als nächstes importiere ich sie mit dem Asset Studio von Android Studio in mein Projekt.


Geräte haben unterschiedliche Auflösungen und Dimensionen. Die Vorhersage der Auflösung und Dimension des Geräts, die die App ausführt, ist schwierig. Skalierende JPEGs können zu verschwommenen oder körnigen Grafiken führen. Eine Möglichkeit, dies zu bekämpfen, besteht darin, SVGs zu verwenden. Es gibt zahlreiche Artikel online über SVGs, aber als Referenz finden Sie im Artikel von Wikipedia SVG, um weitere Informationen zu erhalten.
Android bietet zwei Möglichkeiten, Layouts zu beschreiben: programmatische und XML -Layouts. Das programmatische Beschreiben komplexer Layouts ist ziemlich schwierig, so dass die meisten Menschen dies im Allgemeinen vermeiden. Das Schreiben von XML macht vielleicht nicht so viel Spaß, aber Android Studio bietet mehrere Werkzeuge, um die Schmerzen zu lindern. Ein Vorschau -Tool und ein WSYWIG -Layout -Editor. Aber selbst mit dem Herausgeber ist das Eintauchen in das XML nahezu unvermeidlich.


Die Android Studio Leer -Aktivitätsvorlage startet uns mit einem Root -Layout -Element von ConstraintLayout . Wir benötigen zwei Komponenten in dieser App: Würfelbereich und Controller Area. Der Würfelbereich ist eine scrollbare Würfelliste, und der Controller ist die 3 Tasten "" "" "Rollen" hinzufügen.

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>
Für diejenigen, die mit XML nicht vertraut sind, kann das oben genannte wie Kauderwelsch aussehen. Das Erklären von XML liegt außerhalb des Rahmens dieses Tutorials, aber Google, YouTube und Wikipedia sind großartige Ressourcen für diejenigen, die nach weiteren Informationen suchen. Für dieses Layout verwende ich Klassen ListView , Button , LinearLayout und ConstraintLayout . Die Details um ihre Attribute finden Sie auf der Android -Dokumentationsseite.
Ich suchte nach einem Layout , das leicht einen unteren Bereich der festen Höhe (für Tasten) und einen oberen Bereich (für Würfel) beschreibt, der den verfügbaren Bildschirmraum füllte. LinearLayout speichert die Subelemente mit Gewichten, was es ungeeignet macht. RelativeLayout bietet nicht die Möglichkeit, den verbleibenden Platz zu füllen, und macht ihn auch ungeeignet.
Visuell sieht LinearLayout dem so nahe, was ich brauche. LinearLayout gilt jedoch eher für statische Liste von Elementen als für dynamische Listen. Für diese App kann die Würfelliste zwischen 0 und 25 Würfel haben, und die Erstellung von ListView ist ein besserer Kandidat.
Der 0dp -Wert ist spezifisch für ConstraintLayout , der angibt, dass er den verbleibenden Speicherplatz des übergeordneten übernehmen sollte.
Wie oben beschrieben, enthält jede Würfelreihe 2 Tasten und ein Würfelbild.

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>
Die Taste befindet sich in der vertikalen Mitte der Reihe und einem gewissen Abstand von jeder Kante. Ich hatte das Gefühl, dass das Design sauberer wäre, wenn das Design das tatsächliche Knopfelement und seine Position im Layout getrennt hätte. Für meine App benutze ich den FrameLayout , um die Position anzugeben und die Schaltfläche in diesem Layout zu zentrieren.
String- und Dimensionswerte ermöglichen es uns, Konfigurationszeichenfolgen und Ganzzahlen direkt in Code zu schreiben. Für unsere kleine App, vielleicht keine große Sache.
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 und ArrayAdapter Zu diesem Zeitpunkt habe ich unser Android -Projekt mit einer leeren Mainaktivität initialisiert und einige Layouts verspottet. Als nächstes werde ich in die Logik und den Code eingehen. Zu Beginn möchte ich mich in ein weitere Android -spezifische Java -Klassen einlassen. ListView ist eine grundlegende Layout -Klasse zum Rendern von visuellen Listen. Das Android -Framework trennt die visuellen Komponenten ( ListView ) und Datenkomponenten ( List<Dice> ), indem ein Adaptermuster verwendet wird. In unserem Fall ist der Adapter die Daten ( Dice ) auf ein visuelles Layout ( dice_row.xml ) abzubilden. In diesem Fall beschreibt eine Layout -XML -Datei das Layout.
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;
}
}
}
Die App wird den Würfelzustand mit Würfelsobjekten darstellen. Das Würfelobjekt hat zwei Eigenschaften Dinge: Würfelwert [leer, vergrößern, stern] und ob der Würfel "gehalten" wird. Funktionell hat der Würfel eine Rollmethode, die zufällig ein Würfelgesicht auswählt. Schließlich füge ich eine Methode hinzu, die den Würfelwert auf die nächste in der Liste ändert.
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];
}
}
In diesem Schritt klicke ich auf die Schaltfläche auf Logik. Die Android -Plattform bietet ein paar Möglichkeiten, dies zu tun. Eine Möglichkeit besteht darin, ein Attribut aus der Layoutdatei anzugeben. Eine andere besteht darin, den onClickListener programmatisch festzulegen. Verwenden Sie in unserer App den Attributansatz für die drei Tasten der obersten Ebene und setzen Sie den Listener programmgesteuert für die Zeilenschaltflächen.
addDice Wenn die Würfelzahl weniger als 25 beträgt, fügt der Würfelliste ein neues Würfelobjekt hinzu.
Design
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"/>
....
Logik
MainActivity.java ....
public void addDice(View view) {
if(diceList.size()< MAX_DICE_COUNT) {
diceAdapter.add(new Dice());
}
}
....
removeDice wenn die Würfelliste nicht leer ist, entfernt die letzten Würfel aus der Liste
Design
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"/>
....
Logik
MainActivity.java ....
public void removeDice(View view) {
if(!diceList.isEmpty()) {
int lastIndex = diceList.size() - 1;
diceAdapter.remove(diceAdapter.getItem(lastIndex));
}
}
....
rollDice wird den Wert jeder Würfel auf seiner Liste umrollt, die nicht zum Halten markiert wurde.
Design
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"/>
....
Logik
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 bezeichnet? Der Roll -Taste ändert den Würfelwert für das entsprechende Würfelobjekt. Aufgrund der Trennung von Ansicht/Datenadaptermuster wird das DICE-Zeilenlayout nicht automatisch neu gedreht, sofern dies ausgelöst wird. Aufrufen von notifyDataSetChanged , zeichnet die Ansicht neu.
Wenn Sie auf die Schaltfläche "Halten" klicken, wird das Halteflag des Würfels festgelegt.
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();
}
});
....
Wenn Sie auf die Schaltfläche "Halten" klicken, wird die Wert- und Aktualisierungsschnittstelle des Würfels geändert.
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();
}
});
....