Set Adapter Again After Updating Listview
Update Note: This tutorial is now up to appointment with the latest version of Android Studio version iii.0.i, and uses Kotlin for app development. Update past Joe Howard. Original tutorial by Odie Edo-Osagie.
How many times accept you needed an app to display a group of related items in a list? How well-nigh all the time. :]
Displaying a specific list is essential to the function of well-nigh any app that queries a gear up of information and returns a list of results, so many apps need to do this at one point or another. For example, maybe you lot have a chat app that queries a certain social platform'southward database to find your friends, and so want to display them in a list that lets you select which friends to connect with.
Any time you demand to display a lot of data and brand it like shooting fish in a barrel to navigate, you've got a job for Android's ListView, which handily creates scrollable lists.
In recent years, ListView has been supplanted by RecyclerView. Yet, studying ListView withal has it'south benefits:
- You can gain insights into why RecyclerView works the way information technology does
- You may run into ListView in legacy code, and it'south best to know how to work with it
By working through this tutorial, you'll become familiar with ListView, and you'll do so by creating a recipe listing app. Specifically, you'll learn:
- How to construct and populate a ListView
- How to customize the layout
- How to mode and beautify a ListView
- How to optimize a ListView'south performance
You're welcome to upward your game in the kitchen by learning the recipes too, but maybe wait until you've congenital the app, okay?
Annotation: If you lot're new to Android Development or Kotlin, it's highly recommended that you offset with Beginning Android Development with Kotlin to learn your way around the bones tools and concepts.
Getting Started
To kicking things off, get-go by downloading the materials for this tutorial (yous can notice a link at the pinnacle or bottom of the folio) and open Android Studio 3.0.ane or greater.
In the Welcome to Android Studio dialog, select Open an existing Android Studio project.
In the following dialog, select the top-level directory of the starter projection AllTheRecipes-Starter and click OK.
Inside the imported projection, you lot'll find some assets and resources that you'll use to create your app, such equally strings, colors, XML layout files, and fonts. Additionally, there'south some average lawmaking modeling a Recipe
and a blank bones MainActivity
grade.
Build and run. You should see something like this:
Are you ready to get swell on this listing thing? Awesome!
Add Your First ListView
The first order of business concern is to add a ListView to MainActivity
.
Open res/layout/activity_main.xml. As yous may know, this is the file that describes the layout of MainActivity
. Add a ListView to MainActivity
past inserting the post-obit lawmaking snippet within the ConstraintLayout
tag:
<ListView android:id="@+id/recipe_list_view" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
Open MainActivity and add an example variable for your ListView with the post-obit line:
private lateinit var listView ListView
Add together the post-obit snippet below the existing code within the onCreate
method:
listView = findViewById<ListView>(R.id.recipe_list_view) // 1 val recipeList = Recipe.getRecipesFromFile("recipes.json", this) // 2 val listItems = arrayOfNulls<Cord>(recipeList.size) // 3 for (i in 0 until recipeList.size) { val recipe = recipeList[i] listItems[i] = recipe.championship } // iv val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, listItems) listView.adapter = adapter
Hither's a breakdown of what's happening in at that place:
- This loads a list of
Recipe
objects from a JSON asset in the app. Notice that the starter project contains aRecipe
class that models and stores the information about the recipes that will be displayed. - This creates an assortment of strings that'll incorporate the text to be displayed in the ListView.
- This populates the ListView's data source with the titles of the recipes loaded in section one.
- This creates and sets a simple adapter for the ListView. The
ArrayAdapter
takes in the current context, a layout file specifying what each row in the list should look like, and the data that will populate the list equally arguments.
Enough talk! Your ListView has all that information technology needs to part. Build and run the projection. Y'all should run into something like this:
Adapters: Servants of the ListView
Your recipe app is starting to await functional, but non all that flavory…yet.
In the previous section, you successfully built a list of recipe titles. It works, but it'south nothing to get excited about. What if you lot needed to prove more than just the titles? More than just text? Maybe even add together some screen-licking worthy thumbnails?
For these cases, the simple ArrayAdapter
you just used won't cut it. You lot'll take to have matters into your own hands and write your own adapter. Well, you lot won't actually write your own adapter, per se; you'll simply extend a regular adapter and make some tweaks.
What Exactly is an Adapter?
An adapter loads the information to be displayed from a data source, such as an array or database query, and creates a view for each particular. Then information technology inserts the views into the ListView.
Adapters non just exist for ListViews, but for other kinds of views every bit well; ListView is a subclass of AdapterView, and then you tin can populate it past binding information technology to an adapter.
The adapter acts as the middle human being between the ListView and information source, or its provider. It works kind of similar this:
The ListView asks the adapter what it should brandish, and the adapter jumps into activity:
In short, The ListView isn't very smart, but when given the correct inputs it does a fine chore. It fully relies on the adapter to tell it what to display and how to display information technology.
Building Adapters
Okay, now that yous've dabbled in theory, you can get on with building your very own adapter.
Create a new class by right-clicking on the com.raywenderlich.alltherecipes bundle and selecting New > Kotlin File/Form. Name it RecipeAdapter and define it with the post-obit:
class RecipeAdapter : BaseAdapter() { }
You lot've fabricated the skeleton of the adapter. It extends the BaseAdapter
class, which requires several inherited methods you'll implement after taking care of 1 more than detail.
Update the RecipeAdapter
class as follows:
class RecipeAdapter(private val context: Context, individual val dataSource: ArrayList<Recipe>) : BaseAdapter() { private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater }
In here, you've added the properties that will be associated with the adapter and divers a master constructor for RecipeAdapter
.
Your next stride is to implement the adapter methods. Kicking it off by placing the following code at the lesser of RecipeAdapter
:
//1 override fun getCount(): Int { render dataSource.size } //2 override fun getItem(position: Int): Any { render dataSource[position] } //three override fun getItemId(position: Int): Long { render position.toLong() } //4 override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { // Become view for row particular val rowView = inflater.inflate(R.layout.list_item_recipe, parent, false) render rowView }
Here's a stride-past-step breakdown:
-
getCount()
lets ListView know how many items to display, or in other words, it returns the size of your data source. -
getItem()
returns an item to be placed in a given position from the data source, specifically,Recipe
objects obtained fromdataSource
. - This implements the
getItemId()
method that defines a unique ID for each row in the listing. For simplicity, you just apply the position of the particular equally its ID. - Finally,
getView()
creates a view to be used equally a row in the listing. Here you define what information shows and where it sits within the ListView. You lot too inflate a custom view from the XML layout defined inres/layout/list_item_recipe.xml
— more on this in the adjacent section.
Defining the Layout of the ListView'due south Rows
You probably noticed that the starter project comes with the file res/layout/list_item_recipe.xml
that describes how each row in the ListView should look and be laid out.
Below is an paradigm that shows the layout of the row view and its elements:
Your task is to populate each chemical element of the row view with the relevant recipe information, hence, you'll ascertain what text goes in the "championship" element, the "subtitle" chemical element and so on.
In the getView()
method, add the following code snippet just earlier the return statement:
// Get championship element val titleTextView = rowView.findViewById(R.id.recipe_list_title) equally TextView // Get subtitle element val subtitleTextView = rowView.findViewById(R.id.recipe_list_subtitle) as TextView // Go particular chemical element val detailTextView = rowView.findViewById(R.id.recipe_list_detail) as TextView // Get thumbnail element val thumbnailImageView = rowView.findViewById(R.id.recipe_list_thumbnail) every bit ImageView
This obtains references to each of the elements (or subviews) of the row view, specifically the title, subtitle, particular and thumbnail.
Now that you've got the references sorted out, you need to populate each element with relevant data. To do this, add the following code snippet under the previous one but earlier the render argument:
// one val recipe = getItem(position) equally Recipe // 2 titleTextView.text = recipe.title subtitleTextView.text = recipe.description detailTextView.text = recipe.characterization // 3 Picasso.with(context).load(recipe.imageUrl).placeholder(R.mipmap.ic_launcher).into(thumbnailImageView)
Here's what yous're doing in the in a higher place snippet:
- Getting the corresponding recipe for the electric current row.
- Updating the row view'south text views so they are displaying the recipe.
- Making utilize of the open up-source Picasso library for asynchronous epitome loading — it helps y'all download the thumbnail images on a separate thread instead of the main thread. You lot're also assigning a temporary placeholder for the
ImageView
to handle deadening loading of images.
Note: Y'all should never perform long-running tasks on the main thread. When yous practice, y'all betrayal yourself to the chance of blocking the UI, and that would brand scrolling your lists a nightmare!
At present open upward MainActivity so that yous can get rid of the old adapter. In onCreate
, supplant everything beneath (but not including) this line:
val recipeList = Recipe.getRecipesFromFile("recipes.json", this)
With:
val adapter = RecipeAdapter(this, recipeList) listView.adapter = adapter
You lot but replaced the rather uncomplicated ArrayAdapter
with your own RecipeAdapter
to make the listing more informative.
Build and run and yous should see something like this:
Now y'all're cooking for real! Wait at those recipes — thumbnails and descriptions certain make a big deviation.
Styling
Now that you've got the functionality under wraps, it'due south time to plough your attention to the finer things in life. In this case, your finer things are elements that make your app more snazzy, such as compelling colors and fancy fonts.
Outset with the fonts. Look for some custom fonts under res/font. You'll discover three font files: josefinsans_bold.ttf, josefinsans_semibolditalic.ttf and quicksand_bold.otf.
Open RecipeAdapter.java and go to the getView()
method. Just before the return argument, add together the post-obit:
val titleTypeFace = ResourcesCompat.getFont(context, R.font.josefinsans_bold) titleTextView.typeface = titleTypeFace val subtitleTypeFace = ResourcesCompat.getFont(context, R.font.josefinsans_semibolditalic) subtitleTextView.typeface = subtitleTypeFace val detailTypeFace = ResourcesCompat.getFont(context, R.font.quicksand_bold) detailTextView.typeface = detailTypeFace
In hither, yous're assigning a custom font to each of the text views in your rows' layout. You access the font past creating a Typeface
, which specifies the intrinsic style and typeface of the font, by using ResourcesCompat.getFont()
. Next you set up the typeface
for the corresponding TextView
to set the custom font.
Now build and run. Your outcome should await like this:
On to sprucing up the colors, which are divers in res/values/colors.xml. Open upwardly RecipeAdapter and add the following below the inflater
annunciation:
companion object { private val LABEL_COLORS = hashMapOf( "Low-Carb" to R.color.colorLowCarb, "Depression-Fat" to R.color.colorLowFat, "Low-Sodium" to R.color.colorLowSodium, "Medium-Carb" to R.color.colorMediumCarb, "Vegetarian" to R.color.colorVegetarian, "Balanced" to R.colour.colorBalanced ) }
You lot've created a hash map that pairs a recipe detail characterization with the resource id of a color defined in colors.xml.
Now go to the getView()
method, and add this line only to a higher place the return statement:
detailTextView.setTextColor( ContextCompat.getColor(context, LABEL_COLORS[recipe.label] ?: R.colour.colorPrimary))
Working from the within out:
- Here you lot get the resources id for the color that corresponds to the
recipe.label
from theLABEL_COLORS
hash map. -
getColor()
is used within ofContextCompat
to retrieve the hex color associated with that resource id. - Then you set the colour property of the
detailTextView
to the hex color.
Build and run. Your app should await like this:
User Interaction
Now your list has function and fashion. What's it missing now? Try tapping or long pressing it. There's not much to thrill and delight the user.
What could y'all add here to make the user feel that much more satisfying? Well, when a user taps on a row, don't you call back it'd be nice to prove the full recipe, complete with instructions?
You'll make use of AdapterView.onItemClickListener and a brand spanking new activity to do this with elegance.
Make a New Activity
This activeness will brandish when the user selects an item in the list.
Right-click on com.raywenderlich.alltherecipes and then select New > Activity > EmptyActivity to bring up a dialog. Fill up in the Activity Name with RecipeDetailActivity. Leave the automatically populated fields equally-is. Check that your settings match these:
Click Finish.
Open up res/layout/activity_recipe_detail.xml and add together a WebView
by inserting the post-obit snippet inside the ConstraintLayout
tag:
<WebView android:id="@+id/detail_web_view" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
WebView
volition be used to load and display a webpage containing the selected recipe's instructions.
Open up up RecipeDetailActivity, and add together a WebView
reference as a belongings by adding the following line within the course definition:
individual lateinit var webView: WebView
Add the following below the webView
property declaration:
companion object { const val EXTRA_TITLE = "title" const val EXTRA_URL = "url" fun newIntent(context: Context, recipe: Recipe): Intent { val detailIntent = Intent(context, RecipeDetailActivity::class.coffee) detailIntent.putExtra(EXTRA_TITLE, recipe.championship) detailIntent.putExtra(EXTRA_URL, recipe.instructionUrl) return detailIntent } }
This adds a companion object method to return an Intent for starting the detail activity, and sets up title and url extras in the Intent.
Head back to MainActivity and add the following to the bottom of the onCreate
method:
val context = this listView.setOnItemClickListener { _, _, position, _ -> // one val selectedRecipe = recipeList[position] // 2 val detailIntent = RecipeDetailActivity.newIntent(context, selectedRecipe) // 3 startActivity(detailIntent) }
Annotation: Before you dive into the explanation, make certain you understand the 4 arguments that are provided by onItemClick
; they work as follows:
- parent: The view where the selection happens — in your example, information technology'due south the ListView
- view: The selected view (row) within the ListView
- position: The position of the row in the adapter
- id: The row id of the selected item
You're setting the OnItemClickListener
object for the ListView, and inside doing the following:
- Become the recipe object for the row that was clicked
- Create an intent to navigate to your
RecipeDetailActivity
to display more than information - Launch the
RecipeDetailActivity
by passing the intent object you lot just created to thestartActivity()
method.
Once once again, open RecipeDetailActivity and add together the following snippet at the bottom of the onCreate
method:
// ane val title = intent.extras.getString(EXTRA_TITLE) val url = intent.extras.getString(EXTRA_URL) // 2 setTitle(title) // iii webView = findViewById(R.id.detail_web_view) // 4 webView.loadUrl(url)
You lot can see a few things happening here:
- Y'all remember the recipe data from the
Intent
passed fromMainActivity
past using theextras
belongings. - You prepare the title on the action bar of this activity to the recipe championship.
- Y'all initialize
webView
to the spider web view defined in the XML layout. - Y'all load the recipe web page past calling
loadUrl()
with the corresponding recipe'south URL on the web view object.
Build and run. When you click on the first item in the list, you should see something like this:
Optimizing Operation
Whenever you scroll the ListView, its adapter's getView()
method is called in gild to create a row and display it on screen.
At present, if you wait in your getView()
method, you'll observe that each time this method is called, it performs a lookup for each of the row view's elements by using a call to the findViewById()
method.
These repeated calls can seriously harm the ListView's performance, peculiarly if your app is running on express resources and/or you lot have a very large listing. You can avoid this problem past using the View Holder Pattern.
Implement a ViewHolder Pattern
To implement the ViewHolder pattern, open RecipeAdapter and add the following afterwards the getView()
method definition:
individual course ViewHolder { lateinit var titleTextView: TextView lateinit var subtitleTextView: TextView lateinit var detailTextView: TextView lateinit var thumbnailImageView: ImageView }
As you lot can see, y'all create a class to concur your exact fix of component views for each row view. The ViewHolder
class stores each of the row's subviews, and in turn is stored inside the tag field of the layout.
This ways yous can immediately access the row'south subviews without the need to look them up repeatedly.
Now, in getView()
, supplant everything above (but NOT including) this line:
val recipe = getItem(position) as Recipe
With:
val view: View val holder: ViewHolder // ane if (convertView == null) { // 2 view = inflater.inflate(R.layout.list_item_recipe, parent, false) // iii holder = ViewHolder() holder.thumbnailImageView = view.findViewById(R.id.recipe_list_thumbnail) as ImageView holder.titleTextView = view.findViewById(R.id.recipe_list_title) as TextView holder.subtitleTextView = view.findViewById(R.id.recipe_list_subtitle) every bit TextView holder.detailTextView = view.findViewById(R.id.recipe_list_detail) as TextView // 4 view.tag = holder } else { // v view = convertView holder = convertView.tag every bit ViewHolder } // 6 val titleTextView = holder.titleTextView val subtitleTextView = holder.subtitleTextView val detailTextView = holder.detailTextView val thumbnailImageView = holder.thumbnailImageView
Here'due south the play-by-play of what's happening higher up.
- Check if the view already exists. If it does, in that location's no need to inflate from the layout and phone call
findViewById()
again. - If the view doesn't exist, y'all inflate the custom row layout from your XML.
- Create a new
ViewHolder
with subviews initialized by usingfindViewById()
. - Hang onto this holder for time to come recycling past using
setTag()
to prepare the tag property of the view that the holder belongs to. - Skip all the expensive inflation steps and just go the holder yous already made.
- Get relevant subviews of the row view.
Finally, update the return statement of getView()
with the line below.
render view
Build and run. If your app was running a flake tedious on the last build, you should see it running smoother now. :]
Where to Go From Here?
You can download the completed projection using the download button at the top or bottom of this tutorial.
When you lot develop for Android, AdapterViews are a common concept that you'll meet over and over once more.
If you want to know more than about the inner workings of the ListView and performance details, check out this article on functioning tips for Android ListViews.
There are other ways to create lists, such every bit subclassing a ListActivity and ListFragment. Both of these links take you lot to the official Android developer site then that you tin can learn more than nigh how they work.
Both of these alternatives impose the brake that the respective activity or fragment can only comprise a ListView every bit its child view. Suppose yous wanted an action that had a ListView as well as some other views, it would exist impossible with a ListActivity. The same goes for the ListFragment scenario.
And be certain to cheque out our RecyclerView and Intermediate RecyclerView tutorials to see the more than modern style to prove lists on Android. Unlike ListView, RecyclerView enforces the use of the ViewHolder pattern and is much more flexible in terms of layout and animation.
Feel free to share your feedback, findings or enquire any questions in the comments beneath or in the forums. Talk to y'all before long!
Source: https://www.raywenderlich.com/155-android-listview-tutorial-with-kotlin
0 Response to "Set Adapter Again After Updating Listview"
Postar um comentário