I have a JSON string with the multiple instances of the following
Name
Message
Timestamp
Profile photo url
I want to create a ListView where each list will have the above. Which is the better Adapter I need to extend for this particular case, to create a Custom Adapter?
My assumption is that you have parsed the JSON string into an object model prior to considering either case... let's call this class Profile.
Strategy 1
An ArrayAdapter is sufficient with an overriden getView(..) method that will concatenate your multiple fields together in the way you wish.
ArrayAdapter<Profile> profileAdapter = new ArrayAdapter<Profile>(context, resource, profiles) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
// use the ViewHolder pattern here to get a reference to v, then populate
// its individual fields however you wish... v may be a composite view in this case
}
}
I strongly recommend the ViewHolder pattern for potentially large lists:
https://web.archive.org/web/20110819152518/http://www.screaming-penguin.com/node/7767
It makes an enormous difference.
Advantage 1. Allows you to apply a complex layout to each item on the ListView.
Advantage 2. Does not require you to manipulate toString() to match your intended display (after all, keeping model and view logic separate is never a bad design practice).
Strategy 2
Alternatively, if the toString() method on your representative class already has a format that is acceptable for display you can use ArrayAdapter without overriding getView().
This strategy is simpler, but forces you to smash everything into one string for display.
ArrayAdapter<Profile> profileAdapter = new ArrayAdapter<Profile>(context, resource, profiles)
Related
Is it possible to have a ListView that draws from multiple sources? I want to have some hard-coded items and items from a ContentProvider in the same list, and I just want to know if that's possible.
You could have both types of items implement an interface such as
public interface Item {
int TYPE_1 = 1;
int TYPE_2 = 2;
int getViewType();
View getView(LayoutInflater inflater, View convertView, ViewGroup parent);
}
Then your Adapter can be for a list of Items. Also, if you're unfamiliar with the View Holder pattern I'd recommend looking it up. A quick search revealed a pretty good looking example here
You can let them extends a common parent class, and then use it to construct the adapter. I have done this before, I hope to help you.
So, I realized that
I don't care if ContentProvider data is updated after the fragment loads and
I want to do manipulations on the data in my list that I do not want reflected in the ContentProvider.
So I'm going to dump my cursor results into an ArrayList along with my hardcoded items, and I think that should work fine.
I am trying to speed up an app as much as possible by calling findViewById() as little as possible in the Adapter class.
I've heard of a "way" where I can create a HashMap<T> containing View instances and integers as values, and use it along or together with adapter's ViewHolder inner class. As you can see, I heard very little about this and did not have chance to ask more questions about this "way".
I have been searching for the solution on the Internet, but I am either searching using the wrong keywords or I am not recognizing solutions as such.
Can anyone direct me in a right way? Maybe someone writing a small sample on how to accomplish this?
EDIT 1
I don't have any issues with my current ListView as it's already using ViewHolder pattern. The reason for this question was because I heard that you can actually gain more speed using HashMap pattern than using ViewHolder pattern. I know this is vague description, but that's why I came here to ask.
Sharing a code would not help at all, because it's just a regular adapter using ViewHolder pattern, just like the one in the links you shared. There is no any experimenting or something that made the adapter slow.
Saying it again, the main reason to ask this was to find our if there is a pattern faster than the ViewHolder
The way to reduce the amount of calls to findViewById is to "recycle" Views with ViewHolder. The idea behind is to keep inflated views and reuse them when needed - and since they already have ViewHolders you don't need to make the findViewById calls.
As #nhaarman described in the last part of his answer the way to do it for listviews is in the getView to reuse the convertView if it's not null and using the referenced view in the ViewHolder to update them.
An approach how to create ViewHolder and store them in the View by setTag is described in the first link #nhaarman provided.
Then in the getView function if convertView isn't null call the getTag inorder to get the ViewHolder back.
In another note, you should look into RecyclerView which is a listview that enforces the ViewHolder pattern.
You should be using the ViewHolder paradigm which is most easily implemented with RecyclerView when Adapters are involved. Online sample code available here and GitHub sample cloneable via AndroidStudio directly or via Github.
The HashMap solution will work, but why incur a HashMap lookup when you can simply hold a reference to the object? Also, RecyclerView natively handles different View TYPEs so you don't have to roll your own solution when you have section header rows which look differently from data rows.
===============================================
A more detailed explanation of RecyclerView follows based on snippets from the sample I linked to.
Have your CustomAdapter extend RecyclerView.
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
Create a Custom ViewHolder class. It should almost always be a static class if embedded within your CustomAdapter.
public static class ViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;
public ViewHolder(View v) {
super(v);
// Define click listener for the ViewHolder's View.
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Element " + getPosition() + " clicked.");
}
});
textView = (TextView) v.findViewById(R.id.textView);
Notice that the ViewHolder finds the views in the constructor since a ViewHolder instance is tied to a View instance. The ViewHolder should have references to any UI elements that you plan to update in an onBind callback. The ViewHolder object is then associated with the View by RecyclerView when you include the following code in your CustomAdapter.
// Create new views (invoked by the layout manager)
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// Create a new view.
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.text_row_item, viewGroup, false);
return new ViewHolder(v);
}
Notice how the onCreateViewHolder callback is given a viewType parameter. The current code always returns the same type of custom ViewHolder, but it could be providing different ViewHolder classes based on the type (this is how you support rows that support different views). The following code is then how you update your UI when the view is bound to a dataset.
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
Log.d(TAG, "Element " + position + " set.");
// Get element from your dataset at this position and replace the contents of the view
// with that element
viewHolder.getTextView().setText(mDataSet[position]);
}
With these pieces in place RecyclerView will create and reuse your Views AND ViewHolders by corresponding type reducing view hierarchy look ups and potentially making your app much less "janky".
Documents say:
When the content for your layout is dynamic or not pre-determined, you
can use a layout that subclasses AdapterView to populate the layout
with views at runtime. A subclass of the AdapterView class uses an
Adapter to bind data to its layout.
But most of tutorials are in about ListView,GridView,Spinner and Gallery.
I'm looking to extend a subclass directly from AdapterView. I have to create a custom view that it's content is dependent to an adapter.
How can I do this, and what methods must be overridden?
First, you should be absolutely sure that AdapterView is what you want, because not all "dynamic or not pre-determined" views can be implement via AdapterView. Sometimes you'd better create your view extending ViewGroup.
if you want to use AdapterView, take a look at this really nice example. There are a lot of custom views with adapter on GitHub. Check out this one (extends ViewGroup).
This may not be a total answer to your question but i am showing you most probably a starting point or pointer which can guide you:
Sony Developer Tutorials - 3D ListView
ListView extends AbsListView which in turn extends AdapterView<ListAdapter>. So if you absolutely must implement such a custom view from scratch, you could have a look at the source code of those classes:
ListView
AbsListView
AdapterView
But beware, that's quite a task. Perhaps it may be sufficient to use one of the existing classes and tweak the look.
Deriving from AdapterView can work, but it may not be as beneficial as you hope. Some of the infrastructure provided by AdapterView is package-private, meaning we don't have access to it.
For example, AdapterView manages the selected item index for AbsListView and ListView. However, because methods like setNextSelectedPositionInt(int position) (which is the only path to setting mNextSelectedPosition) are package-private, we can't get to them. AbsListView and ListView can get to them because they're in the same package, but we can't.
(If you dig into the AdapterView source you'll find that setNextSelectedPositionInt() is called from handleDataChanged(). Unfortunately handleDataChanged() is also package-private and is _not_called from anywhere else within AdapterView that could be leveraged to enable setting position.)
That means that if you need to manage selected position, you'll need to recreate that infrastructure in your derived class (or you'll need to derive from ListView or AbsListView...though I suspect you'll run into similar problems deriving from AbsListView). It also means that any AdapterView functionality that revolves around item selection likely won't be fully operational.
You could create something like this :
public class SampleAdapter extends BaseAdapter {
public SampleAdapter() {
// Some constructor
}
public int getCount() {
return count; // Could also be a constant. This indicates the # of times the getView gets invoked.
}
public Object getItem(int position) {
return position; // Returns the position of the current item in the iteration
}
public long getItemId(int position) {
return GridView.INVALID_ROW_ID;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.some_layout, null);
view.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
view.setBackgroungColor(Color.RED);
return view;
}
}
And this could be invoked like :
GridView sampleView = (GridView) linearLayout.findViewById(R.id.sample_layout);
sampleView.setAdapter(new SampleAdapter());
I am new to Android development and reading through some example code. I have copied one method from the sample code in an Adapter class (derived from ArrayAdapter), the derived class has a checkbox in addition to the text view:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View listItem = super.getView(position, convertView, parent);
CheckedTextView checkMark = null;
ViewHolder holder = (ViewHolder) listItem.getTag();
if (holder != null) {
checkMark = holder.checkMark;
} else {
checkMark = (CheckedTextView) listItem.findViewById(android.R.id.text1);
holder = new ViewHolder(checkMark);
listItem.setTag(holder);
}
checkMark.setChecked(isInCollection(position));
return listItem;
}
private class ViewHolder {
protected final CheckedTextView checkMark;
public ViewHolder(CheckedTextView checkMark) {
this.checkMark = checkMark;
}
}
The sample code is to optimize the getView by caching the View within a ViewHolder object.
Where I am confused is I thought the convertView, if not null, would be re-purposed and then the View data is populated into it and returned.
If this is the case, then how could the setTag / getTag methods called in the code be relied upon? It would seem that the same object would have to be retrieved in order for it to work?
perhaps view returned from getTag on a subsequent call is for a different list item, and returns the wrong view
Adapters use a RecycleBin. This class allows the ListView to only create as many row layouts as will fit on the screen, plus one or two for scrolling and pre-loading. So if you have a ListView with 1000 rows and a screen that only displays 7 rows, odds are the ListViiew will only have 8 unique Views.
Now to your question using my example above: only eight row layouts and 8 subsequent ViewHolders are ever created. When the users scrolls no new row layouts are ever created; only the content of the row layout changes. So getTag() will always have a valid ViewHolder that references the appropriate View(s).
(Does that help?)
You're on the right track, here's some information that may help make more sense of how ListViews work:
A simple implementation of the getView() method has two goals. The first is inflating the View to be shown on the list. The second is populating the View with the data that needs to be shown.
As you stated, ListViews re-purpose the Views that compose the list. This is sometimes referred to as view recycling. The reason for this is scalability. Consider a ListView that contains the data of 1000 items. Views can take up a lot of space, and it would not be feasible to inflate 1000 Views and keep them all in memory as this could lead to performance hits or the dreaded OutOfMemoryException. In order to keep ListViews lightweight, Android uses the getView() method to marry Views with the underlying data. When the user scrolls up and down the list, any Views that move off the screen are placed in a pool of views to be reused. The convertView parameter of getView() comes from this list. Initially, this pool is empty, so null Views are passed to getView(). Thus, the first part of getView should be checking to see if convertView has been previously inflated. Additionally, you'll want to configure the attributes of convertView that will be common to all list items. That code will look something like this:
if(convertView == null)
{
convertView = new TextView(context);
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
The second part of an implementation of getView() looks at your underlying data source for the list and configures this specific instance of the View. For example, in our test list, we may have an Array of Strings to set the text of the view, and want to set the tag as the current position in the Data of this View. We know which item in the list we're working with based on the position parmeter. This configuration comes next.
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
This allows us to minimize the amount of time we spend inflating/creating new views, a costly operation, while still being able to quickly configuring each view for display. Putting it all together, your method will look like this:
#Override
public View getView(int position, View convertView, ViewGroup)
{
if(convertView == null)
{
convertView = new TextView(context);
//For more complex views, you may want to inflate this view from a layout file using a LayoutInflator, but I'm going to keep this example simple.
//And now, configure your View, for example...
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
//Configure the View for the item at 'position'
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
//Finally, we'll return the view to be added to the list.
return convertView;
}
As you can see, a ViewHolder isn't needed because the OS handles it for you! The Views themselves should be considered temporary objects and any information they need to hold onto should be managed with your underlying data.
One further caveat, the OS does nothing to the Views that get placed in the pool, they're as-is, including any data they've been populated with or changes made to them. A well-implemented getView() method will ensure that the underlying data keeps track of any changes in the state of views. For example, if you change text color of your TextView to red onClick, when that view is recycled the text color will remain red. Text color, in this case, should be linked to some underlying data and set outside of the if(convertView == null) conditional each time getView() is called. (Basically, static setup common for all convertViews happens inside the conditional, dynamic setup based on the current list item and user input happens after) Hope this helps!
Edited - Made the example simpler and cleaned up the code, thanks Sam!
It would be more convenient for me to register a unique OnClickListener for each row in the ListView, but I want to be sure that this is an acceptable practice. My current design is a rather convoluted way to separate the concerns of the OnClickListener from each row type.
The reason for this is that I have multiple classes of rows in the ListView. Each class has a completely different responsibility and behavior. For example, consider a ListView that can contain both sub-categories and book titles. If a book title is clicked, a new activity should start that shows the cover image. If a sub-category is clicked, a new list of books and categories is displayed.
I'd like the row itself to maintain knowledge about its own identity and responsibility rather than having to leak the knowledge about each row to be maintained by the implementer of onItemClickListener.
I'd also like to know what the performance implications are of doing this vs. implementing my own logic to figure out how to handle the click.
Are there drawbacks to implementing onClickListener for each ListView ArrayAdapter row instead of onItemClickListener? I'm looking for concrete data and specific drawbacks rather than vague recommendations.
Should I expect memory use, initialization time, or steady-state speed (like scrolling through the list) to be significantly affected?
You don't explain why you would need separate click listeners for each row, but I would recommend against it. Take a look at using View.setTag(Object)/View.getTag() to pass row-specific custom data by which you can customize the response of a (shared) click listener.
EDIT
I see from your example why you want different OnClickListeners attached to your rows. I was under the impression that you wanted a separate OnClickListener instance for each row. (That was my main reason for recommending against this.) If you have two types of rows (categories and titles) and hundreds of rows, you need only two kinds of responses, not hundreds of them. I also understand the point about separating concerns.
Nevertheless, I think overriding ListActivity.onListItemClick() (or calling ListView.setOnItemClickListener() if you aren't using a ListActivity) would be cleaner and less likely to interfere with the operation of the list view. You can use a delegate pattern for this, as follows.
Define an abstract class or interface:
public interface MyClickHandler {
public void onItemClick(ListView l, View v, int position, long id);
}
Then create one instance of an object that implements this interface for each type of row data (not one instance for each row). In your adapter, use setTag(Object) to initialize the tag for each row to the appropriate MyClickHander instance. In your ListActivity.onListItemClick() override, use this logic:
protected void onListItemClick(ListView l, View v, int position, long id) {
Object tag = v.getTag();
if (tag instanceof MyClickHandler) {
((MyClickHandler) tag).onItemClick(l, v, position, id);
} else {
// default processing (if any)
}
}