Activitats

Creació d'una interfície avançada

L’objectiu d’aquesta activitat és implementar una interfície d’usuari avançada a partir de fragments i diversos tipus de widgets amb els quals l’usuari podrà interactuar. També es practicarà l’ús de recursos de text i de notificacions. Crearem per a això una aplicació amb informació sobre vins que seleccionarem a partir d’una llista.

Podeu descarregar els recursos necessaris per dur a terme aquest projecte des del següent enllaç:

Crearem un nou projecte amb les següents característiques:

  • Application name: UIAvancat
  • Company domain: cat.xtec.ioc

El projecte ha de tenir una activitat buida que s’anomenarà MainActivity. Abans de definir aquesta activitat crearem un nou fragment de tipus ‘llista’ des del menú contextual de l’Android Studio. Per fer-ho, feu clic amb el botó dret a l’espai de noms de l’aplicació i seleccioneu New/Fragment/Fragment (List), i a les propietats del fragment desmarqueu els checkboxs i accepteu.

Farem unes petites modificacions sobre el fragment que acabem de crear:

  • Emplenarem el ListAdapter amb un ArrayList de noms de vins.
  • Canviarem el mètode onListItemClick per tal que faci la crida a onFragmentInteraction amb el text de l’element de la llista seleccionat.

El fitxer ItemFragment.java ens quedarà així:

  1. package ioc.xtec.cat.uiavancat;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.support.v4.app.ListFragment;
  6. import android.view.View;
  7. import android.widget.ArrayAdapter;
  8. import android.widget.ListView;
  9.  
  10.  
  11. import java.util.ArrayList;
  12. import java.util.List;
  13.  
  14. public class ItemFragment extends ListFragment {
  15.  
  16. // Llista que conté els vins
  17. List<String> llista;
  18.  
  19. //
  20. private OnFragmentInteractionListener mListener;
  21.  
  22. public ItemFragment() {
  23. }
  24.  
  25. @Override
  26. public void onCreate(Bundle savedInstanceState) {
  27. super.onCreate(savedInstanceState);
  28.  
  29. // Creem una llista amb els vins
  30. llista = new ArrayList<String>();
  31. llista.add("Marqués de Griñon");
  32. llista.add("Château Lamartine Cahors");
  33. llista.add("Cune Rioja White Monopole");
  34.  
  35. // Posem la llista de vins al ListView
  36. setListAdapter(new ArrayAdapter<String>(getActivity(),
  37. android.R.layout.simple_list_item_1, llista));
  38.  
  39. }
  40.  
  41. // Forcem a l'activitat a implementar OnFragmentInteractionListener
  42. @Override
  43. public void onAttach(Activity activity) {
  44. super.onAttach(activity);
  45. try {
  46. mListener = (OnFragmentInteractionListener) activity;
  47. } catch (ClassCastException e) {
  48. throw new ClassCastException(activity.toString()
  49. + " must implement OnFragmentInteractionListener");
  50.  
  51. }
  52. }
  53.  
  54. @Override
  55. public void onDetach() {
  56. super.onDetach();
  57. mListener = null;
  58. }
  59.  
  60.  
  61. // Comportament quan fem clic a un element de la llista
  62. @Override
  63. public void onListItemClick(ListView l, View v, int position, long id) {
  64. super.onListItemClick(l, v, position, id);
  65.  
  66. if (null != mListener) {
  67.  
  68. // Passem com a argument l'string escollit de la llista (agafem l'element de la posició seleccionada)
  69. mListener.onFragmentInteraction(llista.get(position));
  70. }
  71. }
  72.  
  73. // Interfície que han d'implementar les activitats que continguin el fragment
  74. public interface OnFragmentInteractionListener {
  75.  
  76. public void onFragmentInteraction(String item);
  77. }
  78.  
  79. }

Si ens fixem amb els mètodes que ens ha afegit l’Android Studio podem identificar onAttach i onDetach, referents al cicle de vida dels fragments (veure figura): onAttach s’executa quan un fragment s’associa a una activitat, i onDetach quan deixa d’estar-ho. Al codi anterior, quan s’afegeix el fragment a l’activitat es comprova que aquesta implementi OnFragmentInteractionListener; en cas contrari llançarà una ClassCastException.

Podeu consultar més informació sobre el cicle de vida dels fragments a Fragments Lifecycle

Figura Cicle de vida d’un fragment

De la mateixa manera que havíem creat el fragment de llista, afegirem un nou fragment blank que anomenarem DetailsFragment (recordeu-vos de desmarcar els dos checkboxes: Include fragment factory methods i Include interface callbacks). Aquest fragment serà l’encarregat de mostrar els detalls del vi quan fem clic a qualsevol element de la llista.

En aquest fragment recollirem la informació que se’ns passarà quan es faci clic a un element de la llista; així, només necessitem el mètode onCreateView.

El codi del fitxer DetailsFragment.java és el següent:

  1. package ioc.xtec.cat.uiavancat;
  2.  
  3.  
  4. import android.os.Bundle;
  5. import android.support.v4.app.Fragment;
  6. import android.view.LayoutInflater;
  7. import android.view.View;
  8. import android.view.ViewGroup;
  9. import android.widget.RatingBar;
  10. import android.widget.TextView;
  11. import android.widget.Toast;
  12.  
  13.  
  14. public class DetailsFragment extends Fragment {
  15.  
  16.  
  17. public DetailsFragment() {
  18. }
  19.  
  20. @Override
  21. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  22. Bundle savedInstanceState) {
  23.  
  24. // Inflate the layout for this fragment
  25. View view = inflater.inflate(R.layout.fragment_details, container, false);
  26.  
  27. // Instanciem el TextView. Hem de fer servir la variable view creada anteriorment
  28. TextView nomVi = (TextView) view.findViewById(R.id.textNomVi);
  29.  
  30. // Recollim la informació que se'ns ha passat, en aquest cas el nom del vi
  31. Bundle extras = getArguments();
  32. // Posem el nom del vi al textview
  33. nomVi.setText(extras.getString("nomVi"));
  34.  
  35. // Quan seleccionem una puntuació
  36. final RatingBar rating = (RatingBar) view.findViewById(R.id.ratingbar);
  37. rating.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
  38. public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
  39. Toast.makeText(getActivity(), getString(R.string.puntuacio) + " " + rating, Toast.LENGTH_SHORT).show();
  40. }
  41. });
  42.  
  43. return view;
  44.  
  45. }
  46.  
  47. }

L’XML d’aquest fragment contindrà el següent codi:

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. tools:context="ioc.xtec.cat.fragmentsnotificacions.DetailsFragment">
  5.  
  6.  
  7. <LinearLayout
  8. android:orientation="vertical"
  9. android:layout_width="fill_parent"
  10. android:layout_height="fill_parent">
  11. <TextView
  12. android:id="@+id/textNomVi"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:text=""
  16. android:textAppearance="?android:attr/textAppearanceLarge" />
  17.  
  18. <Button
  19. android:id="@+id/btMostra"
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:padding="10dp"
  23. android:background="@drawable/boto_ampolla"
  24. android:onClick="onEtiquetaClicked" />
  25.  
  26.  
  27. <LinearLayout
  28. android:id="@+id/layoutPropietats"
  29. android:layout_width="match_parent"
  30. android:layout_height="wrap_content"
  31. android:orientation="vertical"
  32. android:visibility="invisible">
  33.  
  34. <TextView
  35. android:id="@+id/textDescripcio"
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. android:text="@string/descripcio" />
  39.  
  40.  
  41. <EditText
  42. android:id="@+id/editDescripcio"
  43. android:layout_width="fill_parent"
  44. android:layout_height="86dp"
  45. android:singleLine="false"
  46. android:hint="@string/hintDescripcio"
  47. android:text="Descripció del vi" />
  48.  
  49.  
  50. <CheckBox
  51. android:id="@+id/checkboxTastat"
  52. android:layout_width="wrap_content"
  53. android:layout_height="wrap_content"
  54. android:text="@string/tastat"
  55. android:onClick="onTastatClicked" />
  56.  
  57.  
  58. <RadioGroup
  59. android:layout_width="fill_parent"
  60. android:layout_height="wrap_content"
  61. android:orientation="horizontal">
  62.  
  63. <RadioButton
  64. android:id="@+id/radio_negre"
  65. android:layout_width="wrap_content"
  66. android:layout_height="wrap_content"
  67. android:text="@string/negre"
  68. android:onClick="onRadioButtonClicked" />
  69.  
  70. <RadioButton
  71. android:id="@+id/radio_blanc"
  72. android:layout_width="wrap_content"
  73. android:layout_height="wrap_content"
  74. android:text="@string/blanc"
  75. android:onClick="onRadioButtonClicked" />
  76. </RadioGroup>
  77.  
  78. <ToggleButton
  79. android:id="@+id/toggleAfegit"
  80. android:layout_width="wrap_content"
  81. android:layout_height="wrap_content"
  82. android:textOn="@string/afegit"
  83. android:textOff="@string/noafegit"
  84. android:onClick="onAfegitClicked" />
  85.  
  86.  
  87. <TextView
  88. android:id="@+id/textPuntuacio"
  89. android:layout_width="wrap_content"
  90. android:layout_height="wrap_content"
  91. android:text="@string/puntuacio" />
  92.  
  93. <RatingBar
  94. android:id="@+id/ratingbar"
  95. android:layout_width="wrap_content"
  96. android:layout_height="wrap_content"
  97. android:numStars="5"
  98. android:stepSize="1.0" />
  99.  
  100. <Button
  101. android:id="@+id/btnMP3"
  102. android:layout_width="wrap_content"
  103. android:layout_height="wrap_content"
  104. android:text="MP3"
  105. android:onClick="onMP3Clicked" />
  106. </LinearLayout>
  107.  
  108. </LinearLayout>
  109.  
  110. </FrameLayout>

El botó d’identificador android:id=”@+id/btMostra”, en lloc de contenir text, mostrarà una imatge. En particular, el botó tindrà tres imatges diferents que es mostraran en diferents estats del botó. Al fragment es mostraran les propietats del vi (amb altres widgets). Quan es premi el botó, es mostraran o s’amagaran aquestes propietats.

Per crear un botó personalitzat com aquest, primer de tot haureu de copiar les tres imatges que fareu servir dins de la carpeta del projecte /res/drawable, tal com es mostra a la figura.

Figura

Dins d’aquesta mateixa carpeta haureu de crear un fitxer anomenat boto_ampolla.xml, que haureu d’inserir el següent XML:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <item android:drawable="@drawable/ampolla_press" android:state_pressed="true" />
  4. <item android:drawable="@drawable/ampolla_focus" android:state_focused="true" />
  5. <item android:drawable="@drawable/ampolla_normal" />
  6. </selector>

Aquest codi defineix un recurs drawable (dibuixable) que canviarà la imatge basant-se en l’estat del botó. El primer ítem defineix ampolla_press com la imatge quan el botó està premut (s’ha activat). El segon ítem defineix ampolla_focus com la imatge quan el botó concentra el focus de l’activitat (quan s’ha seleccionat el botó amb les tecles del pad). L’últim ítem correspon a l’estat normal del botó i defineix la imatge ampolla_normal.

L’ordre dels ítems a l’XML és important. Quan s’ha de dibuixar un botó, es van mirant els diferents estats fins que se’n troba un que coincideix. En aquest cas, es comprova si està premut o si concentra el focus, i en cas contrari mostra la imatge normal. Si introduíssim l’ítem d’ampolla normal el primer, com que no té cap tipus de condició sempre es mostraria aquest i no es mostraria cap altra imatge al botó (podeu pensar que és com l’opció default). De fet, si proveu a moure l’ítem normal a la primera posició dins del selector, obtindreu un Warning del compilador dient: “No android:state_ attribute found on <item> 0, later states not reachable” (“no s’ha trobat un atribut d’estat al primer ítem, no s’arribarà als estats posteriors”).

Aquest fitxer XML representa un recurs drawable i quan sigui referenciat des d’un Button pel seu fons (característica android:background del fitxer XML del layout), la imatge es mostrarà en funció d’aquests estats.

Mireu de nou al fitxer del layout XML de la vostra activitat, fixeu-vos en la definició del botó:

  1. <Button
  2. android:id="@+id/btMostra"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content"
  5. android:padding="10dp"
  6. android:background="@drawable/ampolla_boto"
  7. android:onClick="onEtiquetaClicked" />

L’atribut android:background especifica el recurs drawable que voleu fer servir per al fons del botó. Com que està desat a /res/drawable/ampolla_boto.xml, es referencia com @drawable/ampolla_boto. Això canvia el fons definit per defecte pel sistema pel drawable que hem definit.

L’atribut android:onEtiquetaClicked, per la seva part, especifica el nom del mètode de l’activitat que el sistema cridarà quan l’usuari faci clic al botó. Heu de crear aquest mètode a l’activitat; per fer-ho, afegiu el següent codi a MainActivity.java:

  1. // Quan es prem el boto etiqueta
  2. public void onEtiquetaClicked(View v) {
  3.  
  4. //Trobem el layout de les propietats
  5. LinearLayout Layout = (LinearLayout) this.findViewById(R.id.layoutPropietats);
  6.  
  7. //Si és visible, l'amaguem
  8. if (Layout.getVisibility() == LinearLayout.INVISIBLE) {
  9. Layout.setVisibility(LinearLayout.VISIBLE);
  10. } else {
  11. //Si està amagat el mostrem
  12. Layout.setVisibility(LinearLayout.INVISIBLE);
  13. }
  14.  
  15. }

El mètode que s’ha de crear ha de tenir les següents característiques:

  1. Ha de ser públic.
  2. Ha de retornar un void.
  3. Ha de tenir com a únic argument un View.
Figura Vista amb el layout ocult

Al fer clic a un element de la llista, es mostrarà solament el nom del vi i el botó (figura). Al fer clic al botó veurem la resta de widgets com es mostren a la figura figura.

Figura Vista amb el layout visible

Per la resta d’elements de l’XML, a l’haver definit el text mitjançat un recurs d’string i no escrivint directament a l’XML, hem d’afegir els corresponents recursos al fitxer res/values/strings.xml. El contingut d’aquest fitxer serà el següent:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.  
  4. <string name="app_name">UIAvançat</string>
  5. <string name="descripcio">descripció</string>
  6. <string name="hintDescripcio">Aquesta és la descripció del vi</string>
  7. <string name="tastat">tastat</string>
  8. <string name="negre">negre</string>
  9. <string name="blanc">blanc</string>
  10. <string name="afegit">afegit llista</string>
  11. <string name="noafegit">no inclòs</string>
  12. <string name="puntuacio">Puntuació</string>
  13. <string name="notastat">No tastat</string>
  14.  
  15. </resources>

Si posteriorment es vol crear una versió del programa per a un altre idioma, en lloc d’anar substituint totes les cadenes que apareixen pel codi del projecte, simplement substituint aquest fitxer ja tindrem el programa traduït. Per exemple, modificant:

  1. <string name="afegit">afegit llista</string>

per

  1. <string name="afegit">añadido a la lista</string>

estaríeu mantenint el nom del recurs (i per tant, no caldria modificar el codi), encara que substituint el seu valor per la cadena de text en l’idioma que voleu fer servir.

Per a la resta d’elements, haurem de crear els mètodes onTastatClicked(View v), onRadioButtonClicked(View v), onAfegitClicked(View v) i onMP3Clicked(View v) a l'activity:

  1. // Quan fem clic al checkbox
  2. public void onTastatClicked(View v) {
  3.  
  4. if (((CheckBox) v).isChecked()) {
  5. Toast.makeText(MainActivity.this, R.string.tastat, Toast.LENGTH_SHORT).show();
  6. } else {
  7. Toast.makeText(MainActivity.this, R.string.notastat, Toast.LENGTH_SHORT).show();
  8. }
  9. }
  10.  
  11. // Quan fem clic als radio buttons
  12. public void onRadioButtonClicked(View v) {
  13. // Perform action on clicks
  14. RadioButton rb = (RadioButton) v;
  15. Toast.makeText(MainActivity.this, rb.getText(), Toast.LENGTH_SHORT).show();
  16. }
  17.  
  18.  
  19. // Quan fem clic al toggle button
  20. public void onAfegitClicked(View v) {
  21.  
  22. if (((ToggleButton) v).isChecked()) {
  23. Toast.makeText(MainActivity.this, R.string.afegit, Toast.LENGTH_SHORT).show();
  24. } else {
  25. Toast.makeText(MainActivity.this, R.string.noafegit, Toast.LENGTH_SHORT).show();
  26. }
  27. }
  28.  
  29. //Quan es prem el boto MP3
  30. public void onMP3Clicked(View v) {
  31.  
  32. MediaPlayer mp = MediaPlayer.create(MainActivity.this, R.raw.a);
  33. mp.start();
  34.  
  35. mp.setOnCompletionListener
  36. (
  37. new MediaPlayer.OnCompletionListener() {
  38.  
  39. public void onCompletion(MediaPlayer mp) {
  40. }
  41. }
  42. );
  43.  
  44. }

Dels CheckBox i dels ToggleButton fem servir la funció isChecked() per saber si estan o no seleccionats. També es podria fer servir aquesta funció pel RadioButton però en aquest cas simplement obtenim el text de l’element seleccionat amb la funció getText().

Ja tenim els dos fragment definits, sols ens queda enllaçar-los a l’activitat i canviar-los segons la necessitat.

Afegiu el següent codi al layout de l’activitat:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3. android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
  4. android:paddingRight="@dimen/activity_horizontal_margin"
  5. android:paddingTop="@dimen/activity_vertical_margin"
  6. android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
  7.  
  8. <FrameLayout
  9. android:id="@+id/contenidorFragment"
  10. android:layout_width="fill_parent"
  11. android:layout_height="fill_parent"
  12. >
  13. </FrameLayout>
  14. </RelativeLayout>

El FrameLayout ens servirà per afegir els fragment mitjançant codi. Així, quan executem la nostra aplicació aquesta haurà de carregar l'ItemFragment i quan es seleccioni un element de la llista, el DetailsFragment.

Per modificar els fragment en temps d’execució necessitem obtenir el FragmentManager i realitzar una FragmentTransaction sobre aquest. Amb la funció replace identificarem quin fragment volem afegir i on ho farem.

Finalment, farem un commit() de la transacció per executar el canvi.

El mètode onCreate de l’activitat principal quedarà de la següent manera:

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.activity_main);
  5.  
  6.  
  7. // Afegim el fragment principal, el de la llista de vins
  8. ItemFragment listFragment = new ItemFragment();
  9. FragmentManager fragmentManager = getSupportFragmentManager();
  10. FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
  11.  
  12. fragmentTransaction.replace(R.id.contenidorFragment, listFragment);
  13. fragmentTransaction.commit();
  14.  
  15. }

És a dir, quan es creï l’actitivat s’afegirà un ItemFragment a R.id.contenidorFragment.

Sols ens queda implementar la interfície OnFragmentInteractionListener que requereix el fragment.

Haurem de fer les següents modificacions de la nostra activitat:

  • La classe haurà de fer l'implements d'OnFragmentInteractionListener:
  1. public class MainActivity extends ActionBarActivity implements ItemFragment.OnFragmentInteractionListener {
  • Haurem de definir el mètode onFragmentInteraction que respondrà als clics dels elements de la llista:
  1. // Mètode que executa quan fem clic a un element de la llista del fragment
  2. @Override
  3. public void onFragmentInteraction(String item) {
  4. ...
  5. }

Aquest mètode farà el canvi de fragment i a més mostrarà una notificació del sistema indicant quin vi ha estat escollit.

Per realitzar una notificació bàsica en Android necessitem els següents components:

  • NotificationCompat.Builder: ens ajuda a construir l’estructura d’una notificació d’una manera senzilla. Els mètodes setSmallIcon, setContentTitle i setContentText defineixen les parts principals d’una notificació: la icona, el títol i el text respectivament.
  • PendingIntent: és un intent que, al ser executat per una altra aplicació, disposarà dels mateixos permisos i identitat que l’aplicació que l’ha creat.
  • NotificationManager: gestiona les notificacions del sistema.

El codi de la notificació podria ser el següent:

  1. /***************************** NOTIFICACIÓ ****************************/
  2. // Creem la notificació definint una icona, un títol i un text
  3. NotificationCompat.Builder mBuilder =
  4. new NotificationCompat.Builder(this)
  5. .setSmallIcon(R.drawable.ic_action_accept) //icona de la notificació
  6. .setContentTitle("Has seleccionat el vi:") // Títol de la notificació
  7. .setContentText(item); // Text de la notificació (paràmetre d'entrada del mètode)
  8.  
  9. // La notificació es tancarà una vegada hem fet clic
  10. mBuilder.setAutoCancel(true);
  11.  
  12. // Definim quina activitat s'obrirà en fer clic a la notificació
  13. Intent resultIntent = new Intent(this, MainActivity.class);
  14.  
  15. // I creem el pendingIntent
  16. PendingIntent resultPendingIntent =
  17. PendingIntent.getActivity(
  18. this,
  19. 0,
  20. resultIntent,
  21. PendingIntent.FLAG_UPDATE_CURRENT
  22. );
  23.  
  24. mBuilder.setContentIntent(resultPendingIntent);
  25.  
  26. // Li assigmen un ID
  27. int mNotificationId = 001;
  28.  
  29. // Obtenim el notification manager
  30. NotificationManager mNotifyMgr =
  31. (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  32.  
  33. // Construim i mostrem la notificació
  34. mNotifyMgr.notify(mNotificationId, mBuilder.build());
  35.  
  36. /*********************************************************************/
Figura Notificació

Sols ens queda obrir el DetailsFragment amb la informació del vi. En aquest exemple només passem el nom del vi, però el codi es podria adaptar fàcilment per recollir la informació d’una base de dades amb totes les característiques de cada vi.

Crearem el bundle amb el nom del vi i el passarem com a argument del fragment, iniciarem la transició i a més farem la crida al mètode fragmentTransaction.addToBackStack(null); que afegirà el fragment a una pila de fragments, permetent-nos fer clic a la tecla enrere i tornar al fragment anterior.

El codi de onFragmentInteraction quedarà així:

  1. // Mètode que executa quan fem clic en un element de la llista del fragment
  2. @Override
  3. public void onFragmentInteraction(String item) {
  4.  
  5.  
  6. /***************************** NOTIFICACIÓ ****************************/
  7. // Creem la notificació definint una icona, un títol i un text
  8. NotificationCompat.Builder mBuilder =
  9. new NotificationCompat.Builder(this)
  10. .setSmallIcon(R.drawable.ic_action_accept) //icona de la notificació
  11. .setContentTitle("Has seleccionat el vi:") // títol de la notificació
  12. .setContentText(item); // text de la notificació (paràmetre d'entrada del mètode)
  13.  
  14. // La notificació es tancarà una vegada hem fet clic
  15. mBuilder.setAutoCancel(true);
  16.  
  17. // Definim quina activitat s'obrirà al fer clic a la notificació
  18. Intent resultIntent = new Intent(this, MainActivity.class);
  19.  
  20. // I creem el pendingIntent
  21. PendingIntent resultPendingIntent =
  22. PendingIntent.getActivity(
  23. this,
  24. 0,
  25. resultIntent,
  26. PendingIntent.FLAG_UPDATE_CURRENT
  27. );
  28.  
  29. mBuilder.setContentIntent(resultPendingIntent);
  30.  
  31. // Li assigmen un ID
  32. int mNotificationId = 001;
  33.  
  34. // Obtenim el notification manager
  35. NotificationManager mNotifyMgr =
  36. (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  37.  
  38. // Construïm i mostrem la notificació
  39. mNotifyMgr.notify(mNotificationId, mBuilder.build());
  40.  
  41. /*********************************************************************/
  42.  
  43. // Informació a enviar
  44. Bundle data = new Bundle();
  45. data.putString("nomVi", item);
  46.  
  47. // Creem el fragment de detalls
  48. DetailsFragment detailsFragment = new DetailsFragment();
  49.  
  50. // Afegim els arguments al fragment
  51. detailsFragment.setArguments(data);
  52. FragmentManager fragmentManager = getSupportFragmentManager();
  53. FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
  54.  
  55. // Reemplacem el fragment que hi havia al contenidor
  56. fragmentTransaction.replace(R.id.contenidorFragment, detailsFragment);
  57. // Afegim una animació d'obertura de fragment
  58. fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
  59. // Permetem tornar a l'anterior amb la tecla de 'back', afegint el fragment a l'stack de fragments
  60. fragmentTransaction.addToBackStack(null);
  61. // Fem la transició
  62. fragmentTransaction.commit();
  63.  
  64. }

Podeu descarregar el codi de l’aplicació des del següent enllaç:

08_uiavancat.zip ( 3.8 MB )

Interacció amb una llista d'elements

L’objectiu d’aquesta activitat és la creació d’una llista d’elements variables amb la qual podrà interactuar l’usuari.

Les llistes d’elements estan implementades amb dues classes diferents:

  1. Objectes de la classe ListView
  2. Objectes de la classe SpinnerView

Els objectes ListView mostren verticalment una sèrie d’ítems en una llista desplegable. Els elements que es mostren vénen d’un objecte ListAdapter. Els ListAdapters bàsicament serveixen per contenir dades que seran mostrades en un ListView.

Creeu una aplicació que mostri una llista de països i realitzi una acció quan se’n seleccioni un.

Creeu un nou projecte d’Android. En primer lloc, la vostra activitat principal heretarà les propietats de ListActivity i no d‘ActionBarActivity com és habitual.

  1. public class MainActivity extends ListActivity {

La segona cosa que més crida l’atenció és que no necessiteu carregar un fitxer XML amb el layout, com heu fet habitualment. La classe ListActivity conté un ListView i no cal fer una crida a setContentView() des del mètode onCreate(). En lloc d’això, heu de fer una crida a setListAdapter(ListAdapter) per tal d’omplir la pantalla de l’activitat amb un ListView (ho feu programant, en lloc de definir un fitxer XML de layout). L’únic argument és un ListAdapter, que és un objecte que fa de pont entre el ListView i les dades que es mostren a la llista. Per crear un ListAdapter invoqueu el constructor amb el context, un identificador del recurs que fareu servir per cada element de la llista (podeu fer servir un estàndard d’Android com android.R.layout.simple_list_item_1, o definir-ne un de vostre en un fitxer XML) i un Array d’strings que fareu servir per omplir la llista del ListView.

Escriviu el següent codi dins del mètode onCreate().

  1. //Països en una variable estàtica
  2. ArrayAdapter<String> A = new ArrayAdapter<String>(this, R.layout.list_item, PAISOS);
  3. setListAdapter(A);

PAISOS és una variable estàtica que conté un array d’strings amb els noms dels països.

  1. static final String[] PAISOS = new String[] {
  2. "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "...", "Zambia", "Zimbabwe"
  3. };

Podeu afegir com a funcionalitat que es vagin filtrant els noms dels països si comenceu a fer servir el teclat del dispositiu. Per exemple, si voleu trobar Bolívia, en lloc de cercar el país a la llista podeu començar a escriure “BOL” i prémer Intro, i es filtraran els noms dels països que comencen amb aquests caràcters. Per activar aquest filtratge feu servir el mètode que activa el filtre, setTextFilterEnabled().

  1. ListView lv = getListView();
  2. lv.setTextFilterEnabled(true);

Finalment, el mètode setOnItemClickListener(OnItemClickListener) defineix un listener per cada vegada que se selecciona un ítem. En primer lloc, feu una cosa molt senzilla: únicament mostreu el nom del país que s’ha seleccionat. Quan un ítem en el ListView és seleccionat, el mètode onListItemClick() es crida amb l’ítem seleccionat dins de l’argument d’entrada v. L’argument parent és el ListView, i position és la posició de l’ítem seleccionat dins del ListView.

  1. public void onListItemClick(ListView parent, View v, int position, long id) {
  2. //Obtenim el nom del país de l'ítem seleccionat
  3. String pais = ((TextView) v).getText().toString();
  4.  
  5. //Mostra el nom del país
  6. Toast.makeText(getApplicationContext(), pais,Toast.LENGTH_SHORT).show();
  7.  
  8. }

Una aplicació més interessant seria, per exemple, mostrar la pàgina de la Viquipèdia del país que s’ha seleccionat. Per fer-ho, en lloc de mostrar el missatge del país seleccionat, únicament heu de crear un Intent amb l’acció que voleu fer i llançar-lo (l’obrirà el navegador web per defecte).

  1. Intent i = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse("http://en.wikipedia.org/wiki/"+pais));
  2. startActivity(i);

La forma en què heu definit l’array d’strings que conté els països no és la millor. En cas que vulgueu definir els noms en un altre idioma, hauríeu de modificar el programa i tornar a compilar-lo. La forma més adequada seria afegir un recurs de text amb els noms dels països i fer-lo servir dins del nostre codi. Per fer-ho, definiu dins del fitxer /res/values/strings.xml el següent recurs:

  1. <string-array name="countries_array">
  2.  
  3. <item>Afghanistan</item>
  4. <item>Albania</item>
  5. <item>Algeria</item>
  6. <item>American Samoa</item>
  7. <item>...</item>
  8. <item>Zambia</item>
  9. <item>Zimbabwe</item>
  10.  
  11. </string-array>

Per carregar la llista de països al ListAdapter feu servir el següent codi, en substitució de l’anterior:

  1. //Països definits a strings.xml
  2.  
  3. String[] paisos = getResources().getStringArray(R.array.countries_array);
  4.  
  5. ArrayAdapter<String> A = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, paisos);
  6.  
  7. setListAdapter(A);

Podeu configurar el vostre ListView perquè es comporti de diferents maneres. Ja heu vist com carregar els strings des d’un recurs i com realitzar una acció (per exemple, obrir una pàgina de la Viquipèdia). A vegades és interessant poder seleccionar més d’un element dins del ListView. Per fer-ho, heu de fer les següents modificacions al vostre programa. Modifiqueu les línies de codi per fer els següents canvis.

En primer lloc, heu de modificar la definició del ListAdapter, per tal que els elements que es mostrin al ListView presentin una marca que indicarà si estan seleccionats o no:

  1. ArrayAdapter<String> A = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_checked, paisos);

En segon lloc, heu de definir el ListView de manera que es puguin fer seleccions múltiples sobre els ítems de la llista:

  1. lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

Finalment, podeu mostrar un missatge que depengui de si l’element ha estat marcat o desmarcat. Podeu fer servir el mètode isItemChecked(position) del ListView per saber si un ítem està marcat o no:

  1. if(parent.isItemChecked(position)) {
  2. Toast.makeText(getApplicationContext(), pais +
  3. " marcat",Toast.LENGTH_SHORT).show();
  4. } else {
  5. Toast.makeText(getApplicationContext(), pais +
  6. " desmarcat",Toast.LENGTH_SHORT).show();
  7. }

Ara podeu anar seleccionant diferents ítems del ListView, tal com es pot veure a la figura.

Figura

Descarregueu tot seguit el document ListView.zip, que conté el codi de la solució:

09_listview.zip ( 259.3 KB )

Persistència de dades

L’objectiu d’aquesta activitat és crear una aplicació amb una sèrie de dades de configuració que mantindran persistència entre execucions de l’aplicació.

Per tal d’introduir nous widgets, feu una aplicació que contingui un text i una barra que serveixi per modificar la mida del text dins de la barra. Quan es tanqui l’aplicació es desaran els valors de preferència de l’aplicació (el text i la mida del text), i quan es torni a carregar l’aplicació aquesta tornarà a carregar aquests valors i a mostrar les mateixes dades de quan es va tancar.

El disseny de layout de l’aplicació és el següent:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical" >
  6.  
  7. <SeekBar
  8. android:id="@+id/SeekBar"
  9. android:layout_width="fill_parent"
  10. android:layout_height="wrap_content"
  11. />
  12.  
  13. <TextView
  14. android:id="@+id/TextView"
  15. android:layout_width="fill_parent"
  16. android:layout_height="wrap_content"
  17. android:text="Nom"
  18. />
  19.  
  20. <EditText
  21. android:id="@+id/EditText"
  22. android:layout_width="fill_parent"
  23. android:layout_height="wrap_content"
  24. />
  25.  
  26. </LinearLayout>

El següent codi afegit a onCreate() serveix per modificar la mida del text amb el valor obtingut de la barra de desplaçament (SeekBar).

  1. barraMida = (SeekBar) findViewById(R.id.SeekBar);
  2. editText = (EditText) findViewById(R.id.EditText);
  3.  
  4. barraMida.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
  5.  
  6. //@Override
  7. public void onStopTrackingTouch(SeekBar seekBar){}
  8.  
  9. //@Override
  10. public void onStartTrackingTouch(SeekBar seekBar){}
  11.  
  12. //@Override
  13. public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  14. editText.setTextSize(progress);
  15. }
  16.  
  17. }
  18. );

El que fa aquest codi és crear un listener que s’activarà quan es canviï el valor de la barra. Amb aquest valor (progress) es modifica la mida del text. Però si executeu aquest codi, quan es tanqui l’aplicació, el text i la seva mida tornaran a tenir els valors per defecte. El que voleu és desar aquests valors per poder recuperar-los la propera vegada que es carregui l’aplicació. Per fer-ho, heu de desar les preferències en el moment que l’aplicació deixi d’estar en primer pla.

Aquest seria el codi que ho fa:

  1. @Override
  2. protected void onPause() {
  3. super.onPause();
  4. //Obtenim les preferències
  5. SharedPreferences settings = getSharedPreferences(NOM_PREFERENCIES, MODE_PRIVATE);
  6. SharedPreferences.Editor editor = settings.edit();
  7.  
  8. //Desem el nom
  9. editText = (EditText) findViewById(R.id.EditText);
  10. editor.putString(CLAU_TEXT, editText.getText().toString());
  11.  
  12. //Desem la mida del text
  13. barraMida = (SeekBar) findViewById(R.id.SeekBar);
  14. editor.putInt(CLAU_MIDA_TEXT, barraMida.getProgress());
  15.  
  16. //Confirma els canvis
  17. editor.commit();
  18.  
  19. }
  20. }

S’han creat prèviament unes constants de text per fer-les servir quan necessiteu especificar una cadena (pel fitxer configuració o les claus dels elements que deseu al fitxer, per exemple):

  1. private static final String NOM_PREFERENCIES = "Preferencies";
  2. private static final String CLAU_MIDA_TEXT = "MidaText";
  3. private static final String CLAU_TEXT = "NomText";

El que fa aquest codi és obtenir l’objecte de preferències i l’editor, i desar les dades amb les claus CLAU_TEXT i CLAU_MIDA_TEXT per als continguts de la caixa de text i la posició de la barra de desplaçament.

El següent pas seria carregar aquests valors quan s’engegui l’aplicació. Per això hauríeu d’afegir el següent codi a onCreate():

  1. barraMida = (SeekBar) findViewById(R.id.SeekBar);
  2. editText = (EditText) findViewById(R.id.EditText);
  3.  
  4. //Carregar les preferències
  5. SharedPreferences prefs = getSharedPreferences(NOM_PREFERENCIES, MODE_PRIVATE);
  6.  
  7. //Barra de mida
  8. int midaFont = prefs.getInt(CLAU_MIDA_TEXT, 12);
  9. barraMida.setProgress(midaFont);
  10.  
  11. //TextView
  12. editText.setText(prefs.getString(CLAU_TEXT, ""));
  13. editText.setTextSize(midaFont);

Descarregueu el document 10_userpreferences.zip, que conté el codi de la solució:

Anar a la pàgina anterior:
Programació avançada
Anar a la pàgina següent:
Programació avançada