L’objectiu d’aquestos apunts es guiar-vos en la creació d’una serie d’aplicacions de dificultat creixent.
Si seguiu el passos i completeu els exercicis adquirireu els conceptes i tècniques necessaris per a poder realitzar les vostres propies aplicacions per al SO Android.
Crear una aplicació que ens permeta portar el compte de vides i verins que tinga cada jugador en una partida de Magic.
S’han de mantindre els contadors si es gira la pantalla.
S’ha de poder resetejar els contadors en una opció de Menú.
-
Creem un nou projecte
No es necessari el suport per a C++.
-
Triem el API level
El API level determina les llibreries que tindrem disponibles i es correspon a les diferents versions d’Android.
Quan major siga aquest valor menys dependrem de la llibreria de compatibilitat i tindrem accés a eines més actualitzades; al mateix temps, però, la nostra aplicació es podrà executar en un nombre més reduït de mòbils.
Nosaltres utilitzarem el nivell 16.
-
Creem una nova activitat de tipus Basic Activity
-
Deixem el nom
MainActivity
. Seleccionem l’opció Use a Fragment.Sempre utilitzarem fragments en les nostres
apps
, ja que ens facilitzarà una posterior adaptació a tabletes. -
Esperem mentre es crea el nou projecte
Els passos son el següents:
-
En acabar de crear-se el projecte sens presentarà la interfície de disseny
-
Hem de verificar que es tracta d’un
ConstraintLayout
-
Agregarem una guia horitzontal que ens servirà per a separar les dues zones de l’aplicació
-
Utilitzant l’eina New Vector Asset creem imatges per al nostres botons.
-
Situem els
ImageButtons
(botons amb imatge) de les fletxes-
Els utilitzarem per passar vides d’un jugador a un altre
-
Com volem que estiguen en la part central de la pantalla utilitzarem la guia central com a referència en l’eix vertical
-
Per a que ocupen tota la pantalla fixarem els
constraints
esquerre i dret als laterals i fixarem com la opcióLayout Width
el valormatch_parent
(ocupar tot el valor del pare). -
Per últim, llevarem el marge entre els botons
-
Utilitzant la propietat
tint
pintarem les icones de vermell. -
El resultat final ha de ser semblant al següent:
-
-
Situem els
Buttons
(botons de text) per als verins -
Situem els
ImageButtons
per a les vides.-
Els utilitzarem per a sumar i restar vides de cada un dels jugadors.
-
Els pintarem de color vermell.
-
Constraints
:-
Cada botó anirà fixat al botó de verí més proper i a la guia central en el sentit vertical
-
En sentit horitzontal agafaran les referencies del botó de verí més proper.
-
-
Els botons tindran el mateix tamany horitzontal que els botons de verí (opció
wrap_content
per aLayout Width
). -
Per últim, llevarem el marge entre els botons
-
Utilitzant la propietat
tint
pintarem les icones de vermell. -
Resultat:
-
-
TextViews
pels contadors -
Fixar IDs
-
Per a poder referenciar els botons i
TextViews
des de el codi hem de donar un ID a cadascun d’ells.
-
El primer pas abans de poder assignar lògica als botons de la nostra interfície és el d’utilitzar findViewById per referenciar-los.
El procés serà el següent:
-
Passem a la pantalla de codi principal.(
app/java/MainActivityFragment.java
). -
Accedim al objecte que fa referència al fragment (
view
) ja que aquest conté el métode findViewById. Guardarem una referència a aquest objecte-
Substituirem el codi
return inflater.inflate(R.layout.fragment_main, container, false)
per
View view = inflater.inflate(R.layout.fragment_main, container, false); //Aquí van les crides a findViewById return view;
Aquest codi es troba en el mètode
onCreateView
, el que s’executa al inicialitzar el fragment.
-
-
Fem totes les crides a findViewById
-
Necessitarem accedit a tots el botons, botons amb imatge i textos (
Button
,ImageButton
iTextView
); -
La sintaxi básica és
TipusComponent nom = view.findViewById(R.id.idComponent) (1)
-
R
es un classe especial autogenerada per l’Android SDK.R.id
conté tots elsids
declarats en elsLayouts
.
-
-
Resultat:
View view = inflater.inflate(R.layout.fragment_main, container, false); ImageButton lifetwotoone = view.findViewById(R.id.lifetwotoone); ImageButton lifeonetotwo = view.findViewById(R.id.lifeonetotwo); Button p1poisonmore = view.findViewById(R.id.p1poisonmore); Button p1poisonless = view.findViewById(R.id.p1poisonless); Button p2poisonmore = view.findViewById(R.id.p2poisonmore); Button p2poisonless = view.findViewById(R.id.p2poisonless); ImageButton p1lifemore = view.findViewById(R.id.p1lifemore); ImageButton p2lifemore = view.findViewById(R.id.p2lifemore); ImageButton p2lifeless = view.findViewById(R.id.p2lifeless); ImageButton p1lifeless = view.findViewById(R.id.p1lifeless); TextView counter1 = view.findViewById(R.id.counterp1); TextView counter2 = view.findViewById(R.id.counterp2); return view;
-
El següent pas serà el de definir com volem que s’emmagatzemen les dades en la nostra aplicació.
Al ser un exemple tan senzill tindrem prou en quatre variables privades situades en el mateix fragment.
public class MainActivityFragment extends Fragment { //(1)
private int life1 = 20; //(2)
private int life2 = 20; //(3)
private int poison1 = 0; //(4)
private int poison2 = 0; //(5)
public MainActivityFragment() { //(6)
-
Definició de la classe
-
Vida del jugador 1
-
Vida del jugador 2
-
Verins del jugador 1
-
Verins del jugador 2
-
Constructor de la classe
També haurem de definir les operacions que es podran realitzar sobre les dades.
Tindrem vuit accions possibles, les referides a incrementar i decrementar les vides i verins de cada jugador..
public void incLife1(){
life1++;
}
public void incLife2(){
life2++;
}
public void decLife1(){
life1--;
}
public void decLife2(){
life2--;
}
public void incPoison1(){
poison1++;
}
public void incPoison2(){
poison2++;
}
public void decPoison1(){
poison1--;
}
public void decPoison2(){
poison2--;
}
Per a poder modificar les dades quan es pressionen els botons hem d’utilitzar els gestor d’events (Events Handlers
) que s’han d’agregar als cotrol que hem obtingut utilitzant findViewById.
La sintaxi general seria:
control.setEvent((View view)) -> { (1) (2)
// Aquí aniria el nostre codi.
}
-
Utilitzem una funcionalitat de Java anomenada funcions lambda.
Ens permeten substituir una classe amb sols un mètode; reduïnt molt el codi que hem d’escriure.
-
view
en aquest cas, es referirà al control causant de l’event.
Anirem assignant els diferents events a les crides del nostre model de dades; d’aquesta manera conforme anem fent click s’anirà modificant el valor de les variables.
Tindrem un resultat semblant al següent:
lifeonetotwo.setOnClickListener((View view) -> {
decLife1();
incLife2();
});
lifetwotoone.setOnClickListener((View view) -> {
decLife2();
incLife1();
});
p1poisonmore.setOnClickListener((View view) -> {
incPoison1();
});
p1poisonless.setOnClickListener((View view) -> {
decPoison1();
});
p2poisonmore.setOnClickListener((View view) -> {
incPoison2();
});
p2poisonless.setOnClickListener((View view) -> {
decPoison2();
});
p1lifemore.setOnClickListener((View view) -> {
incLife1();
});
p2lifemore.setOnClickListener((View view) -> {
incLife2();
});
p1lifeless.setOnClickListener((View view) -> {
devLife1();
});
p2lifeless.setOnClickListener((View view) -> {
devLife2();
});
Com el codi es molt simple podem estalviar la creació de tantes funcions lambda agrupant-les en una sola que tinga un switch
.
View.OnClickListener listener = (View view) -> {
switch (view.getId()) { // (1)
case R.id.lifeonetotwo:
decLife1();
incLife2();
break;
case R.id.lifetwotoone:
decLife2();
incLife1();
break;
case R.id.p1lifeless:
decLife1();
break;
case R.id.p1lifemore:
incLife1();
break;
case R.id.p1poisonless:
decPoison1();
break;
case R.id.p1poisonmore:
incPoison1();
break;
case R.id.p2lifeless:
decLife2();
break;
case R.id.p2lifemore:
incLife2();
break;
case R.id.p2poisonless:
decPoison2();
break;
case R.id.p2poisonmore:
incPoison2();
break;
}
};
lifetwotoone.setOnClickListener(listener);
lifeonetotwo.setOnClickListener(listener);
p1poisonmore.setOnClickListener(listener);
p1poisonless.setOnClickListener(listener);
p2poisonmore.setOnClickListener(listener);
p2poisonless.setOnClickListener(listener);
p1lifemore.setOnClickListener(listener);
p2lifemore.setOnClickListener(listener);
p1lifeless.setOnClickListener(listener);
p2lifeless.setOnClickListener(listener);
-
Obtenim l’identificador del botó polsat.
Per a mostrar les dades haurem de modificar la variable text
utilitzant el method setText
del TextView
.
Crearem un métode que actualitze els textViews
, per a millorar la reusabilitat.
private void updateViews() {
counter1.setText(String.format("%d/%d", life1, poison1));
counter2.setText(String.format("%d/%d", life2, poison2));
}
Per últim, farem una crida al métode al final del onClickListener
.
//...
p2lifeless.setOnClickListener(listener);
updateViews();
D’aquesta forma, cada volta que polsem un botó s’actualizaran els contadors.
Si, en la aplicació oberta, girem el telèfon podem veure com les dades es perden.
Android, per fer que la pantalla s’adapte a les noves dimensions, ha recreat l’Activity i tornat a executar el métode onCreateView
tornat a inicialitzar les nostres dades al valor inicial.
Si no volem que passe aquest fenomen hem d’aprofitar les funcionalitats que ens dona Android per aquesta tasca.
EL primer pas serà guardar el valor de les nostres dades just abans de que s’esborrin.
Android ens proporciona per aquesta tasca el métode onSaveInstanceState
, que si l’implementem ens permet posar les dades en un objecte semblant a un HashMap
anomentat Bundle
que Android guardarà.
El métode quedarà així:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("life1", life1); (1)
outState.putInt("life2", life2);
outState.putInt("poison1", poison1);
outState.putInt("poison2", poison2);
}
-
Guardem les dades en el
Bundle
.
Una volta s’han reconstruït l’activitat i el fragment s’han de recuperar les dades anteriors.
Si treballem amb fragments, com és el nostre cas, la forma de fer-ho és en el métode onCreateView
que té com a paràmetre un objecte de tipus Bundle
anomenat savedInstanceState
.
D’aquesta forma, agafarem aquest objecte, extreurem les dades de l’objecte, inicialitzarem les dades en les que provenen del Bundle
i, per últim, tornarem a actualitzar els contadors.
Exemple:
TextView counter2 = view.findViewById(R.id.textView4);
(1)
if (savedInstanceState != null) {
life1 = savedInstanceState.getInt("life1"); (2)
life2 = savedInstanceState.getInt("life2");
poison1 = savedInstanceState.getInt("poison1");
poison2 = savedInstanceState.getInt("poison2");
updateViews(); (3)
}
View.OnClickListener listener = (View view) -> {
-
Insertem el codi després de fer els
findViewById
i abans de crear els listeners. -
Extraiem les dades
-
Actualitzem els contadors