În situația în care aplicația Android operează cu un volum semnificativ informații provenind din diverse surse de date, trebuie utilizat un set de obiecte specializate pentru gestiunea lor:
android.widget.AdapterView
) responsabil pentru afișarea acestora pe ecran; el conține câte un element de tip android.view.View
pentru fiecare element al listei, oricât de complex;android.widget.Adapter
) care asigură Această arhitectură corespunde șablonului MVC (Model - View - Controller) în care modelul este reprezentat de sursele de date, vizualizarea de componenta grafică în care este afișat conținutul acestora iar controlorul de clasa adaptor.
Pentru afișarea colecțiilor de date se folosesc elemente grafice de tip container, referite sub denumirea de liste. Acestea controlează dimensiunile și modul de dispunere al componentelor, structura unei componente fiind însă gestionată de elementul de tip adaptor asociat. Vor fi prezentate inițial particularitățile pe care le pun la dispoziție fiecare dintre aceste controale, detaliile de implementare referitoare la tipurile de adaptoare asociate fiind tratate ulterior.
Cele mai utilizate tipuri de containere pentru colecțiile de date sunt ListView
, GridView
, Spinner
și Gallery
.
Un obiect de tip ListView afișează elementele pe care le conține vertical, existând posibilitatea ca acestea să fie derulate în situația în care se depășește dimensiunea suprafeței de afișare.
Elementele pot fi separate de anumite delimitatoare (resursă grafică sau culoare) specificate prin proprietatea divider
(pentru care se poate preciza și o înălțime - atributul dividerHeight
). Un astfel de control poate fi precedat, respectiv succedat de un control grafic, existând posibilitatea distingerii acestor elemente prin delimitatoare (proprietățile headerDividersEnabled
, respectiv footerDividersEnabled
).
Dacă este folosit într-o activitate obișnuită, controlul de tip ListView
trebuie să fie specificat în interfața grafică din fișierul XML asociat ferestrei din care face parte, precizându-se și un identificator prin intermediul căruia să poată fi referit:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MoviesActivity" > <ListView android:id="@+id/movies_list_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:divider="@color/lightblue" android:dividerHeight="5dp" /> </RelativeLayout>
Elementele (de tip View
) pe care le afișează un astfel de obiect sunt furnizate de un ListAdapter
care trebuie precizat prin intermediul metodei setAdapter(ListAdapter)
. În codul sursă, se va specifica un tip de adaptor care va fi inițializat cu datele pe care le va conține obiectul respectiv, după care acesta va fi asociat controlului grafic.
public class MoviesActivity extends Activity { String[] movies = new String[] { "The Shawshank Redemption", "The Godfather", "The Godfather: Part II", "The Dark Knight", "Pulp Fiction", "The Good, the Bad and the Ugly", "Schindler's List", "12 Angry Men", "The Lord of the Rings: The Return of the King", "Fight Club" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_movies); ListView listview = (ListView)findViewById(R.id.movies_list_view); final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, movies); listview.setAdapter(adapter); } }
Activitatea MoviesActivity
afișează denumirea unor filme sub forma unei liste în care elementele sunt dispuse unele sub altele.
AdapterView
sau se pot folosi layout-uri definite de utilizator (pentru structuri de date mai complexe ce implică afișarea informației într-un format dependent de acest context).
ListView
se vor realiza prin intermediul obiectului ListAdapter
.
În cazul în care se produc anumite evenimente ce implică interacțiunea utilizatorului asupra acestui control, ele vor fi tratate prin clase ascultător distincte pentru fiecare tip de acțiune (apăsarea - de scurtă durată sau de lungă durată - asupra unui element din cadrul listei, selectarea unei intrări).
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "You have clicked on "+adapter.getItem(position)+" item", Toast.LENGTH_SHORT).show(); } }); listview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "You have long clicked on "+adapter.getItem(position)+" item", Toast.LENGTH_SHORT).show(); return true; } }); listview.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "You have selected the "+adapter.getItem(position)+" item", Toast.LENGTH_SHORT).show(); } @Override public void onNothingSelected(AdapterView<?> parent) { Toast.makeText(MainActivity.this, "Currently, the list has no item selected", Toast.LENGTH_SHORT).show(); } });
Aceste metode de tratare a evenimentelor din clasele ascultător primesc ca parametri:
AdapterView
, indicând lista din care a fost accesat un element;View
, reprezentând elementul din cadrul listei care a fost accesat;
getItemId()
); această valoare este dependentă atât de obiectul de tip adaptor folosit, cât și de sursa de date
Conținutul listei poate fi precizat și în fișierul de resurse /res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Top 10 Movies</string> <string-array name="movies"> <item>The Shawshank Redemption</item> <item>The Godfather</item> <item>The Godfather: Part II</item> <item>The Dark Knight</item> <item>Pulp Fiction</item> <item>The Good, the Bad and the Ugly</item> <item>Schindler's List</item> <item>12 Angry Men</item> <item>The Lord of the Rings: The Return of the King</item> <item>Fight Club</item> </string-array> </resources>
și apoi
ListView
<ListView ... android:entries="@array/movies" ... />
(situație în care obiectul de tip adaptor nu va fi instanțiat, fiind preluat cel creat în mod automat cu intrările din resursa de tip tablou specificată în fișierul XML)
sau
movies = getResources().getStringArray(R.array.movies);
Dacă se dorește ca elementele din listă să poată fi selectate, în constructorul obiectului de tip adaptor se va specifica un model care suportă o astfel de operație android.R.layout.simple_list_item_checked
.
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_checked, movies);
Numărul de componente din cadrul listei care pot fi selectate concomitent pot fi:
ListView.CHOICE_MODE_NONE
ListView.CHOICE_MODE_SINGLE
ListView.CHOICE_MODE_MULTIPLE
Aceste constante vor fi transmise ca parametru metodei setChoiceMode()
definită de clasa ListView
.
Pentru a se determina intrările care au fost selectate de utilizator, va fi parcursă întreaga listă (numărul de elemente fiind obținut prin metoda getCount()
), starea în care se găsesc acestea fiind verificată prin intermediul meteodei isItemChecked(int)
care primește ca parametru poziția din listă pentru care se dorește să se realizeze textul.
String itemsSelected = "You have selected the following items:\n"; for (int position = 0; position < listview.getCount(); position++) if (listview.isChecked(position)) { itemsSelected += " * "+listview.getItemAtPosition(position)+"\n"; }
Totodată, există posibilitate de filtrare a conținutului unei liste în funcție de o valoare dată de utilizator, comportament ce poate fi obținut printr-un apel al metodei setFilterEnabled(boolean)
.
Un caz particular de listă este ExpandableListView
, care permite asocierea conținuturilor în grupuri, acestea putând fi expandate.
Elementele sunt însoțite de un indicator care specifică starea sa (grup - expandat sau nu, respectiv element al grupului, evidențiindu-se ultimul din sublistă), existând astfel elemente de demarcație în modurile de dispunere implicite, ele putând fi de asemenea precizate de utilizator prin intermediul proprietăților groupIndicator
, respectiv childIndicator
.
Layout-urile predefinite pentru acest obiect sunt android.R.layout.simple_expandable_list_item_1
și android.R.layout.simple_expandable_list_item_2
.
GridView este un tip de obiect folosit pentru afișarea conținuturilor sub formă tabulară, existând posibilitatea de derulare a acestora în cazul în care se depășește dimensiunea suprafeței de afișare.
Se poate preciza numărul de coloane prin proprietatea numColumns
, aceasta având de regulă valoarea AUTO_FIT
, astfel încât să se determine în mod automat acest atribut în funcție de conținutul tabelului și de spațiul avut la dispoziție. Dimensiunea fiecărei coloane poate fi specificată explicit (prin columnWidth
- dându-se o valoare însoțită de o unitate de măsură). De asemenea, în condițiile în care dimensiunile spațiului de afișare permit, tabelul poate fi extins în funcție de valoarea indicată pentru proprietatea stretchMode
(spacingWidth
/ spacingWidthUniform
pentru extinderea spațiului dintre coloane, respectiv columnWidth
pentru extinderea coloanelor). Spațierea dintre rânduri și coloane este controlată prin horizontalSpacing
și verticalSpacing
.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ContactsActivity" > <GridView android:id="@+id/contacts_grid_view" android:layout_width="match_parent" android:layout_height="match_parent" android:horizontalSpacing="10dp" android:verticalSpacing="10dp" android:numColumns="auto_fit" android:gravity="center" > <!-- grid layout content --> </GridLayout> </RelativeLayout>
public class ContactsActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> { GridView gridview; SimpleCursorAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); gridview = (GridView)findViewById(R.id.contacts_grid_view); String[] projections = new String[] {Contacts.DISPLAY_NAME}; int[] views = new int[] {android.R.id.text1}; adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, null, projections, views, 0); gridview.setAdapter(adapter); getLoaderManager().initLoader(0, null, this); } public Loader<Cursor> onCreateLoader(int id, Bundle data) { return new CursorLoader(this, Contacts.CONTENT_URI, null, null, null, Contacts.DISPLAY_NAME); } public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { adapter.swapCursor(cursor); } public void onLoaderReset(Loader<Cursor> loader) { adapter.swapCursor(null); } }
Activitatea ContactsActivity
afișează numele persoanelor din lista de contacte a telefonului sub forma unui tabel. Având în vedere faptul că volumul de informații poate fi semnificativ, încărcarea acestora nu se face în cadrul activității (pentru a nu genera situații de neresponsivitate a interfeței grafice), ci prin intermediul unui obiect de tip Loader
.
AndroidManifest.xml
:<uses-permission android:name=“android.permission.READ_CONTACTS” />
În cadrul elementului de tip Spinner, nu se afișează întregul conținut al listei (astfel încât să nu se ocupe întregul spațiu disponibil), ci numai valoarea selectată în mod curent, existând posibilitatea expandării pentru vizualizare a tuturor componentelor în momentul accesării acesteia.
Modul în care sunt afișate intrările dintre care se poate realiza selecția este controlat prin proprietatea spinnerMode
care poate lua valorile dialog
dacă se dorește afișarea unei ferestre de dialog, respectiv dropdown
dacă lista va fi expandată sub controlul care conține valoarea curentă.
dialog
, se poate indica un mesaj suplimentar care va fi afișat în cadrul ferestrei ce prezintă toate opțiunile ce pot fi selectate prin intermediul atributului prompt
dropdown
pot fi specificate în plus următoarele proprietățidropDownHorizontalOffset
/ dropDownVerticalOffset
indică valoarea cu care va fi decalată pe orizontală / verticală lista derulante ce conține celelalte opțiunidropDownSelector
este o referință către o resursă pentru a indica modul de selecție a unui element din cadrul listeidropDownWidth
reprezintă lățimea listei derulante ce conține celelalte opțiuni (poate avea valorile match_parent
sau wrap_content
)popupBackground
specifică o resursă ce va fi desenată pe fundal atâta vreme cât este afișată lista derulantă ce conține celelalte opțiuni
<Spinner android:id="@+id/spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:entries="@array/device_manufacturer" android:spinnerMode="dialog" android:prompt="@string/prompt" /> |
<Spinner android:id="@+id/spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:entries="@array/device_manufacturer" android:spinnerMode="dropdown" android:dropDownHorizontalOffset="10dp" android:dropDownVerticalOffset="10dp" android:popupBackground="@drawable/mobiledevice" android:dropDownSelector="@color/lightblue" /> |
Pentru un astfel de obiect vor trebui specificate două mecanisme de afișare a conținutului:
android.R.layout.simple_spinner_item
;setDropDownViewResource
a obiectului adaptor; valoarea predefinită este android.R.layout.simple_spinner_dropdown_item
.Spinner spinner = (Spinner)findResourceById(R.id.spinner); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, deviceManufacturers); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter);
Elementul de tip Gallery este un tip de control folosit pentru afișarea conținutului în cadrul unei liste dispuse orizontal, în care obiectul selectat este întotdeauna centrat. De obicei, este folosit pentru resurse multimedia de tip imagine.
HorizontalScrollView
și ViewPager
.
Pentru un Gallery
pot fi specificate următoarele atribute:
animationDuration
- durata animației de tranziție de la un element la altul, exprimată în milisecundespacing
- spațierea dintre elementele galeriei; această proprietate se folosește mai ales atunci când nu se specifică un anumit stilunselectedAlpha
- nivelul de transparență pentru elementele care nu sunt selectate, exprimată ca număr zecimal, cuprins între 0 și 1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".FacultyActivity" > <TextView android:id="@+id/textview" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="@string/app_name" android:textSize="16sp" /> <Gallery android:id="@+id/gallery" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:animationDuration="500" android:spacing="5dp" android:unselectedAlpha="0.5" /> <ImageView android:id="@+id/imageview" android:layout_gravity="center" android:padding="10dp" android:layout_width="320dp" android:layout_height="240dp" android:scaleType="fitXY" android:contentDescription="@string/content_description" /> </LinearLayout>
În situația în care conținutul redat de acest element nu este de tip text, va trebui utilizat un tip de adaptor definit de utilizator, care specifică modul în care vor fi afișate astfel de elemente. În cadrul acesteia, pentru fiecare obiect conținut trebuie să se specifice un mecanism de dispunere prin intermediul clasei Gallery.LayoutParams
(specificându-se mai ales dimensiunile elementului care se dorește a fi afișat).
public class FacultyActivity extends Activity { Integer[] facultyImages = { R.drawable.faculty01, R.drawable.faculty02, R.drawable.faculty03 // add resources for other faculties }; class GalleryAdapter extends BaseAdapter { Context context; public ImageAdapter(Context context) { this.context = context; } public int getCount() { return facultyImages.length; } public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { ImageView imageview; if (convertView == null) { imageview = new ImageView(context); imageview.setImageResource(facultyImages [position]); imageview.setScaleType(ImageView.ScaleType.FIT_XY); imageview.setLayoutParams(new Gallery.LayoutParams(160, 120)); } else { imageview = (ImageView)convertView; } return imageview; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_faculty); Gallery gallery = (Gallery)findViewById(R.id.gallery); gallery.setAdapter(new GalleryAdapter(this)); gallery.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ImageView imageview = (ImageView)(findViewById(R.id.imageview)); imageview.setImageResource(facultyImages [position]); } }); } }
În cazul unei colecții de date, rolul unui adaptor este de a gestiona modelul de date transpunându-l (adaptându-l) la fiecare element din cadrul interfeței grafice (listă sau tabel). Toate adaptoarele (predefinite sau definite de utilizator) sunt derivate din clasa BaseAdapter
.
În funcție de proveniența datelor, se vor utiliza tipuri de adaptoare specializate:
ArrayAdapter<T>
- pentru date reținute sub forma unui tablou (de tipul T[]
sau java.util.List<T>
)SimpleCursorAdapter
- pentru date preluate de la un furnizor de conținut (de exemplu, o bază de date)
Acestea au asociate anumite moduri de dispunere a conținutului, predefinite în android.R.layout
, adaptate la tipul de control grafic utilizat.
De acele mai multe ori, pentru structuri de date complexe ce trebuie afișate folosind mecanisme de afișare particularizate pentru asigurarea unei anumite funcționalități, trebuie utilizat un tip de adaptor definit de utilizator, ce suprascrie metodele clasei BaseAdapter
.
Clasa ArrayAdapter este utilizată pentru gestiunea unui tablou sau a unei liste de obiecte Java.
Fiecare element din cadrul acesteia va corespunde unui element din cadrul controlului grafic, conținutul acestuia fiind generat de rezultatul metodei toString()
a clasei din care fac parte obiectele componente a vectorului sau a listei respective. Identificatorul textului de tip TextView
în cadrul căruia se va afișa conținutul referit de adaptor poate fi specificat în constructorul acestuia, în caz contrar folosindu-se un element predefinit (al cărui identificator este android.R.id.text1
).
Instanțierea unui obiect de tip ArrayAdapter<T>
se face prin specificarea cel puțin a contextului în care acesta va fi afișat (activitate, fragment) și a identificatorului unei resurse de tip layout care va fi utilizată pentru dispunerea conținutului său (aceasta putând fi predefinită sau definită de utilizator). În plus, se pot preciza identificatorul unui control de tip TextView
în cadrul căruia va fi plasat fiecare element al colecției, precum și obiectele propriu-zise, sub formă de tablou sau de listă.
ArrayAdapter(Context context, int resource); ArrayAdapter(Context context, int resource, int textViewResourceId); ArrayAdapter(Context context, int resource, T[] objects); ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects); ArrayAdapter(Context context, int resource, List<T> objects); ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects);
ArrayAdapter<CharSequence>
se face prin intermediul metodei statice createFromResource(Context, int, int)
ce primește ca parametri identificatorul resursei care conține colecția de date (de tipul R.array….
) și identificatorul modului de dispunere al elementelor.
Conținutul colecției de date poate fi modificat prin intermediul obiectului de tip ArrayAdapter<T>
numai în situația în care tipul sursei de date suportă acest tip de operații (tablourile nu suportă modificarea prin intermediul adaptorului, în timp ce obiectele de tip listă parametrizată implementează această acțiune).
ArrayAdapter<T>
add(T)
- adaugă un obiectaddAll(Collection<? extends T>)
- adaugă o colecție de obiecteclear()
- șterge toate obiecteleinsert(T, int)
- adaugă un obiect la o anumită pozițieremove(T)
- șterge un anumit obiectnotifyDataSetChanged()
, astfel încât operațiile realizate asupra sursei de date să se propage și la nivelul controlului grafic care îi afișează conținutul.
add
, addAll
, clear
, insert
, remove
) apelează în mod automat notifyDataSetChanged()
. Acest comportament poate fi suprascris prin intermediul metodei setNotifyOnChange(boolean)
.
sort(Comparator<? super T>)
.
Pentru o listă ce afișează coordonatele unor puncte pe ecran în formatul (x, y)
(dat de implementarea metodei toString()
a clasei Point
), gestiunea conținutului se poate face atât prin intermediul colecției de date (de tip ArrayList<Point>
) cât și prin intermediul obiectului adator, de tip ArrayAdapter<Point>
.
class Point { double x, y; public Point() { x = 0; y = 0; } public Point(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } public String toString() { return "("+x+", "+y+")"; } }
ListView listview = (ListView)findViewById(R.id.listview); ArrayList<Point> points = new ArrayList<Point>(); ArrayAdapter<Point> adapter = new ArrayAdapter<Point>(this, android.R.layout.simple_list_item_1, points); listview.setAdapter(adapter);
|
În cazul în care se dorește afișarea conținutului în cadrul unei intefețe grafice mai complexe decât un simplu TextView
, se poate defini o clasă derivată din ArrayAdapter<T>
care va suprascrie metoda responsabilă cu element unui obiect al listei, cunoscându-se poziția sa în cadrul acesteia
public View getView (int position, View convertView, ViewGroup parent);
unde
position
este poziția elementului care este afișat în mod curentconvertView
este un obiect de tip View
(dintre cele create anterior, dar care nu mai este vizibil) ce poate fi reutilizat pentru construirea elementului curentparent
este un obiect de tip ViewGroup
ce reprezintă container-ul în care va fi conținut elementul
În cadrul acestei metode, se va construi un obiect de tip View
(sau se va reutiliza parametrul convertView
, pentru optimizarea memoriei folosite) având modul de dispunere dorit care va fi returnat ca rezultat al metodei.
Astfel, dacă se dorește ca pentru fiecare punct să se afișeze o imagine corespunzătoare cadranului din care face parte, se va defini un tip de layout pentru fiecare element al listei:
<?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/imageview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="2dp" android:contentDescription="@string/content_description" /> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
Implementarea clasei extinse din ArrayAdapter<Point>
va trebui să redefinească metoda getView(int, View, ViewGroup)
care va întoarce un control grafic conținând un element al listei, format dintr-o imagine sugestivă pentru tipul de cadran (obiect de tip ImageView
) și un câmp text conținând coordonatele punctului (obiect de tip TextView
).
class PointArrayAdapter extends ArrayAdapter<Point> { private Context context; private int resource; private ArrayList<Point> content; public PointArrayAdapter(Context context, int resource, ArrayList<Point> content) { super(context, resource, content); this.context = context; this.resource = resource; this.content = content; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if (convertView == null) { LayoutInflater layoutinflator = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = layoutinflator.inflate(resource, null); } else { view = convertView; } ImageView imageview = (ImageView)view.findViewById(R.id.imageview); TextView textview = (TextView)view.findViewById(R.id.textview); Point point = content.get(position); if (point.getX() >= 0 && point.getY() >= 0) { imageview.setImageResource(R.drawable.firstquadrant); } else if (point.getX() < 0 && point.getY() >= 0) { imageview.setImageResource(R.drawable.secondquadrant); } else if (point.getX() < 0 && point.getY() < 0) { imageview.setImageResource(R.drawable.thirdquadrant); } else if (point.getX() >= 0 && point.getY() < 0) { imageview.setImageResource(R.drawable.forthquadrant); } textview.setText(point.toString()); return view; } }
convertView
în cazul în care acesta nu este null
. Conținutul său este cel al unui control grafic instanțiat anterior care nu mai este vizibil pe ecran. În felul acesta se asigură optimizarea memoriei folosite dar și a timpului de execuție, întrucât nu se mai creează obiecte de tip View
în cazul în care acestea există deja.
În cadrul activității va fi definit un obiect de tip adaptor care va primi ca parametri contextul (fereastra), identificatorul modului de dispunere pentru componentele listei (R.layout.pointlayout
) și conținutul acesteia.
setContentView(R.layout.activity_main); ListView listview = (ListView)findViewById(R.id.listview); ArrayList<Point> content = new ArrayList<Point>(); // add points to content PointArrayAdapter adapter = new PointArrayAdapter(this, R.layout.pointlayout, content); listview.setAdapter(adapter);
În situația în care se lucrează cu furnizori de conținut sau cu baza de date din cadrul sistemului de operare Android, este recomandat să se utilizeze un obiect de tipul SimpleCursorAdapter, care asociază rezultatele obținute în urma interogării unor astfel de resurse la controale grafice de tip TextView
sau ImageView
existente în layout-uri predefinite sau definite de utilizator.
Maparea între cele două tipuri se resurse este realizată în două etape:
SimpleCursorAdapter.ViewBinder
disponibil, se apelează metoda setViewValue(View, Cursor, int)
true
, asocierea a fost realizatăfalse
TextView
se apelează metoda setViewText(TextView, String)
ImageView
se apelează metoda setViewImage(ImageView, String)
IllegalStateException
Constructorul clasei SimpleCursorAdapter
public SimpleCursorAdapter (Context context, int layout, Cursor cursor, String[] from, int[] to, int flags);
primește următorii parametri:
context
- activitatea / fragmentul în contextul căruia este afișat controlul grafic conținând rezultatul interogării conținut de cursorlayout
- identificatorul către o resursă predefinită sau definită de utilizator care descrie modul în care sunt vizualizate elementele din cadrul cursoruluicursor
- cursorul conținând rezultatul interogării din sursa de date
null
dacă nu este încă disponibil.
from
- denumirile coloanelor din cadrul cursorului (proiecția) care vor fi afișate în cadrul controlului graficto
- tablou conținând identificatorii elementelor grafice (predefinite / definite de utilizator) în cadrul cărora vor fi afișate valorile extrase din interogare
from
și to
identifică maparea între cursor și view, relația între componentele acestora fiind de 1:1.
flags
- stabilesc comportamentul elementului de tip adaptor, putând avea valorileFLAG_AUTO_REQUERY
- utilizarea sa este descurajată începând cu API 11, forțează cursorul să realizeze interogarea din nou atunci când se primește o notificare cu privire la modificarea conținutului sursei de date; întrucât operația se realizează în contextul interfeței grafice, poate determina o diminuare a responsivității aplicațieiFLAG_REGISTER_CONTENT_OBSERVER
- determină asocierea unui obiect pentru monitorizarea conținutului sursei de date.
De exemplu, dacă se dorește afișarea unor informații din calendarul asociat contului cu care a fost înregistrat dispozitivul mobil, va fi interogată resursa respectivă (specificându-se URI-ul prin care poate fi accesată, lista cu denumirile coloanelor ce trebuie obținute, condiția de selecție a rezultelor și parametrii acesteia (în cazul unei interogări parametrizate) precum și criteriile de sortare a informațiilor). Rezultatul va fi plasat într-un obiect de tip Cursor
. Se instanțiază apoi un obiect de tip SimpleCursorAdapter
ce primește ca parametrii contextul curent (activitatea), modalitatea de dispunere a informațiilor (predefinită, listă având conținutul afișat pe două linii), cursorul ce conține rezultatul interogarii (obținut anterior), precum și listele cu denumirile coloanelor, respectiv identificatorii controalelor grafice în care va fi plasat conținutul acestora.
AndroidManifest.xml
<uses-permission android:name=“android.permission.READ_CALENDAR” />
public class CalendarActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Cursor cursor = getContentResolver().query( Calendars.CONTENT_URI, new String[]{Calendars._ID, Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_TIME_ZONE}, null, null, null); SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, android.R.layout.two_line_list_item, cursor, new String[]{Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_TIME_ZONE}, new int[] {android.R.id.text1, android.R.id.text2}, 0); ListView listview = (ListView)findViewById(R.id.listview); listview.setAdapter(adapter); } }
Obiectul de tip SimpleAdapter este utilizat atunci când se realizează asocieri între date statice și elemente ale interfeței grafice, definite în cadrul unor moduri de dispunere (predefinite sau definite de utilizator). Datele ce vor fi afișate au structura unei liste (List
) al cărei conținut constă în mapări (Map
) de tip (atribut, valoare)
. Arhitectura corespunde unui tablou de entități caracterizate prin anumiți parametri. Fiecare astfel de obiect va fi afișat în cadrul unui element din cadrul controlului cu care va interacționa utilizatorul.
Mecanismul de asociere între sursa de date și interfața grafică se realizează de asemenea în două etape, folosind clasa internă SimpleAdapter.ViewBinder
, astfel:
setViewValue(View, Object, String)
Checkbox
, TextView
, respectiv ImageView
Constructorul clasei SimpleAdapter
se evidențiază prin structura sursei de date care va fi afișată, având forma List<? extends Map<String, ?>>
(este obligatoriu ca tipul cheii din asociere să fie șir de caractere).
public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to);
from
) trebuie să corespundă cheilor din asocierea conținută în listă (obiectul de tip Map<String, ?>
).
De exemplu, pentru entitatea StaffMember
cu atributele name
și position
se dorește afișarea elementelor de acest tip (care se pot regăsi local, în /assets/staffmember.xml
sau la distanță, pe un server) în cadrul unei liste, se poate folosi un obiect de tip SimpleAdapter
. Utilitarul care va parsa fișierul XML va întoarce un obiect ArrayList<HashMap<String, String>>
, unde fiecare element este o proprietate a entității (nume sau poziție). Cu un astfel de rezultat se poate construi un obiect de tip SimpleAdapter
, specificându-se totodată un identificator al tipului de layout, atributele care vor fi afișate precum și controalele cărora le vor fi repartizate.
public class StaffMemberActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_staff_member); try { InputStream inputstream = getAssets().open("staffmember.xml"); StaffXmlParser staffxmlparser = new StaffXmlParser(); List<Map<String,?>> staffmembers = staffxmlparser.parse(inputstream); SimpleAdapter adapter = new SimpleAdapter( this, staffmembers, android.R.layout.simple_list_item_2, new String[] {"name", "position"}, new int[] {android.R.id.text1, android.R.id.text2}); ListView listview = (ListView)findViewById(R.id.listview) listview.setAdapter(adapter); } catch (Exception exception) { Log.println(Log.ERROR, "exception", exception.getMessage()); } } }
Există situații în care funcționalitatea pusă la dispoziție de obiectele standard de tip adaptor nu este adecvată cerințelor unei aplicații, atât din punctul de vedere al gestiunii datelor cât și al interfeței grafice în contextul căreia trebuie să fie afișate informațiile.
În acest scop, este pusă la dispoziția programatorilor clasa abstractă BaseAdapter care poate fi extinsă cu operațiile impuse de aplicație. Definirea unui astfel de obiect de tip adaptor implică de fapt suprascrierea unor metode, definite de interfețele ListAdapter
și SpinnerAdapter
:
public int getCount(); public Object getItem(int position); public long getItemId(int position); public View getView(int position, View convertView, ViewGroup parent);
getCount()
furnizează numărul de elemente din cadrul listei;getItem()
întoarce elementul care se află pe o anumită poziție în cadrul listei;getItemId()
returnează identificatorul elementului de pe o anumită poziție în cadrul listei;getView()
construiește un control grafic corespunzător elementului de pe o anumită poziție în cadrul listei; acesta poate fi o instanță nouă, existând totodată posibilitatea de a se reutiliza parametrul convertView
(care reprezintă o componentă a listei creată anterior, ce nu mai este vizibilă, putând fi astfel reciclată); obiectul parent
indică elementul de tip container în care este afișat conținutul sursei de date.
În plus, în cazul în care elementele listei conțin moduri de dispunere diferite (în funcție de poziția pe care se găsesc, spre exemplu - rândurile impare și rândurile pare), vor fi suprascrise încă alte două metode:
public int getViewTypeCount(); public Object getItemViewType(int position);
getViewTypeCount()
va întoarce numărul tipurilor de layout-uri care vor fi afișate în cadrul listei;getItemViewType()
returnează - pentru elementul aflat pe o anumită poziție - mecanismul de dispunere care va fi utilizat.
Spre exemplu, pentru gestiunea unei liste formată dintr-un ImageView
și două TextView
, dispuse diferit în funcție de rândul pe care se găsesc, vor trebui definite două layout-uri (în /res/layout
) - denumite odd_layout.xml
și even_layout.xml
ce vor fi încărcate de metoda getView()
în funcție de poziția respectivă:
public class CustomAdapter extends BaseAdapter { Activity context; List<CustomObject> data; // ... @Override public View getView(int position, View convertView, ViewGroup parent) { View customView; CustomObject customObject = data.get(position); LayoutInflater layoutInflater = (LayoutInflater)context.getLayoutInflater(); if (position % 2 == 0) { customView = layoutInflater.inflate(R.layout.odd_layout, parent, false); } else { customView = layoutInflater.inflate(R.layout.even_layout, parent, false); } ImageView imageView = (ImageView)customView.findViewById(R.id.imageView); imageView.setImageResource(customObject.getImageId()); TextView textView1 = (TextView)customView.findViewById(R.id.textView1); textView1.setText(customObject.getText1()); TextView textView2 = (TextView)customView.findViewById(R.id.textView2); textView2.setText(customObject.getText2()); return customView; } }
Clasa adaptor definită de utilizator va trebui să rețină referințe către context (activitatea în cadrul căreia este afișată lista) și către sursa de date.
Metoda getView()
construiește un element de pe o anumită poziție din cadrul listei pe care îl va întoarce apoi ca rezultat.
Inițial, trebuie să se obțină conținutul intrării din listă ce trebuie afișată (considerându-se o corespondență 1:1 între conținutul sursei de date și elementele listei) și să se instanțieze obiectul responsabil cu expandarea layout-ului. Ulterior se obține obiectul de tip View
pe baza mecanismului de dispunere (corespunzător poziției curente) specificat în fișierul XML. Acesta va furniza referințe către controalele din cadrul său (prin intermediul identificatorilor unici) pentru care se va putea specifica conținutul, preluat din obiectul curent al sursei de date.
Implementarea unor tehnici pentru optimizarea performanțelor este foarte importantă atunci când se dezvoltă aplicații pentru dispozitive mobile. Având în vedere resursele limitate (în special în privința memoriei și a capacității de procesare) de care dispun acestea, un program care nu le gestionează în mod eficient poate determina o depreciere a timpului de răspuns.
Fiecare element din cadrul unei liste este un View
obținut prin expandarea layout-ului, atât construirea obiectului pentru gestiunea modurilor de dispunere (metoda getLayoutInflater()
) cât și operația de instanțiere a unei intrări din listă pe baza specificației XML (metoda inflate()
) fiind operații extrem de costisitoare. De asemenea, obținerea unei referințe către un control din cadrul interfeței grafice pe baza identificatorului său (metoda findViewById()
) poate avea afecta performanțele aplicației.
Printre cele mai utilizate tehnici pentru optimizarea performanțelor se numără:
public CustomAdapter(Activity context, List<CustomObject> data) { this.data = data; layoutInflater = (LayoutInflater)context.getLayoutInflater(); }
În acest fel, se evită apelul acestei metode - costisitoare din punctul de vedere al utilizării resurselor - de fiecare dată când trebuie creat un element al listei.
getView()
prin intermediul parametrului convertView
, prevenindu-se astfel operația de expandare a layout-ului.@Override public View getView(int position, View convertView, ViewGroup parent) { View customView; CustomObject customObject = data.get(position); if (convertView == null) { if (position % 2 == 0) { customView = layoutInflater.inflate(R.layout.odd_layout, parent, false); } else { customView = layoutInflater.inflate(R.layout.even_layout, parent, false); } } else { customView = convertView; } // ... return customView; }
Prin această metodă se evită apelarea metodei inflate()
de fiecare dată când trebuie să se afișeze un element al listei.
convertView
are valoarea null
. De aceea, în metoda getView()
trebuie verificată întotdeauna valoarea acestui parametru.
getViewTypeCount()
, respectiv getItemViewType()
.
public final static int LIST_VIEW_TYPES = 2; public final static int LIST_VIEW_TYPE_ODD = 0; public final static int LIST_VIEW_TYPE_EVEN = 1; public int getViewTypeCount() { return LIST_VIEW_TYPES; } public Object getItemViewType(int position) { if (position % 2 == 0) return LIST_VIEW_TYPE_ODD; return LIST_VIEW_TYPE_EVEN; }
ViewHolder
prin intermediul căruia pot fi accesate controalele grafice din cadrul unui element al listei, fără a fi necesară utilizarea identificatorului din cadrul documentului XML.ViewHolder
este o clasă internă din cadrul adaptorului, definind exact structura pe care o are layout-ul unei intrări din cadrul listei. În momentul în care un obiect corespunzător unui element al listei este creat, în atributele clasei ViewHolder
se rețin referințe către controalele grafice, acestea fiind atașate, ca etichetă, prin intermediul metodei setTag()
. Ulterior, ele vor fi preluate (prin metoda getTag()
), utilizându-se referințele către elementele componente în loc să se apeleze metoda findViewById()
de fiecare data, rezultatul fiind o apreciere a performanțelor cu circa 15%.public class CustomAdapter extends BaseAdapter { public static class ViewHolder { ImageView imageView; TextView textView1, textView2; }; // ... @Override public View getView(int position, View convertView, ViewGroup parent) { View customView; ViewHolder viewHolder; CustomObject customObject = (CustomObject)data.get(position); if (convertView == null) { if (position % 2 == 0) { customView = layoutInflater.inflate(R.layout.odd_layout, parent, false); } else { customView = layoutInflater.inflate(R.layout.even_layout, parent, false); } viewHolder = new ViewHolder(); viewHolder.imageView = (ImageView)customView.findViewById(R.id.imageView); viewHolder.textView1 = (TextView)customView.findViewById(R.id.textView1); viewHolder.textView2 = (TextView)customView.findViewById(R.id.textView2); customView.setTag(viewHolder); } else { customView = convertView; } viewHolder = (ViewHolder)customView.getTag(); viewHolder.imageView.setImageResource(customObject.getImageeId()); viewHolder.textView1.setText(customObject.getText1()); viewHolder.textView2.setText(customObject.getText2()); return customView; } }
Întrucât de regulă în cadrul unei interfețe grafice nu există decât o singură listă (care ocupă tot spațiul disponibil și se găsește centrată pe suprafața de afișare), în Android au fost definite componente speciale pentru gestiunea acesteia: ListActivity
, respectiv ListFragment
. Nu este necesar să se utilizeze un layout în această situație, lista fiind declarată în mod implicit.
@android:id/list
, în caz contrar generându-se o excepție.
<ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="wrap_content" />
@android:id/empty
care va fi afișat pe ecran numai în cazul în care lista este vidă (aceasta fiind ascunsă în acest caz).
Clasele ListActivity
și ListFragment
funcționează doar cu obiecte de tip ListView
. În cazul în care se dorește folosirea altor tipuri particulare de liste, utilizarea acestora nu va fi posibilă.
Clasa ListActivity definește o activitate ce încapsulează un obiect de tip ListView
, conectat la o sursă de date (cursor sau tablou/listă), expunând către programatori metode de tratare a evenimentului de selectare a unui element.
Gestiunea adaptorului pentru lista implicită din ListActivity
se face prin intermediul metodelor:
ListAdapter getListAdapter()
- întoarce obiectul de tip adaptor asociat listeivoid setListAdapter(ListAdapter adapter)
- asociază listei un obiect de tip adaptor
Referința către lista implicită poate fi obținută prin intermediul metodei ListView getListView()
.
Metoda ce va fi apelată automat în momentul în care utilizatorul selectează un element din cadrul listei este:
protected void onListItemClick(ListView l, View v, int position, long id);
parametrii având următoarea semnificație:
l
- lista pe care s-a produs evenimentulv
- elementul din cadrul listei care a fost apăsat position
- poziția elementului care a fost apăsat în cadrul listeiid
- identificatorul elementului care a fost apăsat în cadrul listei (întors de metoda getItemId()
a clasei adaptor)
Clasa ListActivity
nu definește însă și o metodă pentru tratarea apăsărilor de lungă durată (eng. long-click), în acest sens fiind necesară implementarea unei clase ascultător ce implementează interfața AdapterView.OnItemLongClickListener
, asociată obiectului de tip ListView
din cadrul ferestrei.
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
Semnificația parametrilor este aceeași ca în cazul metodei precedente. Rezultatul întors este true
în cazul în care evenimentul de tip apăsare lungă a unui eveniment a fost consumat și false
altfel.
Clasa ListFragment definește un fragment ce încapsulează un obiect de tip ListView
, conectat la o sursă de date (cursor sau tablou/listă), expunând către programatori metode de tratare a evenimentului de selectare a unui element.
Metodele pe care le pune la dispoziție ListFragment
sunt similare cu cele oferite de ListActivity
.
FragmentActivity
și nu ListActivity
, în caz contrar evenimentele de tip apăsare a unui element fiind consumate de clasa părinte.
Pentru utilizarea de layout-uri definite de utilizatori, lista implicită trebuie referită prin android:id/list
, mecanismul de dispunere fiind încărcat pe metoda onCreateView()
.
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) { View view = inflater.inflate(R.layout.fragment_custom, container, false); return view; }
onActivityCreated()
, când există siguranța că activitatea care îl conține a fost creată. Specificarea unui tip de adaptor definit de utilizator se va face tot în contextul acestei metode.
@Override public void onActivityCreated(Bundle state) { super.onActivityCreated(state); CustomAdapter customAdapter = new CustomAdapter(getActivity(), data); setListAdapter(customAdapter); }
Se observă faptul că referința către activitatea care conține fragmentul se face prin metoda getActivity()
. Numai după ce activitatea a fost creată se garantează că rezultatul acestei metode este relevant.
Specificarea unui fragment în cadrul unei activități poate fi realizată:
addToBackStack()
, pentru a se putea reveni la el)<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <fragment android:id="@+id/fragment1" android:name="ro.pub.cs.systems.pdsd.lab05.Fragment1" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLayout>
Un astfel de fragment nu poate fi niciodată distrus, dar poate fi înlocuit cu un alt conținut, plasându-se conținutul său pe stivă:
Fragment2 fragment2 = new Fragment2(); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(android.R.id.content, fragment2); fragmentTransaction.addToBackStack(null); fragmentTransaction.hide(this); fragmentTransaction.commit();
De remarcat faptul că pentru fragment au fost specificate următoarele proprietăți:
android:id
prin intermediul căruia fragmentul poate fi identificat în mod unic (alternativ se poate specifica android:tag
)android:name
reprezentând clasa corespunzătoare fragmentului, care va fi instanțiată în momentul în care este expandat layoutul corespunzător activității (în locul elementului <fragment>
va fi plasat rezultatul metodei onCreateView()
)<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".CartoonActivity" > <FrameLayout android:id="@+id/fragment1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> </RelativeLayout>
În această situație se utilizează un mecanism de dispunere de tip FrameLayout
care permite afișarea unui singur conținut la un moment dat. În cazul în care în cadrul acestui container se găsea anterior un alt fragment, acesta este distrus (spre diferență de cazul anterior).
Fragment2 fragment2 = new Fragment2(); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.fragment1, fragment2); fragmentTransaction.commit();
În Android, meniurile sunt implementate folosind clasa android.view.Menu, fiecare activitate având asociat un astfel de obiect.
Așa cum se poate observa, un meniu poate conține:
android.view.SubMenu
)android.view.MenuItem
), caracterizate prin următoarele atribute:CATEGORIE | TIP | INTERVAL | DESCRIERE |
---|---|---|---|
container | Menu.CATEGORY_CONTAINER | 0x10000 | intrări de meniu legate de modul de dispunere al interfeței grafice |
sistem | Menu.CATEGORY_SYSTEM | 0x20000 | intrări de meniu create de sistemul de operare Android, descriind funcționalități valabile pentru toate activitățile |
secundare | Menu.CATEGORY_SECONDARY | 0x30000 | intrări de meniu de o importanță mai redusă decât celelalte (mai puțin utilizate) |
alternative | Menu.CATEGORY_ALTERNATIVE | 0x40000 | intrări de meniu create de aplicații externe, oferind modalități alternative de a gestiona datele |
Un meniu ce conține operațiile relevante pentru o anumită activitate se numește meniu de opțiuni, acesta fiind accesibil:
Menu
, afișându-se o porţiune a meniului ce cuprinde șase poziții disponibile; în situația în care meniul respectiv este format din mai multe elemente, va exista și o opțiune More
prin care pot fi accesate şi celelalte opţiuniMenu
, în cazul în care acesta existăEl este instanțiat în momentul apelării metodei:
@Override public boolean onCreateOptionsMenu(Menu menu) { // populate menu, via XML resource file or programatically return true; }
Pentru sistemele Android anterioare versiunii 2.3.x, aceasta este apelată în mod automat atunci când utilizatorul accesează meniul, iar pentru sistemele Android anterioare versiunii 3.0 în momentul instanțierii activității, de vreme ce opțiunile meniului trebuie să fie accesibile și prin intermediul barei de acțiuni.
onCreateOptionsMenu()
să fie true
, pentru a face meniul vizibil. În situația în care metoda întoarce false
, meniul va fi invizibil.
Mai multe informații cu privire la meniuri pot fi consultate aici.
Un meniu poate fi definit:
1. prin intermediul unui fișier de resurse XML plasat în res/menu
- variantă preferată datorită faptului că:
R.menu
);<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/operations" android:title="@string/operations" > <menu> <item android:id="@+id/create" android:icon="@drawable/create" android:orderInCategory="100" android:showAsAction="ifRoom" android:title="@string/create" /> <!-- add other submenu entries --> </menu> </item> <group android:id="@+id/generic_actions" android:visible="true" android:enabled="true" android:checkableBehavior="none" > <item android:id="@+id/settings" android:icon="@drawable/settings" android:orderInCategory="100" android:showAsAction="ifRoom" android:title="@string/settings" /> <!-- add other menu entries --> </group> <item android:id="@+id/help" android:icon="@drawable/help" android:orderInCategory="100" android:showAsAction="ifRoom" android:title="@string/help" /> <!-- add other menu entries --> </menu>
Așa cum se observă, elementul rădăcină este un obiect de tip <menu>
, definește un meniu (de tip android.view.Menu
). Acesta poate conține elemente de tip:
a. <item>
care creează
android.view.Submenu
) în cazul în care are imbricat un alt element de tip <menu>
;android.view.MenuItem
), dacă nu conține alte elemente;
Atributele elementului <item>
sunt:
android:id
- un identificator unic pentru resursă care va fi utilizat ca referință pentru elementul respectivandroid:icon
- o resursă de tip drawable
folosită pe post de pictogramă, atunci când elementul este inclus în cadrul barei de acțiuniandroid:orderInCategory
- numărul de ordine ce definește importanța intrării meniului în cadrul grupuluiandroid:showAsAction
- precizează criteriile în funcție de care elementul respectiv va fi inclus în cadrul barei de acțiuni, putând avea valorile:ifRoom
- va fi plasat în bara de acțiuni numai dacă este spațiu disponibilwithText
- în bara de acțiuni se va afișa atât pictograma intrării cât și denumirea intrării din cadrul meniuluinever
- nu va fi plasat niciodată în bara de acțiunicollapseActionView
- elementul grafic asociat intrării din cadrul meniului poate fi rabatatalways
- va fi plasat întotdeauna în bara de acțiuni
android:title
- denumirea elementului, așa cum este afișată către utilizator (dacă valoarea este un șir de caractere prea lung, poate fi folosită valoarea indicată de proprietatea android:titleCondensed
)android:alphabeticShortcut
, android:numericShortcut
- indică o scurtătură (de tip caracter sau număr) prin care poate fi accesată intrarea din cadrul meniului
b. <group>
, un container pentru intrări propriu-zise ale meniului (elemente <item>
) care partajează același comportament, acesta putând fi specificat ca proprietate a grupului (în loc de a fi precizat pentru fiecare element în parte)
android:visible
- vizibile sau invizibileandroid:enabled
- activate sau dezactivateandroid:checkableBehavior
- selectate sau deselectate (valori posibile fiind none
, all
sau single
după cum pot fi selectate nici una, toate sau o singură intrare a meniului din cadrul grupului)
Pentru ambele tipuri de elemente, poate fi specificată proprietatea android:menuCategory
ce indică prioritatea corespunzătoare categoriei de meniu (container
, system
, secondary
, respectiv alternative
).
Încărcarea meniului în cadrul activității (sau fragmentului) este realizată prin intermediul metodei onCreateOptionsMenu(Menu)
:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; }
2. programatic, în codul sursă, de regulă în cadrul metodei onCreateOptionsMenu()
Metodele prin intermediul cărora sunt adăugate intrări în cadrul meniului sunt:
public SubMenu addSubmenu(int titleRes); public SubMenu addSubmenu(CharSequence title); public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes); public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title); public MenuItem add(int titleRes); public MenuItem add(CharSequence title); public MenuItem add(int groupId, int itemId, int order, int titleRes); public MenuItem add(int groupId, int itemId, int order, CharSequence title);
Astfel, funcționalitatea descrisă prin intermediul fișierelor resursă XML poate fi obținută și prin intermediul codului sursă:
final public static int GROUP_ID_NONE = 0; final public static int GROUP_ID_GENERIC_ACTIONS = 1; final public static int SUBMENU_ID_OPERATIONS = 0; final public static int MENU_ID_CREATE = 1; final public static int MENU_ID_SETTINGS = 2; final public static int MENU_ID_ABOUT = 3; final public static int MENU_ORDER = 100; @Override public boolean onCreateOptionsMenu(Menu menu) { MenuItem menuItem; SubMenu subMenu = menu.addSubMenu(GROUP_ID_NONE, SUBMENU_ID_OPERATIONS, MENU_ORDER, R.string.operations); menuItem = subMenu.add(GROUP_ID_NONE, MENU_ID_CREATE, MENU_ORDER, R.string.create); menuItem.setIcon(R.drawable.create); menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); // add other submenu entries menuItem = menu.add(GROUP_ID_GENERIC_ACTIONS, MENU_ID_SETTINGS, MENU_ORDER, R.string.settings); menuItem.setIcon(R.drawable.settings); menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); // add other menu entries menu.setGroupVisible(GROUP_ID_GENERIC_ACTIONS, true); menu.setGroupEnabled(GROUP_ID_GENERIC_ACTIONS, true); menu.setGroupCheckable(GROUP_ID_GENERIC_ACTIONS, false, false); menuItem = menu.add(GROUP_ID_NONE, MENU_ID_SETTINGS, MENU_ORDER, R.string.about); menuItem.setIcon(R.drawable.about); menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); // add other menu entries return true; }
Aceste metode pot fi utilizate și în situația în care meniul este definit prin intermediul unor fișiere resursă de tip XML, dorindu-se ulterior modificarea dinamică a acestuia.
Alte metode utile puse la dispoziție de clasa Menu
sunt:
findItem(int)
- întoarce un obiect de tip MenuItem
, dându-se identificatorul acestuia;getItem(int)
- întoarce un obiect de tip MenuITem
, dându-se poziția acestuia;removeGroup(int)
- șterge toate elementele din cadrul unui grup, furnizându-se identificatorul acestuia;clear()
- șterge toate intrările intrărilor din cadrul meniului, acesta rămânând vid;removeItem(int)
- șterge o intrare din cadrul meniului, dându-se identificatorul acestuia;size()
- furnizează numărul de intrări din cadrul meniului;hasVisibleItems()
- precizează dacă există intrări din cadrul meniului care sunt vizible sau nu;close()
- determină închiderea meniului, în cazul în care acesta este deschis.
Activity
care va implementa numai metodele onCreateOptionsMenu()
, respectiv onOptionsItemSelected()
, aceasta fiind extinsă pentru a se prelua funcționalitatea de instanțiere a meniului și de tratare a evenimentelor de apăsare.
Tratarea evenimentelor de accesare a unei intrări a meniului se face în cadrul metodei onOptionsItemSelected(MenuItem)
, apelată în mod automat în momentul producerii unui astfel de eveniment. Aceasta primește ca parametru opțiunea din cadrul meniului care a fost aleasă de utilizator, recunoașterea sa realizându-se prin intermediul identificatorului unic asociat fiecărei intrări:
@Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.create: case MENU_ID_CREATE: // ... return true; // ... } return super.onOptionsItemSelected(item); }
Este important ca ulterior tratării evenimentului de accesare a unei intrări din cadrul meniului, rezultatul întors să fie true
. În situația în care intrarea care a fost selectată nu este gestionată, se recomandă să se apeleze metoda părinte care returnează în mod implicit false
.
Acesta este mecanismul recomandat pentru tratarea evenimentelor legate de meniuri. Există însă și alte alternative:
1. definirea unei clase ascultător dedicate care implementează interfața MenuItem.OnMenuItemClickListener
și metoda onMenuItemClick(MenuItem)
; atașarea obiectului ascultător se face prin intermediul metodei setOnMenuItemClickListener()
.
public class CustomMenuItemClickListener implements MenuItem.OnMenuItemClickListener { @Override public boolean onMenuItemClick(MenuItem item) { switch(item.getItemId()) { case R.id.create: case MENU_ID_CREATE: // ... return true; // ... } return true; } } // .. CustomMenuItemClickListener customMenuItemClickListener = new CustomMenuItemClickListener(); menuItem.setOnMenuItemClickListener(customMenuItemClickListener);
Această metodă trebuie să fie apelată pentru fiecare intrare de meniu în parte. Ea are însă precedență în fața altor metode, astfel încât în situația în care rezultatul pe care îl întoarce este true
, nu mai sunt apelate și alte metode, cum ar fi onOptionsItemSelected()
.
2. utilizarea unei intenții asociate la o intrare din cadrul meniului prin intermediul metodei setIntent(Intent)
, lansându-se în execuție activitatea corespunzătoare în condițiile în care:
onOptionsItemSelected()
;onOptionsItemSelected()
este suprascrisă, trebuie să se apeleze metoda părinte (super.onOptionsItemSelected(menu)
) pentru acele intrări ale meniului ce nu sunt procesate.În mod implicit, nici o intrare din cadrul unui meniu nu are asociată o intenție.
Un meniu contextual (clasa ContextMenu) este asociat de regulă unui control din cadrul interfeței grafice (spre diferență de meniurile de opțiuni care sunt asociate activităților). De regulă, acesta este accesat la operația de apăsare prelungită pe obiectul respectiv (de tip View
), având aceeași structură ca orice meniu, metodele utilizate pentru construirea sa fiind aceleași.
O activitate poate avea asociat un singur meniu de opțiuni, însă mai multe meniuri contextuale, asociate fiecare câte unui element din interfața sa grafică.
Instanțierea unui meniu contextual este un proces realizat în mai multe etape:
registerForContextMenu(view);
O astfel de operație este necesară datorită faptului că numai unele dintre elementele interfeței grafice din cadrul activității (sau fragmentului) vor avea asociat un astfel de meniu.
Apelul metodei va atașa activității un obiect ascultător de tip View.OnCreateContextMenuListener
, astfel încât operația de apăsare prelungită pe obiectul respectiv va declanșa apelul metodei onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
care trebuie definită de programator.
În cazul în care se dorește atașarea unui meniu contextual pentru elementele unui container pentru colecții de date (ListView
, GridView
) este suficient ca acesta să fie transmis ca parametru al metodei pentru ca meniul contextual să fie afişat pentru fiecare dintre intrările pe care le conţine.
onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
în cadrul activității (sau fragmentului) @Override public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); // option 1 MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.context_menu, menu); // option 2 menu.setHeaderTitle(R.string.operations); menu.add(GROUP_ID_NONE, MENU_ID_CREATE, MENU_ORDER, R.string.create); }
Metoda primește următorii parametri (ce pot fi utilizați pentru a se determina ce obiect din cadrul interfeței grafice a fost accesat, pentru a se instanția meniul contextual corespunzător):
menu
- meniul contextual care se dorește a fi afișat;view
- elementul din cadrul interfeței grafice căruia îi este asociat meniul contextual;menuInfo
- un obiect care oferă informații suplimentare cu privire la elementul selectat.onContextItemSelected(MenuItem)
care gestionează acțiunea de selectare a unei intrări din cadrul meniului contextual @Override public boolean onContextItemSelected(MenuItem item) { AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo)item.getMenuInfo(); // or ExpandableContextMenuInfo expandableContextMenuInfo = (ExpandableContextMenuInfo)item.getMenuInfo(); switch(item.getItemId()) { case R.id.create: case MENU_ID_CREATE: // ... return true; // ... } return super.onContextItemSelected(item); }
Tratarea acțiunii de selectare a unei intrări din cadrul meniului contextual trebuie semnalată prin întoarcerea unui rezultat al metodei de tip true
, în caz contrar trebuind apelată metoda părinte super.onContextItemSelected(item)
care va determina apelarea metodelor corespunzătoare eventualelor fragmente pe care le poate conține activitatea (în ordinea în care acestea au fost declarate) până în momentul în care una dintre acestea va întoarce un rezultat (implementarea implicită a metodei întoarce false
atât pentru clasa Activity
cât și pentru clasa Fragment
).
Prin intermediul unui meniu alternativ ce poate face parte dintr-un meniu de opțiuni (inclusiv dintr-unul din submeniurile sale) sau dintr-un meniu contextual, pot fi lansate în execuție activități din cadrul aplicației curente sau din cadrul altor activități, dacă acestea sunt disponibile, pentru a gestiona datele curente. În situația în care nu sunt identificate astfel de activități, meniul contextual respectiv nu va conține nici o intrare.
Instanțierea unui meniu alternativ se realizează în cadrul metodei onCreateOptionsMenu(Menu)
și este un proces realizat în mai multe etape:
Intent.CATEGORY_ALTERNATIVE
sau Intent.CATEGORY_SELECTED_ALTERNATIVE
(dacă activitatea se referă la intrarea selectată din cadrul meniului, intenția fiind definită în cadrul metodei onCreateContextMenu()
) Intent intent = new Intent(null, getIntent().getData()); // the URI of the data application is working with; getIntent() may return null!!! intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
O activitate poate fi invocată prin intermediul unui meniu contextual, dacă specifică valorile respective în câmpul ce indică filtrul pentru intenții în fișierul AndroidManifest.xml
:
<intent-filter> ... <category android:name="android.intent.category.ALTERNATIVE" /> <category android:name="android.intent.category.SELECTED_ALTERNATIVE" /> ... </intent-filter>
Menu.addIntentOptions
(apelată pe obiectul transmis ca parametru metodei onCreateOptionsMenu()
) menu.addIntentOptions( Menu.CATEGORY_ALTERNATIVE, // the group the contextual menu will be attached to Menu.CATEGORY_ALTERNATIVE, // menu item ID for all of the menu items Menu.CATEGORY_ALTERNATIVE, // order number for each of the menu items this.getComponentName(), // the name of the current activity null, // no specific items to place with priority intent, // intent that can invoke the corresponding activities (previously defined) 0, // no additional flags to manage the menu items null); // no array of menu items to correlate with specific items
Metoda furnizează o listă de activități care corespund criteriilor precizate de intenție populând cu acestea intrările din cadrul meniului, în cadrul unui anumit grup, alocându-le identificatori și numere de ordine începând cu o anumită valoare.
Semnătura metodei este:
public abstract int addIntentOptions (int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems)
semnificația parametrilor fiind:
groupId
- grupul din cadrul meniului din care vor face parte intrările meniului contextual; se poate folosi valoarea Menu.NONE
în situația în care elementele respective nu trebuie asociate unui grupitemId
- identificator unic folosit pentru elementele meniului contextual; în cazul în care acesta nu va mai fi utilizat ulterior, se poate furniza valoarea Menu.NONE
order
- număr de ordine utilizat pentru sortarea intrărilor din cadrul meniului contextual; dacă acest criteriu nu va fi utilizat, poate fi specificată valoarea Menu.NONE
caller
- denumirea componentei activității curente (denumirea pachetului și denumirea clasei) folosită de sistemul de operare pentru a se identifica resursa care a invocat altă activitatespecifics
- elemente specifice care se doresc a fi plasate cu prioritateintent
- intenție care descrie tipurile de elemente cu care va fi populat meniul contextualflags
- opțiuni suplimentare care controlează mecanismul de atașare al intrărilor (spre exemplu, crearea unui meniu contextual numai cu activitățile specificate de intenția curentă sau folosirea lor împreună cu cele existente anterior - valoarea 0 indică faptul că doar valorile identificate în cadrul apelului curent al metodei ar trebui folosite)outSpecificItems
- un tablou în care se plasează intrările din cadrul meniului contextual care au fost generate pentru fiecare dintre elementele specifice (indicate ca parametru); pentru acțiunile în cazul cărora nu a fost identificată nici o activitate, se va furniza un rezultat null
android:label
specificată pentru elementul <intent-filter>
corespunzător activității respective din cadrul fișierului AndroidManifest.xml
.
Un meniu de tip pop-up (clasa PopupMenu) este asociat unui element din cadrul interfeței grafice, afișat deasupra sau sub aceasta în momentul în care se produce un eveniment legat de acesta.
El se distinge însă față de celelalte tipuri de meniuri, față de care ar părea să implementeze o funcționalitate comună:
Un meniu de tip pop-up poate fi creat - ca orice meniu - atât prin intermediul unui fișier XML de resurse cât și programatic.
Utilizarea unui meniu de tip pop-up presupune definirea unei metode care va fi apelată (manual, de către programator) atunci când se realizează o acțiune legată de controlul grafic căruia i se atașază:
public void showPopupMenu(View view) { PopupMenu popupMenu = new PopupMenu(this, menu); // option 1: defining a popup menu via a XML resource file // android 3.0 MenuInflater menuInflater = popupMenu.getMenuInflater(); menuInflater.inflate(R.menu.popup_menu, popupMenu.getMenu()); // android 4.0 popupMenu.inflate(R.menu.popup_menu); // option 2: defining a popup menu programatically Menu menu = popupMenu.getMenu(); menu.add(GROUP_ID_NONE, MENU_ID_CREATE, MENU_ORDER, R.string.create); // ... popup.show(); }
Se observă că un obiect de tipul PopupMenu
poate fi instanțiat transmițând ca parametri contextul (activitatea) în care acesta urmează să fie afișat precum și controlul grafic căruia îi va fi atașat (existând posibilitatea de a se preciza și modul de dispunere față de acesta).
public PopupMenu (Context context, View anchor); public PopupMenu (Context context, View anchor, int gravity);
Tratarea evenimentului de accesare a unui element din cadrul meniului de tip pop-up se face prin implementarea interfeței PopupMenu.OnMenuItemClickListener
:
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch(item.getItemId()) { case R.id.create: case MENU_ID_CREATE: // ... return true; // ... } return true; } });
Se dorește implementarea unei aplicații Android care să afișeze lista tuturor contactelor din agenda telefonică, împreună cu unele informații despre aceștia (imaginea asociată, numele, numărul de accesări, cea mai recentă accesare, apartenența la grupul de favoriți). De asemenea, în momentul în care este selectat un element din listă, se vor afișa toate numerele telefonice și toate adresele de poștă electronică stocate cu privire la acesta, împreună cu tipul lor. Totodată, va exista o facilitate de căutare pe măsură ce se introduce un șir de caractere, care poate fi regăsit în oricare dintre detaliile contactului respectiv.
0. Se recomandă ca în situația în care se folosește un emulator, să se adauge în aplicația People cel puțin două contacte care să poată fi gestionate prin intermediul aplicației.
→
1. În contul Github personal, să se creeze un depozit denumit 'Laborator05'. Inițial, acesta trebuie să fie gol (NU trebuie să bifați nici adăugarea unui fișier README.md
, nici a fișierului .gitignore
sau a a fișierului LICENSE
).
2. Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/pdsd2015/Laborator05. În urma acestei operații, directorul Laborator05
va trebui să se conțină în subdirectorul labtasks
proiectul Eclipse denumit AddressBook
, fișierul README.md
și un fișier .gitignore
care indică tipurile de fișiere (extensiile) ignorate.
student@pdsd2015:~$ git clone https://www.github.com/pdsd2015/Laborator05
3. Să se încarce conținutul descărcat în cadrul depozitului 'Laborator05' de pe contul Github personal.
student@pdsd2015:~$ cd Laborator05/labtasks student@pdsd2015:~/Laborator05/labtasks$ git remote add Laborator05_perfectstudent https://github.com/perfectstudent/Laborator05 student@pdsd2015:~/Laborator05/labtasks$ git push Laborator05_perfectstudent master
4. Să se încarce în mediul integrat de dezvoltare Eclipse proiectul AddressBook
, folosind opțiunea File → Import….
5. Să se afișeze contactele din agenda telefonică sub forma unei liste, indicând, pentru fiecare dintre acestea:
Contact.DISPLAY_NAME_PRIMARY
);Contact.PHOTO_THUMBNAIL_URI
) - în cazul în care aceasta lipsește, se va afișa imaginea din resursa R.drawable.contact_photo
;Contact.TIMES_CONTACTED
;Contact.LAST_TIME_CONTACTED
);Contacts.STARRED
):R.drawable.star_set
- dacă persoana face parte din grupul de favoriți;R.drawable.star_unset
- dacă persoana NU face parte din grupul de favoriți.
a. Se va defini fișierul contact_view.xml
în care se vor declara controalele grafice necesare expunerii informațiilor legate de contacte.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" > <ImageView android:id="@+id/contact_photo_image_view" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="left|center_vertical" android:layout_margin="10dp" android:layout_weight="2" android:contentDescription="@string/photo_content_description"/> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="12" android:orientation="vertical" > <TextView android:id="@+id/contact_name_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/contact_times_contacted_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/times_contacted" /> <TextView android:id="@+id/contact_last_time_contacted_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/last_time_contacted" /> </LinearLayout> <ImageView android:id="@+id/contact_starred_image_view" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="right|center_vertical" android:layout_margin="10dp" android:layout_weight="1" android:contentDescription="@string/starred_description"/> </LinearLayout>
android:layout_weight
să fie luat în considerare, dimensiunea pe orientarea respectivă (android:layout_width
/ android:layout_height
) trebuie să aibă valoarea 0dp
.
b. În clasa ContactAdapter
din pachetul ro.pub.cs.systems.lab05.addressbook.controller
se vor implementa metodele necesare definirii unui obiect adaptor (de tip BaseAdapter
) particularizat.
int getCount()
- întoarce numărul de înregistrări din sursa de date afișată în cadrul listei;Object getItem(int position)
- întoarce sursa de date corespunzătoare unui contact de la o anumită poziție a listei;long getItemId(int position)
- întoarce identificatorul interfeței grafice corespunzătoare unui contact de la o anumită poziție a listei;
long getItemId(int position)
poate întoarce valoarea 0 în situația în care nu este necesară accesarea interfeței grafice prin intermediul metodei View findViewById(int id)
.
View convertView(int position, View convertView, ViewGroup parent)
- întoarce interfața grafică corespunzătoare unui contact de la o anumită poziție a listei.public class ContactAdapter extends BaseAdapter { @Override public int getCount() { return data.size(); } @Override public Object getItem(int position) { return data.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { Contact contact = data.get(position); LayoutInflater inflater = (LayoutInflater)context.getLayoutInflater(); View contactView = inflater.inflate(R.layout.contact_view, parent, false); ImageView contactPhotoImageView = (ImageView)contactView.findViewById(R.id.contact_photo_image_view); TextView contactNameTextView = (TextView)contactView.findViewById(R.id.contact_name_text_view); TextView contactTimesContactedTextView = (TextView)contactView.findViewById(R.id.contact_times_contacted_text_view); TextView contactLastTimeContactedTextView = (TextView)contactView.findViewById(R.id.contact_last_time_contacted_text_view); ImageView contactStarredImageView = (ImageView)contactView.findViewById(R.id.contact_starred_image_view); if (getCheckedItemPosition() == position) { contactView.setBackgroundColor(context.getResources().getColor(R.color.light_blue)); } else { contactView.setBackgroundColor(context.getResources().getColor(R.color.light_gray)); } AssetFileDescriptor assetFileDescriptor = null; try { Uri contactPhotoUri = contact.getPhoto(); if (contactPhotoUri == Uri.EMPTY) { contactPhotoImageView.setImageResource(R.drawable.contact_photo); } else { assetFileDescriptor = context.getContentResolver().openAssetFileDescriptor(contactPhotoUri, "r"); FileDescriptor fileDescriptor = assetFileDescriptor.getFileDescriptor(); if (fileDescriptor != null) { contactPhotoImageView.setImageBitmap(BitmapFactory.decodeFileDescriptor(fileDescriptor, null, null)); } } } catch (FileNotFoundException fileNotFoundException) { Log.e(Constants.TAG, "An exception has occurred: "+fileNotFoundException.getMessage()); if (Constants.DEBUG) { fileNotFoundException.printStackTrace(); } } finally { if (assetFileDescriptor != null) { try { assetFileDescriptor.close(); } catch (IOException ioException) { Log.e(Constants.TAG, "An exception has occurred: "+ioException.getMessage()); if (Constants.DEBUG) { ioException.printStackTrace(); } } } } contactNameTextView.setText(contact.getName()); contactTimesContactedTextView.setText(context.getResources().getString(R.string.times_contacted)+" "+contact.getTimesContacted()); long value = contact.getLastTimeContacted(); if (value != 0) { contactLastTimeContactedTextView.setText(context.getResources().getString(R.string.last_time_contacted)+" "+Utilities.displayDateAndTime(value)); } else { contactLastTimeContactedTextView.setText(context.getResources().getString(R.string.last_time_contacted)+" -"); } if (contact.getStarred() == 1) { contactStarredImageView.setImageResource(R.drawable.star_set); } else { contactStarredImageView.setImageResource(R.drawable.star_unset); } return contactView; } }
6. Să se implementeze un meniu, care să ofere următoarele funcționalități:
ContactsManagerActivity
.
a. în directorul res/menu
se va defini un fișier address_book.xml
conținând structura fișierului (câte un element de tip <item>
pentru fiecare funcționalitate, specificând un text care trebuie afișat și o imagine care va fi plasată în bara de stare (dacă spațiul îl permite);
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context="ro.pub.cs.systems.pdsd.lab05.addressbook.AddressBookActivity" > <item android:id="@+id/action_insert" android:icon="@drawable/insert" android:orderInCategory="100" android:showAsAction="ifRoom" android:title="@string/insert"/> <item android:id="@+id/action_update" android:icon="@drawable/update" android:orderInCategory="100" android:showAsAction="ifRoom" android:title="@string/update"/> <item android:id="@+id/action_delete" android:icon="@drawable/delete" android:orderInCategory="100" android:showAsAction="ifRoom" android:title="@string/delete"/> </menu>
b. În metoda boolean onOptionsItemSelected(MenuItem item)
din clasa AddressBookActivity
(pachetul ro.pub.cs.systems.pdsd.lab05.addressbook.view
) să se realizeze operațiile de adăugare, modificare, ștergere, în funcție de identificatorul intrării din meniu care a fost accesată.
@Override public boolean onOptionsItemSelected(MenuItem item) { ListView addressBookListView = (ListView)findViewById(R.id.address_book_list_view); ContactAdapter addressBookContactAdapter = (ContactAdapter)addressBookListView.getAdapter(); int id = item.getItemId(); if (id == R.id.action_insert) { Intent intentAdd = new Intent(this, ContactsManagerActivity.class); intentAdd.putExtra(Constants.OPERATION, Constants.OPERATION_INSERT); startActivityForResult(intentAdd, Constants.OPERATION_INSERT); return true; } if (id == R.id.action_update) { if (addressBookContactAdapter.getCheckedItemPosition() == -1) { Toast.makeText(this, "There is no selection made", Toast.LENGTH_LONG).show(); return false; } Intent intentUpdate = new Intent(this, ContactsManagerActivity.class); intentUpdate.putExtra(Constants.OPERATION, Constants.OPERATION_UPDATE); intentUpdate.putExtra(Constants.CONTACT_NAME, addressBookContactAdapter.getSelectedContact().getName()); int index; TextView contactPhonesTextView = (TextView)findViewById(R.id.contact_phones_text_view); String contactPhoneNumbers = contactPhonesTextView.getText().toString(); String[] contactPhoneNumbersParts = contactPhoneNumbers.replaceAll(" ", "").split("[:\n]"); if ((contactPhoneNumbersParts.length % 2) == 0) { ArrayList<PhoneNumber> contactPhones = new ArrayList<PhoneNumber>(); index = 0; while (index < contactPhoneNumbersParts.length) { PhoneNumber contactPhoneNumber = new PhoneNumber(contactPhoneNumbersParts[index+1], contactPhoneNumbersParts[index]); contactPhones.add(contactPhoneNumber); index += 2; } intentUpdate.putExtra(Constants.CONTACT_PHONES, contactPhones); } TextView contactEmailsTextView = (TextView)findViewById(R.id.contact_emails_text_view); String contactEmailAddresses = contactEmailsTextView.getText().toString(); String[] contactEmailAddressesParts = contactEmailAddresses.replaceAll(" ", "").split("[:\n]"); if ((contactEmailAddressesParts.length % 2) == 0) { ArrayList<EmailAddress> contactEmails = new ArrayList<EmailAddress>(); index = 0; while (index < contactEmailAddressesParts.length) { EmailAddress contactEmailAddress = new EmailAddress(contactEmailAddressesParts[index+1], contactEmailAddressesParts[index]); contactEmails.add(contactEmailAddress); index += 2; } intentUpdate.putExtra(Constants.CONTACT_EMAILS, contactEmails); } intentUpdate.putExtra(Constants.CONTACT_ID, String.valueOf(addressBookContactAdapter.getContactId())); startActivityForResult(intentUpdate, Constants.OPERATION_UPDATE); return true; } if (id == R.id.action_delete) { ArrayList<ContentProviderOperation> contentProviderOperations = new ArrayList<ContentProviderOperation>(); contentProviderOperations.add(ContentProviderOperation .newDelete(ContactsContract.RawContacts.CONTENT_URI) .withSelection( RawContacts.CONTACT_ID + " = ?", new String[] { String.valueOf(addressBookContactAdapter.getContactId()) }) .build() ); try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, contentProviderOperations); } catch (RemoteException remoteException) { Log.e(Constants.TAG, "An exception has occurred: "+remoteException.getMessage()); if (Constants.DEBUG) { remoteException.printStackTrace(); } return false; } catch (OperationApplicationException operationApplicationException) { Log.e(Constants.TAG, "An exception has occurred: "+operationApplicationException.getMessage()); if (Constants.DEBUG) { operationApplicationException.printStackTrace(); } return false; } FragmentManager fragmentManager = getFragmentManager(); ContactAdditionalDetailsFragment contactAdditionalDetailsFragment = (ContactAdditionalDetailsFragment)fragmentManager.findFragmentById(R.id.contact_additional_details); if (contactAdditionalDetailsFragment != null) { FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.remove(contactAdditionalDetailsFragment); fragmentTransaction.commit(); addressBookContactAdapter.setCheckedItemPosition(-1); } return true; } return super.onOptionsItemSelected(item); }
7. Să se realizeze o dispunere alternativă a listei de contacte din agenda telefonică, astfel încât pe rândurile pare respectiv pe rândurile impare, imaginea asociată unei persoane și pictograma indicând dacă aceasta a fost inclusă în lista de favoriți să fie aranjate invers.
res/layout
se vor defini două fișiere, contact_view_odd.xml
și contact_view_even.xml
, conținând cele două moduri de dispunere diferită;getView()
din clasa ContactAdapter
se vor încărca cele două fișiere succesiv, în funcție de paritatea rândului pentru care se obține interfața grafică: if (position % 2 == 0) { contactView = inflater.inflate(R.layout.contact_view_even, parent, false); } else { contactView = inflater.inflate(R.layout.contact_view_odd, parent, false); }
ContactAdapter
se vor suprascrie metodele:int getViewTypeCount()
- care furnizează numărul de vizualizări diferite ale listei;int getItemViewType(int position)
- care întoarce indexul vizualizării, pentru poziția curentă. public final static int CONTACT_VIEW_TYPES = 2; public final static int CONTACT_VIEW_TYPE_ODD = 0; public final static int CONTACT_VIEW_TYPE_EVEN = 1; @Override public int getViewTypeCount() { return CONTACT_VIEW_TYPES; } @Override public int getItemViewType(int position) { if (position % 2 == 0) { return CONTACT_VIEW_TYPE_ODD; } return CONTACT_VIEW_TYPE_EVEN; }
8. (opțional) Să se implementeze optimizări la nivelul codului sursă pentru a eficientiza timpul de încărcare a listei, în momentul în care utilizatorul realizează operația de derulare prin aceasta:
public ContactAdapter(Activity context) { this.context = context; inflater = (LayoutInflater)context.getLayoutInflater(); data = new ArrayList<Contact>(); initData(); }
getView()
) @Override public View getView(int position, View convertView, ViewGroup parent) { View contactView; Contact contact = data.get(position); if (convertView == null) { LayoutInflater inflater = (LayoutInflater)context.getLayoutInflater(); contactView = inflater.inflate(R.layout.contact_view, parent, false); // ... } else { contactView = convertView; } // ... return contactView; }
ViewHolder
prin care controalele pot fi accesate, fără a mai fi necesară utilizarea metodei findViewById()
public class ContactAdapter extends BaseAdapter { public static class ViewHolder { ImageView contactPhotoImageView, contactStarredImageView; TextView contactNameTextView, contactTimesContactedTextView, contactLastTimeContactedTextView; }; // ... @Override public View getView(int position, View convertView, ViewGroup parent) { View contactView; ViewHolder viewHolder; Contact contact = data.get(position); if (convertView == null) { LayoutInflater inflater = (LayoutInflater)context.getLayoutInflater(); contactView = inflater.inflate(R.layout.contact_view, parent, false); viewHolder = new ViewHolder(); viewHolder.contactPhotoImageView = (ImageView)contactView.findViewById(R.id.contact_photo_image_view); viewHolder.contactNameTextView = (TextView)contactView.findViewById(R.id.contact_name_text_view); viewHolder.contactTimesContactedTextView = (TextView)contactView.findViewById(R.id.contact_times_contacted_text_view); viewHolder.contactLastTimeContactedTextView = (TextView)contactView.findViewById(R.id.contact_last_time_contacted_text_view); viewHolder.contactStarredImageView = (ImageView)contactView.findViewById(R.id.contact_starred_image_view); contactView.setTag(viewHolder); } else { contactView = convertView; } // ... viewHolder = (ViewHolder)contactView.getTag(); viewHolder.contactPhotoImageView.setImageBitmap(...); viewHolder.contactNameTextView.setText(...); viewHolder.contactTimesContactedTextView.setText(...); viewHolder.contactLastTimeContactedTextView.setText(...); viewHolder.contactStarredImageView.setImageResource(...); } return contactView; }
9. Să se încarce modificările realizate în cadrul depozitului 'Laborator05' de pe contul Github personal, folosind un mesaj sugestiv.
student@pdsd2015:~/Laborator05/labtasks$ git add AddressBook/* student@pdsd2015:~/Laborator05/labtasks$ git commit -m "implemented taks for laboratory 05" student@pdsd2015:~/Laborator05/labtasks$ git push Laborator05_perfectstudent master
Joseph ANNUZZI, Jr, Lauren DARCEY, Shane CONDER, Introduction to Android Application Development - Developer's Library, 4th Edition, Addison-Wesley, 2013 - capitolul 8, subcapitolul Using Container Control Classes, capitolul 17, subcapitolul Menus
Bill PHILLIPS, Brian HARDY, Android Programming. The Big Nerd Ranch Guide, Pearson Technology Group, 2013 - capitolele 9, 16
Reto MEIER, Professional Android for Application Development, John Wiley & Sons, 2012 - capitolele 4 (The Android Widget Toolbok, Introducing Adapters), 10 (Configuring Action Bar Icon Navigation Behavior)
Ronan SCHWARZ, Phil DUTSON, James STEELE, Nelson TO, Android Developer's Cookbook, Building Applications with the Android SDK, 2nd Edition, Addison Wesley, 2013 - capitolele 6, 7
Wei Meng LEE, Beginning Android 4 Application Development, Wiley, 2012
Satya KOMATINENI, Dave MACLEAN, Pro Android 4, Apress, 2012
Dezvoltarea aplicațiilor pentru Android
Android Programming Tutorials - Core Servlets - secțiunea Widgets II: Spinners (Combo Boxes)
Using lists in Android (ListView) - Tutorial
Parsing XML Data
Accessing Contacts Data
Query Contacts database using Loader, with Search Function
Adding, Deleting and Retrieving Contacts in Android
Menus