I have some extended cursor adapter, in witch I call super with the context and the resource layout for the item in the list, something like this.
call to super in my adapter:
super(activity, viewResource, c, false);
creation of my adapter:
new MyCursorAdapter(this, null, R.layout.my_list_item, null);
What I want to achieve is something like my stupid mock up made in paint.
Put into words I want to have different kinds of layout for the items, for example I want all even items to have layout1 and all odd to have the layout2. So far I am able to give only one layout in this case R.layout.my_list_item. Is it possible to dynamically change the layout ?
Is it possible to construct the adapter to have items with different layout ? My goal is to dynamically chose the layout of the item. I do not want to have just one layout for all of the items I want to have foe example two...
Thanks
Yes, you're going to have to do two things though. First, override the getItemViewType() method in your adapter so that you can be sure your bindView() only get's views that appropriate for a particular position in the list, like so:
public int getItemViewType(int position){
if(correspondsToViewType1(position)){
return VIEW_TYPE_1;
}
else(correspondsToViewType2(position)){
return VIEW_TYPE_2;
}
//and so on and so forth.
}
Once you do that, just have a simple test in your bindView() that checks to see what type of view it should have recieved and setup things accordingly like so:
public void bindView(View view, Context context, Cursor cursor){
if(correspondsToViewType1(cursor)){
//Now we know view is of a particular type and we can do the
//setup for it
}
else if(correspondsToViewType2(cursor){
//Now we know view is of a different type and we can do the
//setup for it
}
}
Note that you're going to have to have to different methods for correpondsToViewType, one that takes a cursor and one that takes an int (for a position). The implementation for these will vary depending on what you want to do.
Note that doing things this way will allow you to reuse potentially recycled views. If you don't do this, your app is going to take a huge performance hit. Scrolling will be super choppy.
I'm guessing your extending SimpleCursorAdapter from the name of your custom adapter. You will want to override the function getView in your adapter and depending on the object in the list inflate a different layout and return that view.
EX:
#Override
public View getView (int position, View convertView, ViewGroup parent)
{
Object myObject = myList.get(position);
if(convertView == null)
{
if( something to determine layout )
convertView = inflater.inflate(Layout File);
else
convertView = inflater.inflate(Some Other Layout File);
}
//Set up the view here, such as setting textview text and such
return convertView;
}
This is just an example and is somewhat sudo code so it will need some adjustments for your specific situation.
Just override the newView Method:
public class MyCursorAdapter extends CursorAdapter {
private final LayoutInflater inflater;
private ContentType type;
public MyCursorAdapter (Context context, Cursor c) {
super(context, c);
inflater = LayoutInflater.from(context);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
if( cursor.getString(cursor.getColumnIndex("type")).equals("type1") ) {
// get elements for type1
} else {
// get elements for type1
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
if( cursor.getString(cursor.getColumnIndex("type")).equals("type1") ) {
final View view = inflater.inflate(R.layout.item_type1, parent, false);
} else {
final View view = inflater.inflate(R.layout.item_type2, parent, false);
}
return view;
}
Related
I'm trying to create a UI similar to Google Keep. I know how to create a staggered View using a Recycler View. If i click a specific Card. Then it has to open a activity.
I can achieve this using onclick method.
This same scenario happens in atleast 5 different Activities in my App.
My question is that
Can I use this single Adapter in all those 5 places ?
If yes, where should i place the onclick actions ?
If no, How can I Create a staggered layout like Keep?
Thanks in Advance!
(See application for RecyclerView below in edits)
Like I mentioned in my comment, it's certainly fine to have separate adapters for all your Activities which use different data and views. As your app data and layouts get more complex, so does your code...that's just the way it is.
But if some of your Activities used similar data in their ListViews -- maybe, for example, two TextViews and an ImageButton -- you could save some effort by defining a single adapter that can be used for multiple Activities. You would then instantiate separate objects for each Activity, similar to the way you would create several ArrayAdapter<String> objects to populate multiple ListViews.
The BaseAdapter is a great class to extend when writing a custom adapter. It's flexible and allows you complete control over the data that's getting shown in your ListView. Here's a minimal example:
public class CustomBaseAdapter extends BaseAdapter {
private Context context;
private ArrayList<String> listData;
public CustomBaseAdapter(Context context, ArrayList<String> listData) {
this.context = context;
this.listData = listData;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.your_list_item_layout, parent, false);
//populate the view with your data -- some examples...
TextView textData = (TextView) convertView.findViewById(R.id.yourTextView);
textData.setText(listData.get(position));
ImageButton button = (ImageButton) convertView.findViewById(R.id.yourImageButton);
button.setOnClickListener(new View.OnClickListener() {
//...
//...
});
}
return convertView;
}
#Override
public Object getItem(int position) {
return 0;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getCount() {
return listData.size();
}
}
So the key part of this code is obviously the getView() method, which is called every time the ListView needs some data to display. For efficiency, views are stored in something called a convertView so they may be re-used and not have to be inflated every time a view appears on the screen.
So what we do in getView() is first find out if the convertView exists. If it does, we just pass that back to the calling ListView because everything should already be instantiated and ready to go. If the convertView is null, it means the data hasn't been instantiated (or needs to be re-instantiated for whatever reason), and so we inflate a brand new view and populate it with our data.
So in the case of this example adapter above, if several of your Activities all displayed a single list of Strings, you could reuse this adapter for each one, passing in a different ArrayList<String> through the constructor each time you created a new object. But obviously you could pass in more than just Strings if you had more data to show. The level of complexity is up to you. And if the difference among your Activities was too great, I would just create custom versions of this class for all of them and just instantiate them and populate them however you'd like. It will keep all your data very organized and encapsulated.
Hope this helps! Feel free to ask questions for more clarification if you need it.
EDIT IN RESPONSE TO COMMENTS
Since you are using a RecyclerView instead of just plain ListViews (which I, for some reason, totally forgot) you could still do something very similar using a RecyclerView.Adapter<YourViewHolder> instead. The difference would be that instead of inflating the views in a getView() method, they are inflated inside your custom ViewHolder, which I assume you already have. The code might look something like this:
public class CustomRecyclerViewAdapter extends RecyclerView.Adapter<StringViewHolder> {
private final List<String> items;
public CustomRecyclerViewAdapter(ArrayList<String> items) {
this.items = items;
}
#Override
public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//So instead of inflating the views here or in a getView() like in
//in the BaseAdapter, you would instead inflate them in your custom
//ViewHolder.
return new StringViewHolder(parent);
}
#Override
public void onBindViewHolder(StringViewHolder holder, int position) {
holder.setModel(items.get(position));
}
#Override
public long getItemId(int position) {
return items.get(position).hashCode();
}
#Override
public int getItemCount() {
return items.size();
}
}
I am so confused with the list list view. I want to add an specific colur to the last added row to the list, how can I do that? I have googled all internet and stackoverflow for 2 days but I can't figure out how??
I have tried
lv.getChildAt(lv.getLastVisiblePosition()). setBackgroundColor(Color.RED);
but no success so far. I get the randowm row coloured. But I only want to colour the most recent added item.
Could someone please advice me on that.
Any comment is highly appreciated.
Cheers
Adapters have a view recycler for efficiency reasons, so this is where the "randomness" comes from...
Anyway simply create a custom adapter that tracks the index of the last row added and in getView() check whether the current index matches this last index:
If so change the background color
If not restore the original background color.
Watch Android's Romain Guy explain the view recycler at Google Talks.
Here is an example extending ArrayAdapter:
public class MyArrayAdapter<T> extends ArrayAdapter<T> {
private int lastAdded;
public MyArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
super(context, textViewResourceId, objects);
lastAdded = objects.size() - 1;
}
#Override
public void add(T object) {
lastAdded = getCount();
super.add(object);
};
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if(position == lastAdded) // Red
view.setBackgroundColor(0xffff0000);
else // Transparent
view.setBackgroundColor(0x00000000);
return view;
}
#Override
public void insert(T object, int index) {
lastAdded = index;
super.insert(object, index);
};
}
Note: This is not comprehensive. There are other ways to add data which you may or may not want to override depending the way you use the adapter.
with that call lv.getChildAt(lv.getLastVisiblePosition()) you're getting the last child that is visible AND currently on the screen.
If you want to change anything on the last item of your list you must inside the getView of your adapter to check if(position == getCount()-1){ } and do stuff to it there.
You can use a customadapter for this and overriding the getView() can help you to achieve your task.
public View getView (int position, View convertView, ViewGroup parent){
// some task
if(position == last) // once even try position == getCount()
{
set background color to the view
}
return convertView;
}
here last is the last item that you will be inflating to the listview(may be an arraylist or an array or someother one that suits your requirement)
I have a simple cursor adapter set on a list in my application as follows:
private static final String fields[] = {"GenreLabel", "Colour", BaseColumns._ID};
datasource = new SimpleCursorAdapter(this, R.layout.row, data, fields, new int[]{R.id.genreBox, R.id.colourBox});
R.layout.row consists of two TextViews (genreBox and colourBox). Rather than setting the content of the TextView to the value of "Colour" , I would like to set its background colour to that value.
What would I need to do to achieve this?
Check out SimpleCursorAdapter.ViewBinder.
setViewValue is basically your chance to do whatever you wish with the data in your Cursor, including setting the background color of your views.
For example, something like:
SimpleCursorAdapter.ViewBinder binder = new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
String name = cursor.getColumnName(columnIndex);
if ("Colour".equals(name)) {
int color = cursor.getInt(columnIndex);
view.setBackgroundColor(color);
return true;
}
return false;
}
}
datasource.setViewBinder(binder);
Update - if you're using a custom adapter (extending CursorAdaptor) then the code doesn't change a whole lot. You'd be overriding getView and bindView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView != null) {
return convertView;
}
/* context is the outer activity or a context saved in the constructor */
return LayoutInflater.from(context).inflate(R.id.my_row);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
int color = cursor.getInt(cursor.getColumnIndex("Colour"));
view.setBackgroundColor(color);
String label = cursor.getString(cursor.getColumnIndex("GenreLabel"));
TextView text = (TextView) findViewById(R.id.genre_label);
text.setText(label);
}
You're doing a bit more manually, but it's more or less the same idea. Note that in all of these examples you might save on performance by caching the column indices instead of looking them up via strings.
What you're looking for requires a custom cursor adapter. You can subclass SimpleCursorAdapter. This basically give access to the view as its created (although you'll be creating it yourself).
See this blog post on custom CursorAdapters for a complete example. Particularly, I think you'll need to override bindView.
I'm using a listview with an adapter extending CursorAdapter.
The listitems contains a few TextViews which can contain arbitrary lengths of text.
The problem is now as the views (in the listview) are recycled, items might get alot higher than needed since a previous item in the view needed bigger space.
I guess that the solution is to somehow to not allow recycling, or just force set the size of the view upon it being bound. I've been trying a number of different solutions but I haven't found a way. Could someone please help me? ;)
#Override
public View newView(Context context, Cursor c, ViewGroup parent)
{
settings = ctx.getSharedPreferences("myprefs", 0);
View v = inflater.inflate(R.layout.convoview_list_item, parent,false);
ctx2 = context;
parentGroup = parent;
return v;
}
#Override
public void bindView(View v, Context context, Cursor c)
{
//Adding text etc to my views from the cursor here.
}
If you know that the list will always be very small, i.e. 10 entries, here is what I do (override these 2 methods). So, when view is recycled, the same type is returned for each position. If you know that your list could be large but there could be only 3-4 types of list items, then instead of returning position, asssign type 0, 1, 2, etc.
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getViewTypeCount() {
return MAX_ITEMS;
}
The problem was that in my bindView implementation made some TextViews "invisible" instead of "gone" and because of this they took up space even though you couldn't see them.
I'm having problems with some BaseAdapter code that I adapted from a book. I've been using variations of this code all over the place in my application, but only just realized when scrolling a long list the items in the ListView become jumbled and not all of the elements are displayed.
It's very hard to describe the exact behavior, but it's easy to see if you take a sorted list of 50 items and start scrolling up and down.
class ContactAdapter extends BaseAdapter {
ArrayList<Contact> mContacts;
public ContactAdapter(ArrayList<Contact> contacts) {
mContacts = contacts;
}
#Override
public int getCount() {
return mContacts.size();
}
#Override
public Object getItem(int position) {
return mContacts.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if(convertView == null){
LayoutInflater li = getLayoutInflater();
view = li.inflate(R.layout.groups_item, null);
TextView label = (TextView)view.findViewById(R.id.groups_item_title);
label.setText(mContacts.get(position).getName());
label = (TextView)view.findViewById(R.id.groups_item_subtitle);
label.setText(mContacts.get(position).getNumber());
}
else
{
view = convertView;
}
return view;
}
}
You are only putting data in the TextView widgets when they are first created. You need to move these four lines:
TextView label = (TextView)view.findViewById(R.id.groups_item_title);
label.setText(mContacts.get(position).getName());
label = (TextView)view.findViewById(R.id.groups_item_subtitle);
label.setText(mContacts.get(position).getNumber());
to be after the if/else block and before the method return, so you update the TextView widgets whether you are recycling the row or creating a fresh one.
To further clarify the answer of CommonsWare, here is some more info:
The li.inflate operation (needed here for parsing of the layout of a row from XML and creating the appropriate View object) is wrapped by an if (convertView == null) statement for efficiency, so the inflation of the same object will not happen again and again every time it pops into view.
HOWEVER, the other parts of the getView method are used to set other parameters and therefore should NOT be included within the if (convertView == null){ }... else{ } statement.
In many common implementation of this method, some textView label, ImageView or ImageButton elements need to be populated by values from the list[position], using findViewById and after that .setText or .setImageBitmap operations.
These operations must come after both creating a view from scratch by inflation and getting an existing view if not null (e.g. on a refresh).
Another good example where this solution is applied for a ListView ArrayAdapter appears in https://stackoverflow.com/a/3874639/978329