Construire une application Android à rouleaux de dés de dice minimum viable à l'aide des constructions Android de base.

Vector AndroidRelativeLayout , ConstraintLayout , LinearLayout ]ListView et ArrayAdapter ListView et AdapterIl y a un peu de temps, je suis entré dans ce jeu de société Mansions of Madness. Le jeu est un peu comme l'indice classique où les joueurs errent dans une maison essayant de résoudre un mystère. C'est un jeu génial que je recommande vivement . Quoi qu'il en soit, le jeu utilise des rouleaux de dés pour résoudre les actions et d'autres événements de jeu. Curieusement, les joueurs doivent parfois rouler plus de dés que le jeu ne comprend (6)! J'ai décidé que c'était une occasion parfaite de créer une application de rouleaux de dés personnalisée. Dans ce tutoriel, j'utiliserai des composants Android de base pour construire un rouleau de dés folie de Mansons of Madness.

Cette application Dice est spécialement conçue pour les manoirs de la folie, donc je vais d'abord décrire comment le jeu utilise les dés.
Pour garder les choses simples, l'application sera une liste verticale et déficient de dés. L'application aura 3 boutons pour déclencher les fonctions "Roll Dice", "Ajouter des dés" et "Retirer les dés". Chaque dés aura un «maintien» et un «changement» correspondants.

La plate-forme Android change toujours, ce qui fait des tutoriels comme ceux-là obsolètes au fil du temps. Pour référence, mon environnement de développement:
Environnement Android


Tous ces noms peuvent être modifiés après la création du projet, bien qu'il puisse devenir lourd de poursuivre toutes les références de nom si le projet devient complexe.

Android a de nombreuses versions. À chaque version, la plate-forme change. Cela signifie essentiellement qu'il existe de nombreux appareils Android dans le monde avec différentes versions. Cela devient un mal de tête pour les développeurs d'applications, car selon les bibliothèques que l'application utilise, l'application peut être incompatible avec certains appareils. Le compromis ici est que l'application utilisant les nouvelles bibliothèques Android ne peut pas fonctionner sur des appareils plus anciens. Si l'application doit s'exécuter sur des appareils plus anciens, l'application doit utiliser certaines des anciennes constructions Android.

Le modèle initial n'a pas trop d'importance pour cette application. Le code de modèle est parfois utile car il prépopule la mise en page et les classes initiales avec un peu de code. Comme je ne vais pas utiliser tout cela, j'ai choisi l'activité vide.

La boîte de dialogue de création de projet Android initialise le projet avec des constructions de base:
Gradle est un cadre pour faciliter les projets de construction. Sur wikipedia
Le manifeste Android décrit l'application sur Android. Propriétés d'application telles que les autorisations et les activités. Les détails peuvent être trouvés sur la page de documentation Android.
Pour commencer, j'ai utilisé un simple éditeur SVG en ligne appelé Clker pour tirer les visages de dés en tant que SVG.



Ensuite, je les importe dans mon projet en utilisant Asset Studio d'Android Studio.


Les appareils ont différentes résolutions et dimensions. Prédire la résolution et la dimension de l'appareil que l'application s'exécute est difficile. La mise à l'échelle des JPEG peut entraîner des graphiques flous ou granuleux. Une façon de s'attaquer à ceci est pour les graphiques est d'utiliser les SVG. Il existe de nombreux articles en ligne discutant des SVG, mais pour référence, veuillez consulter l'article Wikipedia SVG pour plus d'informations.
Android propose deux façons de décrire les dispositions: les dispositions programmatiques et XML. Décrire les dispositions complexes programmatiques est assez difficile, donc la plupart des gens évitent généralement cela. L'écriture XML n'est peut-être pas si amusante, mais Android Studio propose plusieurs outils pour soulager la douleur. Un outil d'aperçu et un éditeur de mise en page Wsywig. Mais même avec l'éditeur, la plongée dans le XML est presque inévitable.


Le modèle d'activité vide d'Android Studio nous démarre avec un élément de mise en page Root ConstraintLayout . Nous aurons besoin de deux composants dans cette application: zone de dés et zone de contrôleur. La zone de dés sera une liste de dés d'écran et le contrôleur sera les 3 boutons "ajouter" "retirer" "roll".

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>
Pour ceux qui ne connaissent pas le XML, ce qui précède peut ressembler à du charabia. Expliquer XML est en dehors de la portée de ce tutoriel, mais Google, YouTube et Wikipedia sont d'excellentes ressources pour ceux qui recherchent plus d'informations. Pour cette mise en page, j'utilise des classes ListView , Button , LinearLayout et ConstraintLayout . Les détails autour de leurs attributs peuvent être trouvés sur la page de documentation Android.
Je cherchais une Layout qui décrit facilement une zone inférieure à hauteur fixe (pour les boutons) et une zone supérieure (pour les dés) qui remplissaient l'espace d'écran disponible. LinearLayout espace ses sous-éléments en utilisant des poids, ce qui le rend inapproprié. RelativeLayout n'offre pas la possibilité de «remplir l'espace restant», ce qui le rend également inadapté.
Visuellement, LinearLayout semble assez proche de ce dont j'ai besoin. Cependant, LinearLayout est destiné à la liste statique des éléments plutôt que des listes dynamiques. Pour cette application, la liste des dés peut avoir entre 0 et 25 dés, la fabrication de ListView est un meilleur candidat.
La valeur 0dp est spécifique à ConstraintLayout qui indique qu'il devrait remplir l'espace restant du parent.
Comme décrit ci-dessus, chaque ligne de dés comprend 2 boutons et une image de dés.

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>
Le bouton se trouve au centre vertical de la ligne et à une certaine distance de chaque bord. J'ai senti que la conception serait plus propre si la conception séparait l'élément bouton réel et sa position dans la disposition. Donc, pour mon application, j'utilise le FrameLayout pour spécifier la position et centrer le bouton de cette disposition.
Les valeurs de chaîne et de dimension nous permettent de ne pas écrire des chaînes de configuration et des entiers directement dans le code. Pour notre petite application, peut-être pas un gros problème.
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 et ArrayAdapter À ce stade, j'ai initialisé notre projet Android avec une MainActivity vide et je me suis moqué de certaines dispositions. Ensuite, je vais entrer dans la logique et le code. Pour commencer, j'aimerais entrer dans des cours Java spécifiques à Android. ListView est une classe de mise en page de base pour rendre les listes visuelles. Le framework Android sépare les composants visuels ( ListView ) et les composants de données ( List<Dice> ) en utilisant un modèle d'adaptateur. Dans notre cas, tout ce que l'adaptateur fait est de cartographier les données ( Dice ) à une disposition visuelle ( dice_row.xml ). Dans ce cas, un fichier XML de mise en page décrit la mise en page.
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;
}
}
}
L'application représentera l'état de dés avec des objets de dés. L'objet Dice a deux propriétés: Valeur de dés [Blank, Magnify, Star], et si le dés est «maintenu». Fonctionnellement, le dés a une méthode de roulis qui sélectionnera au hasard un visage de dés. Enfin, j'ajoute une méthode qui modifie la valeur des dés à la suivante sur la liste.
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];
}
}
Dans cette étape, le bouton Map clique sur la logique. La plate-forme Android offre quelques façons de le faire. Une façon consiste à spécifier un attribut à partir du fichier de mise en page. Un autre consiste à définir programmatiques le onClickListener . Dans notre application, utilisez une approche d'attribut pour les trois boutons de niveau supérieur et définissez programmatiquement l'écouteur des boutons de ligne.
addDice Si le nombre de dés est inférieur à 25, ajoute un nouvel objet de dés à la liste des dés.
Conception
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"/>
....
Logique
MainActivity.java ....
public void addDice(View view) {
if(diceList.size()< MAX_DICE_COUNT) {
diceAdapter.add(new Dice());
}
}
....
removeDice si la liste des dés n'est pas vide, supprime les derniers dés de la liste
Conception
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"/>
....
Logique
MainActivity.java ....
public void removeDice(View view) {
if(!diceList.isEmpty()) {
int lastIndex = diceList.size() - 1;
diceAdapter.remove(diceAdapter.getItem(lastIndex));
}
}
....
rollDice relâche la valeur de chaque liste de dés sur la liste qui n'a pas été marquée pour la tenue.
Conception
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"/>
....
Logique
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 ? Le bouton de roulis modifie la valeur de dés pour l'objet de dés correspondant. En raison de la séparation du modèle de vue / des données, la disposition de la ligne de dés ne renvoie pas automatiquement à moins que le déclencheur soit déclenché. Appeler notifyDataSetChanged redessine la vue.
Cliquez sur le bouton Hold définira l'indicateur de maintien du dés.
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();
}
});
....
Cliquez sur le bouton Hold modifiera la valeur et l'interface de mise à jour du dés.
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();
}
});
....