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());
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".
I wonder what is convert view ? I understand that it is
The old view to reuse, if possible. Note: You should check that this
view is non-null and of an appropriate type before using. If it is not
possible to convert this view to display the correct data, this method
can create a new view. Heterogeneous lists can specify their number of
view types, so that this View is always of the right type (see
getViewTypeCount() and getItemViewType(int)).
What is this under the hood ?
Is it just simple view inflated earlier ? Or something other ?
Why we cannot use it like this ?
if (convertView!=null) {
return convertView;
}
else {
return new ......
}
Why we have to use setTag method to set reference to the our inflated view ?
convertView.setTag(holder);
We are setting one view another view reference as the TAG , why not just to pass it directly as convertView ?
Please help with this question, I can't write any code because cannot understand this.
Under the hood speaking you would see that it inflates the views just a couple of times (enough to fill up the screen) as this is really slow. It is as you said reusing the views it had inflated earlier. When you scroll, it just tells you that you need to fill the earlier inflated views with the data appropriate for the current position.
You actually can always return a new view you inflate every time, but this will have a negative impact on your performance and there by scrolling smoothness.
With the setTag you are telling it to remember the object which holds information about the view itself (called ViewHolder).
What is this under the hood ? Is it just simple view inflated earlier
? Or something other ?
it is both. In particular is an array of views of the type you inflated. The size of this array is equal to the number of views enough to fill completely your screen. This way you allocate only a constant number of views.
Why we cannot use it like this ?
if (convertView!=null) {
return convertView;
}
else {
return new ......
}
you still need to fill up your view with the data of your dataset. In the snippet you posted above you aren't doing it. You could do something like
if (convertView!=null) {
// convertView.findVIewById(...)
// set data to view
return convertView;
}
else {
convertView = new ......
// convertView.findVIewById(...)
// set data to view
return converView;
}
Why we have to use setTag method to set reference to the our inflated
view ?
it's called ViewHolder pattern, and you are not setting the inflated view, but a small object that contains the inflated's view component. This way you can avoid calling findViewById every time getView is called and you want to fill up the view with content of your dataset (at position)
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)
}
}
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)