Android kotlin – les listes
Intro
- réaliser le diagramme de classe permettant de décrire l’interaction entre les layouts, les Activity et Adapter des parties 1 et 2.
Partie 1 – RecyclerView
Livrable : oless/mission4/…
On va afficher une liste de villes dans une liste déroulante performante
- ajouter dans build.gradle(app)
dependencies { ... implementation "androidx.recyclerview:recyclerview:1.1.0" }
- ajouter le layout list_ville correspondant à la liste d’item
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/villes_recycler_view" android:layout_height="match_parent" android:layout_width="match_parent" /> </RelativeLayout>
- ajouter le layout item_ville d’un item
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp"> <TextView android:id="@+id/item_nom" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
- ajouter une classe VilleAdapter qui permet d’afficher chaque élement de la liste
class VilleAdapter(val villes: Array<String>) : RecyclerView.Adapter<VilleAdapter.ViewHolder>() { class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ val nom=itemView.findViewById(R.id.item_nom) as TextView } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) val viewItem = inflater.inflate(R.layout.item_ville,parent,false) return ViewHolder(viewItem) } override fun getItemCount(): Int { return villes.size } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val ville= villes[position] holder.nom.text=ville } }
- ajouter dans l’activité VilleActivity
class MainActivity : AppCompatActivity() { var villes=arrayOf<String>("Erceville", "Ercheu", "Erdeven", "Ergersheim", "Ergny", "Ergue-Gaberic", "Erize-Saint-Dizier", "Ermenonville", "Ermont", "Ernee", "Ernemont-sur-Buchy", "Ernestviller", "Ernolsheim-Bruche", "Erome", "Eroudeville", "Erquinghem-Lys", "Erquinvillers", "Erquy", "Erre", "Errouville", "Erstein", "Ervauville", "Esbarres", "Esbly", "Escalquens", "Escames", "Escassefort", "Escaudain", "Escaudoeuvres", "Escautpont", "Escazeaux", "Eschau", "Eschbach-au-Val", "Eschentzwiller", "Esches", "Esclainvillers", "Escolives-Sainte-Camille", "Escombres-et-le-Chesnois", "Escondeaux", "Escorneboeuf", "Escou", "Escout", "Escoutoux", "Escurolles", "Esery", "Eslettes", "Esmery-Hallon", "Esnandes", "Esnouveaux", "Espagnac", "Espalais", "Espalion", "Espaly-Saint-Marcel", "Esparron-de-Verdon", "Espedaillac", "Espelette", "Espeluche", "Espezel", "Espiet", "Espinasses", "Espira-de-Conflent", "Espirat", "Espondeilhan", "Esquay-Notre-Dame", "Esquay-sur-Seulles", "Esquelbecq", "Esquerchin", "Esquerdes"); val adapter = VilleAdapter(villes) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.list_ville ) villes_recycler_view!!.layoutManager=LinearLayoutManager(this) villes_recycler_view!!.adapter=adapter } }
- ajouter un bouton à l’activité principale pour voir VilleActivity
- ajouter un menu
Partie 2 – les contacts
Livrable : oless/mission4/…
On va récupérer les contacts du téléphone et les afficher
- ajouter la permission au manifest
<uses-permission android:name="android.permission.READ_CONTACTS" />
- réaliser la classe Contact qui a deux propriétés nom et numero
data class Contact(val nom : String= "", val numero : String): Parcelable { constructor(parcel: Parcel) : this( parcel.readString().toString(), parcel.readString().toString() ) { } override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(nom) parcel.writeString(numero) } override fun describeContents(): Int { return 0 } companion object CREATOR : Parcelable.Creator<Contact> { override fun createFromParcel(parcel: Parcel): Contact { return Contact(parcel) } override fun newArray(size: Int): Array<Contact?> { return arrayOfNulls(size) } } }
- réaliser le layout item_contact qui permet d’afficher une ligne
<?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="30dp" android:orientation="horizontal"> <TextView android:id="@+id/item_nom" android:layout_height="wrap_content" android:layout_width="0dp" android:layout_weight="0.2" android:layout_marginLeft="1dp" /> <TextView android:id="@+id/item_numero" android:layout_width="0dp" android:layout_weight="0.2" android:layout_height="wrap_content" android:layout_toRightOf="@id/item_nom" android:layout_marginLeft="10dp" /> </LinearLayout>
- réaliser le layout list_contact qui permet d’afficher la liste
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/contacts_recycler_view" android:layout_height="match_parent" android:layout_width="match_parent" /> </RelativeLayout>
- réaliser la classe ContactAdapter qui permet d’afficher un item
class ContactAdapter(val contacts : ArrayList<Contact>) : RecyclerView.Adapter<ContactAdapter.ViewHolder>() { class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ val nom=itemView.findViewById(R.id.item_nom) as TextView val numero=itemView.findViewById(R.id.item_numero) as TextView } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) val viewItem = inflater.inflate(R.layout.item_contact,parent,false) return ViewHolder(viewItem) } override fun getItemCount(): Int { return contacts.size } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val contact = contacts[position] holder.nom.text=contact.nom holder.numero.text=contact.numero } }
- réaliser une activité Contact Activity qui permette de récupérer depuis le téléphone les contacts en demandant l’autorisation et d’afficher la liste
class ContactActivity : AppCompatActivity() { companion object { val PERMISSIONS_REQUEST_READ_CONTACTS = 100 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) loadContacts() } private fun loadContacts() { var contactsX= arrayListOf<Contact>() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission( Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS), PERMISSIONS_REQUEST_READ_CONTACTS) //callback onRequestPermissionsResult } else { contactsX = getContacts() val adapter = ContactAdapter(contactsX) setContentView(R.layout.list_contact) contacts_recycler_view!!.layoutManager= LinearLayoutManager(this) contacts_recycler_view!!.adapter=adapter } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { loadContacts() } else { Toast.makeText(this,"Permission must be granted in order to display contacts information",Toast.LENGTH_LONG).show() } } } private fun getContacts(): ArrayList<Contact> { var contactsX= arrayListOf<Contact>() val resolver: ContentResolver = contentResolver; val cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null) if (cursor!!.count > 0) { while (cursor.moveToNext()) { val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)) val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) val phoneNumber = (cursor.getString( cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))).toInt() if (phoneNumber > 0) { val cursorPhone = contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?", arrayOf(id), null) if(cursorPhone!!.count > 0) { while (cursorPhone.moveToNext()) { val phoneNumValue = cursorPhone.getString( cursorPhone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)) contactsX.add(Contact(name,phoneNumValue)) } } cursorPhone.close() } } } else { Toast.makeText(this,"pas de contact",Toast.LENGTH_LONG).show() } cursor.close() return contactsX } }
- ajouter des contacts au téléphone, on pourra connecter son compte pour les importer automatiquement.
- modifier votre MainActivity pour afficher ContactActivity
Amélioration de la liste
On va améliorer l’affichage et récupérer l’élément de la liste au clique.
- ajouter la dépendance cardView dans build.gradle
implementation "androidx.cardview:cardview:1.0.0"
- modifier item_contact, on ajoute le cardView et l’écoute d’événement
<?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="40dp" android:orientation="horizontal"> <androidx.cardview.widget.CardView android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="38dp" android:clickable="true" android:focusable="true" android:foreground="?android:attr/selectableItemBackground"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <TextView android:id="@+id/item_nom" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_weight="0.2" /> <TextView android:id="@+id/item_numero" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/item_nom" android:layout_weight="0.2" /> </LinearLayout> </androidx.cardview.widget.CardView> </LinearLayout>
- modifier ContactAdapter pour gérer l’évenement
class ContactAdapter(val contacts : ArrayList<Contact>, /*new*/ val itemClickListener: View.OnClickListener) : RecyclerView.Adapter<ContactAdapter.ViewHolder>() { class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ /*new*/ val cardView=itemView.findViewById(R.id.card_view) as CardView val nom=itemView.findViewById(R.id.item_nom) as TextView val numero=itemView.findViewById(R.id.item_numero) as TextView } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) val viewItem = inflater.inflate(R.layout.item_contact,parent,false) return ViewHolder(viewItem) } override fun getItemCount(): Int { return contacts.size } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val contact = contacts[position] holder.nom.text=contact.nom holder.numero.text=contact.numero /*new*/ holder.cardView.tag=position holder.cardView.setOnClickListener(itemClickListener) }
- modifier ContactActivity qui récupère l’événement de l’affiche
class ContactActivity : AppCompatActivity(),/*new*/ View.OnClickListener { companion object { val PERMISSIONS_REQUEST_READ_CONTACTS = 100 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) loadContacts() } private fun loadContacts() { var contactsX= arrayListOf<Contact>() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission( Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS), PERMISSIONS_REQUEST_READ_CONTACTS) //callback onRequestPermissionsResult } else { contactsX = getContacts() /*new*/ val adapter = ContactAdapter(contactsX,this) setContentView(R.layout.list_contact) contacts_recycler_view!!.layoutManager= LinearLayoutManager(this) contacts_recycler_view!!.adapter=adapter } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { loadContacts() } else { Toast.makeText(this,"Permission must be granted in order to display contacts information",Toast.LENGTH_LONG).show() } } } var contactsX= arrayListOf<Contact>() private fun getContacts(): ArrayList<Contact> { val resolver: ContentResolver = contentResolver; val cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null) if (cursor!!.count > 0) { while (cursor.moveToNext()) { val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)) val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) val phoneNumber = (cursor.getString( cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))).toInt() if (phoneNumber > 0) { val cursorPhone = contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?", arrayOf(id), null) if(cursorPhone!!.count > 0) { while (cursorPhone.moveToNext()) { val phoneNumValue = cursorPhone.getString( cursorPhone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)) contactsX.add(Contact(name,phoneNumValue)) } } cursorPhone.close() } } } else { Toast.makeText(this,"pas de contact",Toast.LENGTH_LONG).show() } cursor.close() return contactsX } /*new*/ override fun onClick(view: View) { if(view.tag !=null){ val index=view.tag as Int val contact=contactsX[index] Toast.makeText(this,"${contact.nom}",Toast.LENGTH_SHORT).show() } } }
- afficher le numéro de téléphone dans le Toast plutôt que le nom