This is an old revision of the document!
Pentru rezolvarea subiectelor propuse în cadrul colocviului 1, sunt necesare:
Proiectul Eclipse corespunzător aplicației Android ce conține rezolvările complete ale cerințelor colocviului este disponibil pe contul de Github al disciplinei.
A.1. Se accesează Github și se realizează autentificarea în contul personal, prin intermediul butonului Sign in.
Se creează o zonă de lucru corespunzătoare unui proiect prin intermediului butonului New Repository.
Configurarea depozitului la distanță presupune specificarea:
git init
;git clone
- depozitul la distanță nu trebuie să fie vid, în această situațoe README
(opțional);.gitignore
;
A.2. Prin intermediul comenzii git clone
se poate descărca întregul conținut în directorul curent (de pe discul local), inclusiv istoricul complet al versiunilor anterioare (care poate fi ulterior reconstituit după această copie, în cazul coruperii informațiilor stocate pe serverul la distanță).
student@eim2016:~$ git clone https://www.github.com/perfectstudent/PracticalTest01
A.3. Se urmăresc indicațiile disponibile în secțiunea Crearea unei aplicații Android în Eclipse Luna SR1a (4.4.1).
A.4. Implementarea interfeței grafice va fi realizată prin intermediul unui fișier .xml
care va fi plasat în directorul /res/layout
al proiectului.
De cele mai multe ori, interfața grafică poate fi realizată în două moduri:
RelativeLayout
în care vor fi plasate toate controalele, având poziția definită relativ unele la celelalte sau la marginile părintelui în care sunt incluse;LinearLayout
, imbricate, având orientări diferite (orizontal - implicit, sau vertical).RelativeLayout
android:layout_alignLeft
și android:layout_alignRight
(proprietatea android:layout_width
fiind ignorată în acest caz). În mod similar, dacă se dorește alinierea cu un control aflat la stânga (aliniere pe verticală), acesta va fi precizat ca valoare pentru proprietățile android:layout_alignTop
și android:layout_alignBottom
(proprietatea android:layout_height
fiind ignorată în acest caz).
<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="wrap_content" tools:context="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01MainActivity" > <Button android:id="@+id/navigate_to_secondary_activity_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:text="@string/navigate_to_secondary_activity" /> <EditText android:id="@+id/left_edit_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:saveEnabled="false" android:enabled="false" android:inputType="number" android:ems="7" android:layout_alignParentLeft="true" android:layout_below="@id/navigate_to_secondary_activity_button" android:gravity="center" /> <EditText android:id="@+id/right_edit_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:saveEnabled="false" android:enabled="false" android:inputType="number" android:ems="7" android:layout_alignParentRight="true" android:layout_below="@id/navigate_to_secondary_activity_button" android:gravity="center" /> <Button android:id="@+id/left_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/left_edit_text" android:layout_below="@id/left_edit_text" android:layout_alignRight="@id/left_edit_text" android:text="@string/press_me" /> <Button android:id="@+id/right_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/right_edit_text" android:layout_alignRight="@id/right_edit_text" android:layout_below="@id/right_edit_text" android:text="@string/press_me_too" /> </RelativeLayout>
LinearLayout
imbricate
android:layout_weight
, însă pentru a fi luată în considerare, atributul android:layout_width
, respectiv android:layout_height
(în funcție de coordonata pe care se dorește obținerea unei astfel de funcționalități) trebuie să aibă valoarea 0dp
.
<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="wrap_content" android:orientation="vertical" tools:context="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01MainActivity" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <EditText android:id="@+id/left_edit_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:enabled="false" android:inputType="number" android:gravity="center" /> <EditText android:id="@+id/right_edit_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:enabled="false" android:inputType="number" android:gravity="center" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:baselineAligned="false" > <ScrollView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> <Button android:id="@+id/left_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/press_me" /> </ScrollView> <ScrollView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> <Button android:id="@+id/right_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/press_me_too" /> </ScrollView> </LinearLayout> </LinearLayout>
În activitatea corespunzătoare, interfața grafică definită în fișierul .xml
trebuie încărcată pe metoda onCreate()
prin intermediul setContentView()
, căreia i se transmite ca parametru identificatorul (referința) către această resursă (așa cum a fost generată în clasa abstractă R.layout
). În situația în care este necesar să se realizeze anumite operații pe controalele grafice componente (spre exemplu, să se încarce conținutul acestora), trebuie inițial să se obțină o referință către aceasta printr-un apel al metodei findViewById()
, care primește de asemenea ca parametru identificatorul (referința) către această resursă (așa cum a fost generată în clasa abstractă R.id
).
onCreateOptionsMenu()
, respectiv onOptionsItemSelected()
, acestea fiind de regulă generate în mod automat de mediul integrat de dezvoltare Eclipse.
package ro.pub.cs.systems.eim.practicaltest01; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class PracticalTest01MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practical_test01_main); EditText leftEditText = (EditText)findViewById(R.id.left_edit_text); EditText rightEditText = (EditText)findViewById(R.id.right_edit_text); leftEditText.setText(String.valueOf(0)); rightEditText.setText(String.valueOf(0)); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.practical_test01, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
B.1. Pentru procesarea evenimentelor legate de interacțiunea de utilizator este necesară:
Pe metoda onCreate()
a activității se obțin referințe ale controlului grafic (prin metoda findViewById()
) și se înregistrează obiectul ascultător respectiv.
public class PracticalTest01MainActivity extends Activity { private ButtonClickListener buttonClickListener = new ButtonClickListener(); private class ButtonClickListener implements Button.OnClickListener { @Override public void onClick(View view) { EditText leftEditText = (EditText)PracticalTest01MainActivity.this.findViewById(R.id.left_edit_text); EditText rightEditText = (EditText)PracticalTest01MainActivity.this.findViewById(R.id.right_edit_text); int leftButtonClickedNumber = Integer.parseInt(leftEditText.getText().toString()); int rightButtonClickedNumber = Integer.parseInt(rightEditText.getText().toString()); switch(view.getId()) { case R.id.left_button: leftButtonClickedNumber++; leftEditText.setText(String.valueOf(leftButtonClickedNumber)); break; case R.id.right_button: rightButtonClickedNumber++; rightEditText.setText(String.valueOf(rightButtonClickedNumber)); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... Button leftButton = (Button)findViewById(R.id.left_button); leftButton.setOnClickListener(buttonClickListener); Button rightButton = (Button)findViewById(R.id.right_button); rightButton.setOnClickListener(buttonClickListener); } // ... }
B.2.
a) Pentru majoritatea controalelor grafice, sistemul de operare Android realizează în mod automat salvarea / restaurarea stării în situația în care o activitate este distrusă pentru asigurarea necesarului de memorie. Dacă se dorește ca această operație să fie tratată în alt mod, dezactivarea acestui mecanism se realizează prin specificarea atributului android:saveEnabled
corespunzător controlului grafic respectiv având valoarea false
.
<LinearLayout ... > <EditText android:id="@+id/left_edit_text" android:saveEnabled="false" ... /> <EditText android:id="@+id/right_edit_text" android:saveEnabled="false" ... /> <!-- other nested layouts or widgets --> </LinearLayout>
b) În situația în care sistemul de operare Android distruge activitatea pentru asigurarea resurselor necesare, este necesară asigurarea unui comportament consistent / persistenței aplicației:
onSaveInstanceState()
care primește un parametru de tip Bundle
în care vor fi stocate informațiile respective, identificarea lor realizându-se prin intermediul unei chei de tip șir de caractere; metoda este invocată înainte de onStop()
deși pot exista situații în care aceasta este apelată chiar înainte de onPause()
;onCreate()
pentru care parametrul de tip Bundle
este nenul în situația în care există o stare anterioară;onRestoreInstanceState()
care este invocată în mod automat - primind un parametru de tip Bundle
numai în situația în care există o stare anterioară; metoda se apelează între metodele onStart()
și onRestore()
.
Bundle
nu pot fi stocate decât obiecte care implementează android.os.Parcelable
.
Salvarea stării implică plasarea în obiectul de tip Bundle
a unei valori, prin metode de tip put<type>()
, folosind un șir de caractere definit de utilizator (o convenție de nume).
Restaurarea stării implică preluarea în obiectul de tip Bundle
a valorii, prin metode de tip get<type>()
, folosind șirul de caractere corespunzător (convenția de nume). Se recomandă să se verifice faptul că valoarea respectivă este nenulă (respectiv nu a fost furnizată valoarea implicită).
public class PracticalTest01MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... if (savedInstanceState != null) { String leftCount = savedInstanceState.getString("leftCount"); if (leftCount != null) { leftEditText.setText(leftCount); } else { leftEditText.setText(String.valueOf(0)); } String rightCount = savedInstanceState.getString("rightCount"); if (rightCount != null) { rightEditText.setText(rightCount); } else { rightEditText.setText(String.valueOf(0)); } } else { leftEditText.setText(String.valueOf(0)); rightEditText.setText(String.valueOf(0)); } } @Override protected void onSaveInstanceState(Bundle savedInstanceState) { EditText leftEditText = (EditText)findViewById(R.id.left_edit_text); EditText rightEditText = (EditText)findViewById(R.id.right_edit_text); savedInstanceState.putString("leftCount", leftEditText.getText().toString()); savedInstanceState.putString("rightCount", rightEditText.getText().toString()); } // ... }
c) Pentru a simula faptul că sistemul de operare Android distruge activitatea pentru asigurarea necesarului de resurse, se poate proceda astfel:
onPause()
și onStop()
și implicit a metodei onSaveInstanceState()
;onCreate()
, respectiv onStart()
și onResume()
(și, implicit, onRestoreInstanceState()
).
d) În situația în care utilizatorul apasă butonul Back, se apelează metodele onPause()
, onStop()
și onDestroy()
, fără a se invoca însă și metoda onSaveInstanceState()
întrucât se consideră că în această situație utilizatorul nu își dorește să revină la starea curentă a activității. Din acest motiv, starea nu este salvată în obiectul de tip Bundle
și acesta nu va conține informațiile respective care să poată fi astfel restaurate.
Dacă se dorește salvarea / restaurarea stării în situația în care utilizatorul apasă tasta Back, se recomandă să se utilizeze un alt mecanism pentru asigurarea persistenței, cum ar fi baza de date SQLite sau un obiect de tipul android.content.SharedPreferences
.
C.1. Într-o aplicație Android, o activitate trebuie să fie precizată prin următoarele elemente:
<activity>
în fișiserul AndroidManifest.xml
, având asociată și un filtru de intenții (etichetă de tip <intent-filter>
);.xml
în directorul /res/layout
;Activity
, în care să se implementeze cel puțin metoda onCreate()
, pe care să se încarce interfața grafică definită anterior.Crearea acestor resurse este realizată în mod automat în mediul integrat de dezvoltare Eclipse în momentul în care se solicită definirea unei resurse de tip Android Activity (File → New → Other → Android → Android Activity).
a) În fișierul AndroidManifest.xml
se specifică activitatea printr-o element de tip <activity>
în cadrul etichetei <application>
pentru care se definesc:
android:name
în care se indică denumirea clasei de tip Activity
care va gestiona activitatea (afișarea interfeței grafice și tratarea interacțiunii cu utilizatorul);android:label
precizează o etichetă care va fi asociată activității;<action>
stabilește acțiunea pe care o poate realiza activitatea; va avea o valoare definită de utilizator, reprezentând o convenție;<category>
clasifică activitatea, impunându-se să fie folosită valoarea android.intent.category.DEFAULT
pentru ca activitatea să poată fi invocată prin intermediul unei intenții.
AndroidManifest.xml
nu se completează în mod automat și filtrul de intenții, fiind necesar ca acesta să fie menționat de utilizator, manual.
<manifest ... > <application ... > <activity android:name=".PracticalTest01SecondaryActivity" android:label="@string/title_activity_practical_test01_secondary" > <intent-filter> <action android:name="ro.pub.cs.systems.eim.intent.action.PracticalTest01SecondaryActivity" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest>
b) O interfață grafică este definită în mod obișnuit, prin intermediul unui fișier .xml
plasat în directorul /res/layout
în care sunt precizate controalele grafice cu atributele și valorile lor, sub formă de componente în cadrul unor mecanisme de dispunere.
<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="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01SecondaryActivity" > <TextView android:id="@+id/number_of_clicks_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" > <Button android:id="@+id/ok_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ok" /> <Button android:id="@+id/cancel_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/cancel" /> </LinearLayout> </LinearLayout>
c) În clasa care gestionează comportamentul activității, pe metoda onCreate()
, în mod obișnuit, se încarcă interfața grafică (prin metoda setContentView()
), se obțin referințe către controalele grafice (printr-un apel al metodei findViewById()
) și se stabilește conținutul acestora, respectiv se înregistrează obiecte ascultător pentru anumite tipuri de evenimente (după ce în prealabil a fost implementată și clasa corespunzătoare).
Pentru activitățile care sunt invocate prin intermediul unei intenții:
extra
prin intermediul metodelor getExtras()
sau get<type>Extra()
care primesc ca parametru cheia sub care au fost stocate datele respective;setResult()
care primește ca parametri:
Metoda finish()
poate fi folosită pentru a finaliza activitatea în mod forțat (semnalând faptul că procesările corespunzătoare activității au fost terminate).
package ro.pub.cs.systems.eim.practicaltest01; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.TextView; public class PracticalTest01SecondaryActivity extends Activity { private ButtonClickListener buttonClickListener = new ButtonClickListener(); private class ButtonClickListener implements Button.OnClickListener { @Override public void onClick(View view) { switch(view.getId()) { case R.id.ok_button: setResult(RESULT_OK, new Intent()); finish(); break; case R.id.cancel_button: setResult(RESULT_CANCELED, new Intent()); finish(); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practical_test01_secondary); TextView numberOfClicksTextView = (TextView)findViewById(R.id.number_of_clicks_text_view); Intent intent = getIntent(); if (intent != null) { String numberOfClicks = intent.getStringExtra("number_of_clicks"); if (numberOfClicks != null) { numberOfClicksTextView.setText(getResources().getString(R.string.number_of_clicks).replace("???", numberOfClicks)); } } Button buttonOk = (Button)findViewById(R.id.ok_button); buttonOk.setOnClickListener(buttonClickListener); Button buttonCancel = (Button)findViewById(R.id.cancel_button); buttonCancel.setOnClickListener(buttonClickListener); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.practical_test01_secondary, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
C.2. Pentru a invoca o altă activitate prin intermediul unei intenții, o activitate trebuie:
.xml
din directorul /res/layout
);<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="wrap_content" android:orientation="vertical" tools:context="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01MainActivity" > <Button android:id="@+id/navigate_to_secondary_activity_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="@string/navigate_to_secondary_activity" /> <!-- other nested layouts or widgets --> </LinearLayout>
onCreate()
:Intent
, care poate primi ca parametru:this
) și clasa care deservește activitatea (obiect de tip .class
) - adecvată situației în care activitatea invocată se găsește în aceeași aplicație ca și activitatea care invocă;extra
a intenției) prin intermediul metodei putExtra()
, fiecare valoare (de tip android.os.Parcelable
) având asociat un atribut prin care va fi identificat ulterior, de tip șir de caractere;startActivityForResult()
care primește ca parametru intenția și un cod de cerere, având rolul de a identifica ulterior instanța activității din care se revine.onActivityResult()
, apelată în mod automat la revenirea din activitatea invocată, aceasta primind ca parametrii:public class PracticalTest01MainActivity extends Activity { private final static int SECONDARY_ACTIVITY_REQUEST_CODE = 1; private ButtonClickListener buttonClickListener = new ButtonClickListener(); private class ButtonClickListener implements Button.OnClickListener { @Override public void onClick(View view) { EditText leftEditText = (EditText)PracticalTest01MainActivity.this.findViewById(R.id.left_edit_text); EditText rightEditText = (EditText)PracticalTest01MainActivity.this.findViewById(R.id.right_edit_text); switch(view.getId()) { case R.id.navigate_to_secondary_activity_button: Intent intent = new Intent("ro.pub.cs.systems.eim.intent.action.PracticalTest01SecondaryActivity"); intent.putExtra("number_of_clicks", String.valueOf( Integer.parseInt(leftEditText.getText().toString()) + Integer.parseInt(rightEditText.getText().toString()) )); startActivityForResult(intent, SECONDARY_ACTIVITY_REQUEST_CODE); break; // ... } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... Button navigateToSecondaryActivityButton = (Button)findViewById(R.id.navigate_to_secondary_activity_button); navigateToSecondaryActivityButton.setOnClickListener(buttonClickListener); } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { Toast.makeText(this, "The activity returned with result "+resultCode, Toast.LENGTH_LONG).show(); } // ... }
C.3. Pentru încărcarea codului în contextul depozitului din cadrul contului Github personal:
git add
, indicându-se și fișierele respective (pot fi folosite șabloane pentru a desemna mai multe fișiere);git commit -m
, precizându-se și un mesaj sugestiv:git push
, care primește ca parametrii:origin
se indică depozitul de unde au fost descărcat conținutul);master
).student@eim2016:~/PracticalTest01$ git add * student@eim2016:~/PracticalTest01$ git commit -m "finished tasks for PracticalTest01" student@eim2016:~/PracticalTest01$ git push origin master
În cazul în care este necesar, vor fi configurați parametrii necesari operației de consemnare (numele de utilizator și adresa de poștă electronică):
student@eim2016:~/PracticalTest01$ git config --global user.name "Perfect Student" student@eim2016:~/PracticalTest01$ git config --global user.email perfectstudent@cs.pub.ro