Add a different element to ArrayAdapter/ListView - android

I have a ListView that's being populated by an ArrayAdapter:
someListView.setAdapter(adapter);
Each element in the adapter is inflated using the same layout.xml. Now I want to add an element of a different type (inflated using a different layout file) to the beginning of the ListView.
What I want to achieve is, to have a special element on top of all other elements in the list view, but also scrolls with the list (exits the screen from top if the user scrolls down).
I've tried to add the new element to the array but it's a different type so that won't work.
I've tried to insert a dummy element to the array at position 0, and modify the adapter's getView() so that if (position == 0) return myUniqueView, but that screwed up the entire list view somehow: items not showing, stuff jumping all over the place, huge gaps between elements, etc.
I start to think the best practice of achieving what I want, is not through editing the array adapter. But I don't know how to do it properly.

You don't need anything special to do what you ask. Android already provides that behavior built in to every ListView. Just call:
mListView.addHeaderView(viewToAdd);
That's it.
ListView Headers API
Tutorial

Do't know exactly but it might usefull
https://github.com/chrisjenx/ParallaxScrollView

In your adapter add a check on the position
private static final int LAYOUT_CONFIG_HEADER = 0;
private static final int LAYOUT_CONFIG_ITEMS = 1;
int layoutType;
#Override
public int getItemViewType(int position) {
if (position== 0){
layoutType = LAYOUT_CONFIG_HEADER;
} else {
layoutType = LAYOUT_CONFIG_ITEMS;
}
return layoutType;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
LayoutInflater inflater = null;
int layoutType = getItemViewType(position);
if (row == null) {
if (layoutType == LAYOUT_CONFIG_HEADER) {
//inflate layout header
}
} else {
//inflate layout of others rows
}
}

Related

How to optimise list view with multiple layouts

I am trying to determine the best way to have a single listview contains different rows styles. I know how to create a custom row + custom array adapter to support a custom row for the entire list view. But how can one listview support many different row styles?
Since you know how many types of layout you would have - it's possible to use those methods.
getViewTypeCount() - this methods returns information how many types of rows do you have in your list
getItemViewType(int position) - returns information which layout type you should use based on position
Then you inflate layout only if it's null and determine type using getItemViewType.
Look at this tutorial for further information.
To achieve some optimizations in structure that you've described in comment I would suggest:
Storing views in object called ViewHolder. It would increase speed because you won't have to call findViewById() every time in getView method. See List14 in API demos.
Create one generic layout that will conform all combinations of properties and hide some elements if current position doesn't have it.
I hope that will help you. If you could provide some XML stub with your data structure and information how exactly you want to map it into row, I would be able to give you more precise advise. By pixel.
You have to write you custom adapter which extends BasaAdapter. NOTE: viewTypeCount(). Then you can use condition to check and assign different views
private class MyAdapter extends BaseAdapter{
public static final int ITEM_TYPE_ONE = 1;
public static final int ITEM_TYPE_TWO = 2;
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
return (position % 2 == 0) ? ITEM_TYPE_ONE : ITEM_TYPE_TWO;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int itemTypeId = getItemViewType(position);
if (convertView == null) {
if (itemTypeId == ITEM_TYPE_ONE) {
convertView = inflater.inflate(R.layout.layout_one, parent, false);
} else {
convertView = inflater.inflate(R.layout.layout_two, parent, false);
}
}
}
}
Just create an attribute and myListView.addView('your inflate view')
LayoutInflater inflater = LayoutInflater.from(this);
RelativeView myCustomView =(RelativeView)inflater.inflate(R.layout.task_since_user);
myListView.addView(myCustomView);

ListView and it's "weird" behavior. How can i solve this situation?

As you should know, ListView recycles the view. But i want to work with elements that can be clicked and expanded. Like i already did:
But it was completely messed up, even using:
View checklayout = convertView;
if(checklayout == null){
checklayout = inflater.inflate(R.layout.home_cell, null);
}
When some opened expandable views goes out of the screen, the recycled one, which shouldn't be expandable, receives the vanished's layout. Only view that has "1 AVALIAÇÃO LANÇADA" should open, and show it's content. I add this content by using if(qtdAvaliacoes > 0) that is a property of my Object that comes from ArrayList<>.
I "solved" this disabling the recycler, with:
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
Once my listView will only receives 5~10 rows. But i know that isn't a good practice. While i'm writting this question, i found a solution, calling my object before inflate any view, then checking the property:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View checklayout = convertView;
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final Disciplina disciplina = lista.get(position);
if(checklayout == null || disciplina.getQtdAvaliacoes() == 0){
checklayout = inflater.inflate(R.layout.home_cell, null);
}
final View layout = checklayout;
But I don't think this is the best way to do this. I read something about Tags, but was little confused. I think if i could bind these onClick methods to the row position it would be better.
Any ideas ? Or is my solution good at you, developer's, point of view.
Thanks.
The easiest way is to not do subinflates within a list item. Do it via view visibilities instead, making the inflated part GONE if you don't want it to display yet. You'll just have to explicitly set the visibility of that view in every call to getView

How to add some space in the middle of a listview?

I'm trying to add some extra space between the 4th and 5th items in the listview. What are my options?
I tried doing that in adapter's getView(), as well as manually getting access to the fourth element and adding padding to it.
Is there a better way to do this?
Another way to do this would be to use a different layout for the the 4th item (that has additional padding). It's similar to your solution but maybe a bit "cleaner". I'm assuming that you're extending ArrayAdapter.
In your adapter override the getViewTypeCount() method:
#Override public int getViewTypeCount() {
return 2;
}
This way you're telling your adapter that you will use two different layouts for your items. Next, you have to specify which items will be of which type by overriding another method:
#Override public int getItemViewType(int position) {
if(position == 3) {
return 0;
} else {
return 1;
}
}
This will tell your adapter to use a different view (only) for the 4th element in the list, and it will not be reused for other elements. Now for the last part, override onCreateView():
#Override public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = LayoutInflater.from(context);
if(position == 3) {
convertView = inflater.inflate(R.layout.your_layout_with_padding, parent, false);
} else {
convertView = inflater.inflate(R.layout.your_regular_item_padding, parent, false);
}
//TODO this is the place to initialize your view holder
} else {
//TODO this is the place to restore your view holder
}
//TODO setup your view here
return convertView;
}
For the item with position == 3 (4th item in the list) convertView argument of the getView() method will be null, because that is the first (and only) item of the type 1 in the list. Therefore you can inflate a different layout that includes a padding for that item.
I thought to some ways but if i have to be honest the only way to do this well is to change the layout in the adapter when the position is equal to 4. I meant that you can do an xml file with a RelativeLayout of the height that you want as space between the 4th and 5th element and set the visibility to gone and put him above all your adapter's elements. When the position is equal to 4 in your getView you set the visibility of that item to visible with nameOfYourRelativeLayout.setVisibility(View.VISIBLE)
So you can add this blank space only between 4th and 5th element. Mine is just a suggestion but i think it can work well.
Layout all of your items in the listview to include your data as well as a header view, maybe a textview or even a Viewgroup like another layout. Keep the header invisible until some logic in your code triggers (i.e. pos ==4) and make the header visible

ListView: how to access Item's elements programmatically from outside?

I have the following situation.
I have a ListView, each item of the ListView is comprised of different widgets (TextViews, ImageViews, etc...) inflated form a Layout in the getView() method of the custom adapter.
Now, I would like to achieve the following:
when a certain event is triggered I want to change the background of a View which is inside the item.
Please how do I do it?
This is the the Item Layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/cardlayout"
android:layout_width="320dp"
android:layout_height="130dp"
android:background="#android:color/transparent"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingRight="5dp"
android:paddingTop="5dp" >
<FrameLayout
android:layout_width="320dp"
android:layout_height="117dp" >
<View
android:id="#+id/card"
android:layout_width="320dp"
android:layout_height="117dp"
android:background="#drawable/card_selector" />
</FrameLayout>
</LinearLayout>
I need to change the background of card
I have tried doing this:
View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);
But no success :-((
When your event is triggered you should just call a notifyDataSetChanged on your adapter so that it will call again getView for all your visible elements.
Your getView method should take into account that some elements may have different background colors (and not forget to set it to normal color if the element doesn't need the changed background, else with recycling you would have many elements with changed background when you scroll)
edit :
I would try something like this :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.card, parent, false);
}
//This part should also be optimised with a ViewHolder
//because findViewById is a costly operation, but that's not the point of this example
CardView cardView =(CardView)convertView .findViewById(R.id.card);
//I suppose your card should be determined by your adapter, not a new one each time
Card card = getItem(position);
//here you should check sthg like the position presence in a map or a special state of your card object
if(mapCardWithSpecialBackground.contains(position))
{
card.setBackgroundResource(specialBackground);
}
else
{
card.setBackgroundResource(normalBackground);
}
cardView.setCard(card);
return convertView;
}
And on the special event i would add the position of the item into the map and call notifyDataSetChanged.
Use the onitemclicklistener which has method onclicksomething..that takes four or five parameters. (View parent, View view, int position, int id). Use the view parameter to customize your background.
Update
Here's some of my code, If you don't understand I recommend to read about recycling and ViewHolder pattern.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
{
ViewHolder viewHolder;
// If convertView isn't a recycled view, create a new.
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.row_gallery_frame, parent, false);
viewHolder = new ViewHolder();
// Here you must be able to find your Widget inside convertView and set a listener to it I guess?
viewHolder.nameHolder = (TextView) convertView.findViewById(R.id.nameTv);
// Set a reference to newly inflated view
convertView.setTag(viewHolder);
}
// If it is, then get the ViewHolder by tag
else{
viewHolder = (ViewHolder)convertView.getTag();
}
// Set the data
GalleryFrame galleryFrame = galleryFrameArrayList.get(position);
viewHolder.nameHolder.setText(galleryFrame.getName());
return convertView;
}
}
// Viewholder pattern which holds all widgets used
public static class ViewHolder{
public TextView nameHolder;
}
I assume you have a model object that you use to "draw" the list item , and for example the background color is determined based on a boolean or something.
All you need to do, is change the value on which you base your decision which background color should that TextView have.
Your getView() method should have code like that
if (myModelObj.isBrown()) {
myTextView.setBackgroundResource(R.drawable.brown_bg);
else
myTextView.setBackgroundResource(R.drawable.not_brown_bg);
All you should do when ur event is triggered, is set the value of the brown boolean in your model
and call notifyDataSetChanged() on your adapter
EDIT
If for some reason you don't wanna call nofitfyDataSetChanged(), althought it won't move the scroll position of your list and with the right recyclying it won't cause bad performance
You can find the View object that represent the list item you want to edit-if it's visisble-, and simply change the background in it, without refreshing the list at all.
int wantedPosition = 10; // Whatever position you're looking for
int firstPosition = listView.getFirstVisiblePosition() - listView.getHeaderViewsCount();
int wantedChild = wantedPosition - firstPosition
if (wantedChild < 0 || wantedChild >= listView.getChildCount()) {
// Wanted item isn't displayed
return;
}
View wantedView = listView.getChildAt(wantedChild);
then use wantedView to edit your background
This answer can be found here
try this one:
View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);
card.invalidate();
v.invalidate();
those function force your views to redraw itself and they will render again.
look at invalidate()
What I normally do is this:
public static class EventDetailsRenderer {
private TextView title;
private TextView description;
private Event item;
public EventDetailsRenderer(View view) {
extractFromView(view);
}
private final void extractFromView(View view) {
title = (TextView) view.findViewById(R.id.EventTitle);
description = (TextView) view.findViewById(R.id.Description);
}
public final void render() {
render(item);
}
public final void render(Event item) {
this.item= item;
title.setText(item.getTitle());
description.setText(item.getDescription());
}
}
private class EventsAdapter
extends ArrayAdapter<Event> {
public EventsAdapter(Context context) {
super(context, R.layout.list_node__event_details, 0);
}
public void addAllItems(Event... services) {
for (int i = 0; i < services.length; i++) {
add(services[i]);
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Event event = getItem(position);
EventDetailsRenderer eventRenderer;
if (convertView != null && convertView.getTag() != null) {
eventRenderer = (EventDetailsRenderer) convertView.getTag();
} else {
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_node__event_details, null);
eventRenderer = new EventDetailsRenderer(convertView);
convertView.setTag(eventRenderer);
}
eventRenderer.render(event);
return convertView;
}
}
NOTE: that this example might not compile I pasted it from some code I have and deleted some lines to show an example but the logic it the same.
And then when you want to render it, just get the children from the list, iterate over them, check if the renderer contains the card you want to flip and call its render method... then you render a specific item in the list without effecting the rest of the items.
Let me know if this works...
Adam.
User EasyListViewAdapters library https://github.com/birajpatel/EasyListViewAdapters
Features
Easier than implementing your own Adapter (ie handling
BaseAdaper#getView).Very Easier to provide multi-row support.
Library takes care of recycling all views, that ensures performance
& helps your list view scroll smoothly.
Cleaner code. By keeping different RowViewSetter classes for
different row-types makes your code easy to manage & easy to reuse.
No data browsing, Library takes care of browsing data through
data-structure when View is being drawn or event occurs so that
Users does not have to look for their data to take actions.
Just by passing correct row-types library will Auto-map your
data-types to row-types to render views. Row views can be created by
using XML or Java (doesn't restrict to XML-Only Approach).
Load More callbacks can be registered to implement paginatation
support to your list.
Handling children viewclicks, you can also register for
Children(present inside your rows) view click events.
All these Views are registered with single OnClickListner so that
this mechanism is very memory efficient when click event occurs
users you gets clickedChildView, rowData,int eventId as callback
params.

How to manage Views in a Listview correctly

I am wondering how to manage the views inside a ListView.
I have a custom Adapter that is set on the ListView, this Adapter overrides the getView method
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
v = mInflater_.inflate(R.layout.news_newsentry, null);
}
final NewsItem newsItem = getItem(position);
if (newsItem != null) {
// Do stuff
}
return v;
}
But the thing is that when the user clicks on an item, I slightly change the view to make it bigger. It works well, but when the item view is recycled, it keeps the "big" height to display another item.
To prevent that, I changed the code to create a new View each time
Change:
View v = convertView;
if (v == null) {
v = mInflater_.inflate(R.layout.news_newsentry, null);
}
By
View v = mInflater_.inflate(R.layout.news_newsentry, null);
The problem now is that when the item disappears from the list and reappears (the list is scrolled), the view is completely new and the height is set to "small".
My question then: how to manage the items views to keeps their properties, without messing with the other views and the view recycling?
I think you can get the result you want by using the ListView built in support for more than one view type in a list.
In your adapter you would implement additional methods similar to
#Override
public int getItemViewType(int position) {
int type = 0;
if (position == mySelectedPosition) {
type = 1;
}
return type;
}
#Override
public int getViewTypeCount() {
return 2;
}
Then your getView method will be handed a view of the correct type for the position of the item. Ie, the selected item will always be given a "big" view to re-use.
Creating a new View every time is not recommended for performance and memory reasons.

Categories

Resources