I am trying to determine the best way to have a single ListView that contains different layouts for each row. I know how to create a custom row + custom array adapter to support a custom row for the entire list view, but how can I implement many different row styles in the ListView?
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.
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?
You already know the basics. You just need to get your custom adapter to return a different layout/view based on the row/cursor information being provided.
A ListView can support multiple row styles because it derives from AdapterView:
An AdapterView is a view whose children are determined by an Adapter.
If you look at the Adapter, you'll see methods that account for using row-specific views:
abstract int getViewTypeCount()
// Returns the number of types of Views that will be created ...
abstract int getItemViewType(int position)
// Get the type of View that will be created ...
abstract View getView(int position, View convertView, ViewGroup parent)
// Get a View that displays the data ...
The latter two methods provide the position so you can use that to determine the type of view you should use for that row.
Of course, you generally don't use AdapterView and Adapter directly, but rather use or derive from one of their subclasses. The subclasses of Adapter may add additional functionality that change how to get custom layouts for different rows. Since the view used for a given row is driven by the adapter, the trick is to get the adapter to return the desired view for a given row. How to do this differs depending on the specific adapter.
For example, to use ArrayAdapter,
override getView() to inflate, populate, and return the desired view for the given position. The getView() method includes an opportunity reuse views via the convertView parameter.
But to use derivatives of CursorAdapter,
override newView() to inflate, populate, and return the desired view for the current cursor state (i.e. the current "row") [you also need to override bindView so that widget can reuse views]
However, to use SimpleCursorAdapter,
define a SimpleCursorAdapter.ViewBinder with a setViewValue() method to inflate, populate, and return the desired view for a given row (current cursor state) and data "column". The method can define just the "special" views and defer to SimpleCursorAdapter's standard behavior for the "normal" bindings.
Look up the specific examples/tutorials for the kind of adapter you end up using.
Take a look in the code below.
First, we create custom layouts. In this case, four types.
even.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff500000"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="match_parent"
android:layout_gravity="center"
android:textSize="24sp"
android:layout_height="wrap_content" />
</LinearLayout>
odd.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff001f50"
android:gravity="right"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="28sp"
android:layout_height="wrap_content" />
</LinearLayout>
white.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ffffffff"
android:gravity="right"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/black"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="28sp"
android:layout_height="wrap_content" />
</LinearLayout>
black.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff000000"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="33sp"
android:layout_height="wrap_content" />
</LinearLayout>
Then, we create the listview item. In our case, with a string and a type.
public class ListViewItem {
private String text;
private int type;
public ListViewItem(String text, int type) {
this.text = text;
this.type = type;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
After that, we create a view holder. It's strongly recommended because Android OS keeps the layout reference to reuse your item when it disappears and appears back on the screen. If you don't use this approach, every single time that your item appears on the screen Android OS will create a new one and causing your app to leak memory.
public class ViewHolder {
TextView text;
public ViewHolder(TextView text) {
this.text = text;
}
public TextView getText() {
return text;
}
public void setText(TextView text) {
this.text = text;
}
}
Finally, we create our custom adapter overriding getViewTypeCount() and getItemViewType(int position).
public class CustomAdapter extends ArrayAdapter {
public static final int TYPE_ODD = 0;
public static final int TYPE_EVEN = 1;
public static final int TYPE_WHITE = 2;
public static final int TYPE_BLACK = 3;
private ListViewItem[] objects;
#Override
public int getViewTypeCount() {
return 4;
}
#Override
public int getItemViewType(int position) {
return objects[position].getType();
}
public CustomAdapter(Context context, int resource, ListViewItem[] objects) {
super(context, resource, objects);
this.objects = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
ListViewItem listViewItem = objects[position];
int listViewItemType = getItemViewType(position);
if (convertView == null) {
if (listViewItemType == TYPE_EVEN) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_even, null);
} else if (listViewItemType == TYPE_ODD) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_odd, null);
} else if (listViewItemType == TYPE_WHITE) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_white, null);
} else {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_black, null);
}
TextView textView = (TextView) convertView.findViewById(R.id.text);
viewHolder = new ViewHolder(textView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.getText().setText(listViewItem.getText());
return convertView;
}
}
And our activity is something like this:
private ListView listView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // here, you can create a single layout with a listview
listView = (ListView) findViewById(R.id.listview);
final ListViewItem[] items = new ListViewItem[40];
for (int i = 0; i < items.length; i++) {
if (i == 4) {
items[i] = new ListViewItem("White " + i, CustomAdapter.TYPE_WHITE);
} else if (i == 9) {
items[i] = new ListViewItem("Black " + i, CustomAdapter.TYPE_BLACK);
} else if (i % 2 == 0) {
items[i] = new ListViewItem("EVEN " + i, CustomAdapter.TYPE_EVEN);
} else {
items[i] = new ListViewItem("ODD " + i, CustomAdapter.TYPE_ODD);
}
}
CustomAdapter customAdapter = new CustomAdapter(this, R.id.text, items);
listView.setAdapter(customAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView adapterView, View view, int i, long l) {
Toast.makeText(getBaseContext(), items[i].getText(), Toast.LENGTH_SHORT).show();
}
});
}
}
now create a listview inside mainactivity.xml
like this
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.shivnandan.gygy.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listView"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="100dp" />
</android.support.design.widget.CoordinatorLayout>
In your custom array adapter, you override the getView() method, as you presumably familiar with. Then all you have to do is use a switch statement or an if statement to return a certain custom View depending on the position argument passed to the getView method. Android is clever in that it will only give you a convertView of the appropriate type for your position/row; you do not need to check it is of the correct type. You can help Android with this by overriding the getItemViewType() and getViewTypeCount() methods appropriately.
If we need to show different type of view in list-view then its good to use getViewTypeCount() and getItemViewType() in adapter instead of toggling a view VIEW.GONE and VIEW.VISIBLE can be very expensive task inside getView() which will affect the list scroll.
Please check this one for use of getViewTypeCount() and getItemViewType() in Adapter.
Link : the-use-of-getviewtypecount
ListView was intended for simple use cases like the same static view for all row items.
Since you have to create ViewHolders and make significant use of getItemViewType(), and dynamically show different row item layout xml's, you should try doing that using the RecyclerView, which is available in Android API 22. It offers better support and structure for multiple view types.
Check out this tutorial on how to use the RecyclerView to do what you are looking for.
Related
I'm trying to implement an EmptyView on my RecyclerView Adapter but I'm not getting any result.
I've followed this tutorial and this tip, but noone worked for me.
I've implemented:
if (viewType == EMPTY_VIEW) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view, parent, false);
EmptyViewHolder evh = new EmptyViewHolder(v);
return evh;
}
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_row, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
But it doesn't let me compile because they are differents ViewHolder, because I've created two ViewHolder classes but they extends Recycler.ViewHolder so I don't get it...
I'm trying to do this because I've got a SearchView and I want when the list is empty it shows an EmptyView, I've got it doing it programmatically but I prefer to add like a layout because I don't know that much how to put TextViews and Buttons programmatically.
Also if I put
return dataList.size() > 0 ? dataList.size() : 1;
It gives to me error because index is 0.
I've debugged the viewType and always is 1, then it won't join the if condition...
Deep on Android I found this :
/**
* Return the view type of the item at <code>position</code> for the purposes
* of view recycling.
*
* <p>The default implementation of this method returns 0, making the assumption of
* a single view type for the adapter. Unlike ListView adapters, types need not
* be contiguous. Consider using id resources to uniquely identify item view types.
*
* #param position position to query
* #return integer value identifying the type of the view needed to represent the item at
* <code>position</code>. Type codes need not be contiguous.
*/
public int getItemViewType(int position) {
return 0;
}
But the thing is that no changes the value.
EDIT
I almost done it, I did this :
#Override
public int getItemViewType(int position) {
return list.size() > 0 ? list.size() : 1;
}
But sometimes it returns 0 when the size() is 0... I don't get it, I'm using this SearchView, and sometimes when I type a letter that doesn't matches with any item of the list it doesn't show and sometimes it does...
Also other thing that happens is that when the layout popups it shows on the left of the screen when I put that is on center, but I think it's problem with RecyclerView because the layout puts inside of it.
RecyclerView layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:id="#+id/rtpew"
android:layout_centerInParent="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/linearpew">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</RelativeLayout>
And this is my emptylayout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/ImageViewSearchFail"
android:src="#drawable/sadface"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_gravity="center"
android:textSize="#dimen/15dp"
android:layout_marginTop="4dp"
android:text="foo"
android:layout_below="#+id/ImageViewSearchFail"
android:layout_centerHorizontal="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/ButtonAddEntity"
android:text="foo"
android:background="?android:selectableItemBackground"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
The other way that I thought is to implement it programmatically as follow :
#Override
public boolean onQueryTextChange(String query) {
final ArrayList<List> filteredModelList = filter(mModel, query);
mAdapter.animateTo(filteredModelList);
rv.scrollToPosition(0);
if(query.isEmpty()){
//Here
}
return true;
}
And :
private ArrayList<List> filter(ArrayList<List> models, String query) {
query = query.toLowerCase();
final ArrayList<List> filteredModelList = new ArrayList<List>();
for (List model : models) {
final String text = model.getRedName().toLowerCase();
if (text.contains(query)) {
filteredModelList.add(model);
}
}
if (filteredModelList.size()<0) {
//HERE
}
else{
//Delete the views added
}
return filteredModelList;
}
PROBLEMS
-I only add the view using the #Jimeux answer but I'd like to do this on the Adapter, I got it, but not always shows the view even if the list is empty.
-At the time to put the emptyview.xml it puts inside of the RecyclerView then since I've put all of this xml at the center it shows on the right. I've tried to add the xml programmatically but it's like a chaos....
Since you need to handle two different kind of views, it would be easier to use an intermediate list of business object for more easily binding them with views. Idea is to have a kind of placeholder in your list for representing empty state. Defining an intermediate layer is extremely useful in this sense for allowing you to consider eventual changes to be applied to your list in future (e.g. adding you element types). Moreover in this way you can more clearly separate your business model from ui representation (for example you can implement methods returning ui settings based on internal status of model objects).
You can proceed as follows:
Define a dedicated abstract type for List items (e.g. ListItem) to wrap your business objects. Its implementation could be something like this:
public abstract class ListItem {
public static final int TYPE_EMPTY = 0;
public static final int TYPE_MY_OBJ = 1;
abstract public int getType();
}
Define a class for each of your List element type:
public class EmptyItem extends ListItem {
#Override
public int getType() {
return TYPE_EMPTY;
}
}
public class MyObjItem extends ListItem {
private MyObj obj;
public ContactItem(MyObj obj) {
this.obj = obj;
}
public MyObj getMyObj() {
return obj;
}
// here you can also add methods for simplify
// objects rendering (e.g. get background color
// based on your object internal status)
#Override
public int getType() {
return TYPE_MY_OBJ;
}
}
Create your list.
List<ListItem> mItems = new ArrayList<>();
if (dataList != null && dataList.size() > 0) {
for (MyObj obj : dataList) {
mItems.add(new MyObjItem(obj));
}
} else {
mItems.add(new EmptyItem());
}
This is the most important part of code. You have many options for creating this list. You can do it inside your RecyclerView Adapter or outside, but it's extremely important to properly handle eventual modifications to it. This is essential for exploiting Adapter notify methods. For example, if you create list within the Adapter, it should probably provide also methods for adding or removing your model items. For example:
public void addObj(MyObj obj) {
if (mItems.size() == 1 && mItems.get(0).getType() == ListItem.EMPTY_TYPE) {
mItems.clear();
}
mItems.add(new MyObjItem(obj));
notifyDataSetChanged();
}
Define an adapter for your RecyclerView, working on List defined at point 3. Here what is important is to override getItemViewType method as follows:
#Override
public int getItemViewType(int position) {
return mItems.get(position).getType();
}
Moreover, type of ViewHolder should be RecyclerView.ViewHolder (unless you decide to create an intermediate class even in this case).
Then you need to have two layouts and ViewHolder for empty and business obj items. Adapter methods should take care of this accordingly:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ListItem.TYPE_EMPTY) {
View itemView = mLayoutInflater.inflate(R.layout.empty_layout, parent, false);
return new EmptyViewHolder(itemView);
} else {
View itemView = mLayoutInflater.inflate(R.layout.myobj_layout, parent, false);
return new MyObjViewHolder(itemView);
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
int type = getItemViewType(position);
if (type == ListItem.TYPE_EMPTY) {
EmptyItem header = (EmptyItem) mItems.get(position);
EmptyViewHolder holder = (EmptyViewHolder) viewHolder;
// your logic here... probably nothing to do since it's empty
} else {
MyObjItem event = (MyObjItem) mItems.get(position);
MyObjViewHolder holder = (MyObjViewHolder) viewHolder;
// your logic here
}
}
Of course, as I wrote at the beginning you don't need to strictly define intermediate types for ui representation (EmptyItem and MyObjItem). You can even just use MyObj type and create a specific configuration for it that represent an empty placeholder. This approach is probably not the best in case in future you need to make your logic more complex by including for example new list item types.
Follow the below steps one by one
1). Since you have two types of views for your RecyclerView item, your adapter declaration should look like this a generic one
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
and your ViewHolders for both listview item and empty view should extend RecyclerView.ViewHolder like this
static class ListItemViewHolder extends RecyclerView.ViewHolder {
public ListItemViewHolder(View itemView) {
super(itemView);
// initialize your views here for list items
}
}
static class EmptyViewViewHolder extends RecyclerView.ViewHolder {
public EmptyViewViewHolder(View itemView) {
super(itemView);
// initialize your views here for empty list
}
}
2). You have to Override getItemCount() and getItemViewType()
#Override
public int getItemCount() {
return yourList.size() > 0 ? yourList.size() : 1;// if size of your list is greater than 0, you will return your size of list otherwise 1 for the empty view.
}
#Override
public int getItemViewType(int position) {
if (yourList.size() == 0) {
return VIEW_TYPE_EMPTY;
}
return position;
}
3). Your onCreateViewHolder() will look alike this now
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_EMPTY) {
return new EmptyViewViewHolder(mLayoutInflater
.inflate(R.layout.empty_view_layout, parent, false));
} else {
return new ListItemViewHolder(mLayoutInflater
.inflate(R.layout.row_list_item, parent, false));
}
}
4). Same check you have to apply in your onBindViewHolder() as well
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == VIEW_TYPE_EMPTY) {
EmptyViewViewHolder emptyViewViewHolder = (EmptyViewViewHolder) holder;
// set values for your empty views
} else {
ListItemViewHolder listItemViewHolder = (ListItemViewHolder) holder;
// set values for your list items
}
}
5). At last Override your SearcView.setOnQueryTextListener()
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
currentSearchKeyword = newText.trim();
if(currentSearchKeyword.iseEmpty()){
yourList.clear();
yourAdapter.notifyDataSetChanged();
}else{
// there will two cases again 1). If your currentSearchKeyword matchces with list results, add that items to your list and notify your adapter. 2) If the currentSearchKeyword doesn't matched with list results, clear your list and notify your adapter;
}
return false;
}
});
Hope it will help you, let me know if any issues.
The compilation error probably results because of you extending RecyclerView.Adapter with your main ViewHolder as the generic argument.
You should make it like
YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
And then cast your ViewHolders appropriately (you can reuse getViewType(position) here). Be sure to switch the ViewHolder type in your methods as well.
If I were you, I wouldn't put the empty view in the adapter at all. Put it under your linearpew layout that's holding the RecyclerView and hide/show it as your data changes. You can easily add a loading view, error view, etc. with this setup too.
Here's a bit of simplified code from one of my apps to give you some ideas. #Bind comes from Butter Knife if you're not familiar with it. You may also want to check out Jake Wharton's u2020 project for more RecyclerView ideas.
//fragment_layout.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/content">
</FrameLayout>
<include layout="#layout/status_views" />
</RelativeLayout>
//status_views.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout style="#style/ListStatusView"
android:id="#+id/empty_view"/>
<LinearLayout style="#style/ListStatusView"
android:id="#+id/error_view"/>
<LinearLayout style="#style/ListStatusView"
android:id="#+id/loading_view"
android:padding="30dp"/>
</LinearLayout>
//MyFragment.java
#Bind(R.id.content) protected ViewGroup contentView;
#Bind(R.id.loading_view) protected ViewGroup loadingView;
#Bind(R.id.empty_view) protected ViewGroup emptyView;
#Bind(R.id.error_view) protected ViewGroup errorView;
#Bind({R.id.loading_view, R.id.error_view, R.id.empty_view, R.id.content})
protected List<ViewGroup> stateViews;
protected void activateView(View view) {
for (ViewGroup vg : stateViews)
vg.setVisibility(View.GONE);
view.setVisibility(VISIBLE);
}
#Override
public void onActivityCreated(#Nullable Bundle state) {
super.onActivityCreated(state);
if (state == null) {
activateView(loadingView);
loadData();
} else if (data.isEmpty())
activateView(emptyView);
else
activateView(contentView);
}
Edit: Here's a simplified version without Butter Knife.
private ViewGroup contentView;
private ViewGroup emptyView;
#Override
protected void onCreate(#Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
contentView = (ViewGroup) findViewById(R.id.content_view);
emptyView = (ViewGroup) findViewById(R.id.empty_view);
}
#Override
public boolean onQueryTextChange(String query) {
final ArrayList<List> filteredModelList = filter(mModel, query);
mAdapter.animateTo(filteredModelList);
rv.scrollToPosition(0);
if(query.isEmpty()){
contentView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
} else {
contentView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
return true;
}
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rtpew"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<LinearLayout
android:id="#+id/content_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<RelativeLayout android:id="#+id/empty_view">
<ImageView android:src="#drawable/sadface"/>
<TextView android:text="foo"/>
<Button android:id="#+id/ButtonAddEntity"/>
</RelativeLayout>
</RelativeLayout>
Here is what you can try:
1. Replace
EmptyViewHolder evh = new EmptyViewHolder(v);
with
RecyclerView.ViewHolder evh = new EmptyViewHolder(v);
This is probably why the compilation fails.
2. Replace
#Override
public int getItemViewType(int position) {
return list.size() > 0 ? list.size() : 1;
}
with
#Override
public int getItemViewType(int position) {
return list.get(position) != null ? 1 : 0;
}
For this to work, you must insert a null object whenever you want to show an EmptyView:
int progressPosition = list.size();
list.add(null);
adapter.notifyItemInserted(progressPosition);
and remove the null object when you want to hide the EmptyView:
int progressPosition = existingList.size() - 1;
existingList.remove(progressPosition);
adapter.notifyItemRemoved(progressPosition);
Also, you must modify your onCreateViewHolder() method as follows:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
// inflate your default ViewHolder here ...
} else {
// inflate the EmptyViewHolder here
}
}
I believe we have discussed this before ... see this question for a detailed discussion on this.
3. Instead of using a SearchView, consider using an AutoCompleteTextView with a Filter. This may be easier to integrate with your RecyclerView's Adapter. See this answer for an example of this.
I will update this answer as I understand your question better ... do try this and update me.
I know that I should measure children in onMeasure() and layout them in onLayout(). The question is in what of these methods should I add/recycle views so I could measure all children together with an eye on how they are mutually positioned (i.e. grid, list or whatever)?
My first approach was to add/recycle views in onLayout() but from that point I can't measure my children because they aren't added to AdapterView yet and getChildCount() returns 0 in onMeasure(). And I can't measure AdapterView itself without children being already layouted because it really depends upon their mutual positions, right?
I'm really confused with android layouting process in AdapterView when childrens are added/removed dynamically.
I can't post a comment because I'm a new user, but can you describe WHAT you're trying to do, as opposed to HOW you're trying to do it? Frequently, you will find that this is an issue of design as opposed to coding. Especially if you're coming from a different platform (example, iOS). From experience, I found that measuring and manual layouts in Android is mostly unnecessary if you design your layout properly in light of your business need.
EDIT:
As I mentioned this can be solved using some design decisions. I will use your Nodes/List example (hoping that this is your actual use case, but the solution can be expanded for a more general problem).
So if we think about your Header as a comment in a forum, and the List as replies to your comment, we can make the following assumption:
One list is enough, not two. Each item in the list can either be a header (comment) or a list item (reply). Each reply is a comment, but not all comments are replies.
For item n, I know if it's a comment or a reply (i.e. is it a header or an item in your list).
For item n, I have a boolean member isVisible (default false; View.GONE).
Now, you can use the following components:
One extended adapter class
Two Layout XMLs: One for your Comment, one for your reply. You can have unlimited comments and each comment can have unlimited replies. Both those satisfy your requirements.
Your fragment or activity container class that implements OnItemClickListener to show/hide your list.
So let's look at some code, shall we?
First, your XML files:
Comment row (your header)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/overall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<TextView
android:id="#+id/comment_row_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
Now your reply row (an element in your list)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/overall"
android:layout_width="match_parent"
android:layout_height="wrap_content"> <!-- this is important -->
<TextView
android:id="#+id/reply_row_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/> <!-- important -->
</RelativeLayout>
Ok, now your adapter class
public class CommentsListAdapter extends BaseAdapter implements OnClickListener
{
public static String TAG = "CommentsListAdapter";
private final int NORMAL_COMMENT_TYPE = 0;
private final int REPLY_COMMENT_TYPE = 1;
private Context context = null;
private List<Comment> commentEntries = null;
private LayoutInflater inflater = null;
//All replies are comments, but not all comments are replies. The commentsList includes all your data. (Remember that the refresh method allows you to add items to the list at runtime.
public CommentsListAdapter(Context context, List<Comment> commentsList)
{
super();
this.context = context;
this.inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.commentEntries = commentsList;
}
//For our first XML layout file
public static class CommentViewHolder
{
public RelativeLayout overall;
public TextView label;
}
//For our second XML
public static class ReplyViewHolder
{
public RelativeView replyOverall;
public TextView replyLabel;
}
#Override
public int getViewTypeCount()
{
return 2; //Important. We have two views, Comment and reply.
}
//Change the following method to determine if the current item is a header or a list item.
#Override
public int getItemViewType(int position)
{
int type = -1;
if(commentEntries.get(position).getParentKey() == null)
type = NORMAL_COMMENT_TYPE;
else if(commentEntries.get(position).getParentKey() == 0L)
type = NORMAL_COMMENT_TYPE;
else
type = REPLY_COMMENT_TYPE;
return type;
}
#Override
public int getCount()
{
return this.commentEntries.size(); //all data
}
#Override
public Object getItem(int position)
{
return this.commentEntries.get(position);
}
#Override
public long getItemId(int position)
{
return this.commentEntries.indexOf(this.commentEntries.get(position));
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
CommentViewHolder holder = null;
ReplyViewHolder replyHolder = null;
int type = getItemViewType(position);
if(convertView == null)
{
if(type == NORMAL_COMMENT_TYPE)
{
convertView = inflater.inflate(R.layout.row_comment_entry, null);
holder = new CommentViewHolder();
holder.label =(TextView)convertView.findViewById(R.id.comment_row_label);
convertView.setTag(holder);
}
else if(type == REPLY_COMMENT_TYPE)
{
convertView = inflater.inflate(R.layout.row_comment_reply_entry, null);
replyHolder = new ReplyViewHolder();
replyHolder.replyLable = (TextView)convertView.findViewById(R.id.reply_row_label);
convertView.setTag(replyHolder);
}
}
else
{
if(type == NORMAL_COMMENT_TYPE)
{
holder = (CommentViewHolder)convertView.getTag();
}
else if(type == REPLY_COMMENT_TYPE)
{
replyHolder = (ReplyViewHolder)convertView.getTag();
}
}
//Now, set the values of your labels
if(type == NORMAL_COMMENT_TYPE)
{
holder.label.setTag((Integer)position); //Important for onClick handling
//your data model object
Comment entry = (Comment)getItem(position);
holder.label.setText(entry.getLabel());
}
else if(type == REPLY_COMMENT_TYPE)
{
replyHolder = (ReplyViewHolder)convertView.getTag(); //if you want to implement onClick for list items.
//Or another data model if you decide to use multiple Lists
Comment entry = (Comment)getItem(position);
replyHolder.replyLabel.setText(entry.getLabel()));
//This is the key
if(entry.getVisible() == true)
replyHolder.replyLabel.setVisibility(View.VISIBLE);
else
replyHolder.replyLabel.setVisibility(View.GONE);
}
return convertView;
}
//You can use this method to add items to your list. Remember that if you are using two data models, then you will have to send the correct model list here and create another refresh method for the other list.
public void refresh(List<Comment> commentsList)
{
try
{
this.commentEntries = commentsList;
notifyDataSetChanged();
}
catch(Exception e)
{
e.printStackTrace();
Log.d(TAG, "::Error refreshing comments list.");
}
}
//Utility method to show/hide your list items
public void changeVisibility(int position)
{
if(this.commentEntries == null || this.commentEntries.size() == 0)
return;
Comment parent = (Comment)getItem(position);
for(Comment entry : this.commentEntries)
{
if(entry.getParent().isEqual(parent))
entry.setVisible(!entry.getVisible()); //if it's shown, hide it. Show it otherwise.
}
notifyDataSetChanged(); //redraw
}
}
Ok great, now we have a list of headers with hidden children (remember, we set the default visibility of children to 'gone'). Not what we wanted, so let's fix that.
Your container class (fragment or activity) you will have the following XML definition
<!-- the #null divider means transparent -->
<ListView
android:id="#+id/comments_entries_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#null"
android:dividerHeight="5dp" />
And your onCreateView will implement OnItemClickListener and have the following
private ListView commentsListView = null;
private List<Comment>comments = null;
private static CommentsListAdapter adapter = null;
....
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
...
//comments list can be null here, and you can use adapter.refresh(data) to set the data
adapter = new CommentsListAdapter(getActivity(), comments);
this.commentsListView.setAdapter(adapter);
this.commentsListView.setOnClickListener(this); //to show your list
}
Now to show your list when you click a header
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id)
{
adapter.changeVisibility(position);
}
Now, if an item is clicked and that item has a parent (i.e. list item), it will be shown/hidden according to its current state.
Some comments about the code:
I wrote this on WordPad as I don't have a dev environment handy. Sorry for any compilation errors.
This code can be optimized: If you have a very large data set, this code would be slow since you're redrawing the entire list on every call to changeVisibility(). You can maintain two lists (one for headers, one for list items) and in changeVisibility you can query over the list items only).
I re-enforce that idea that some design decisions would make your life a lot easier. For example, if your list items were actually just a list of labels, then you can have one custom XML file (for your header) and a ListView view within it that you can set to View.GONE. This will make all other views pretend that it's not even there and your layout will work properly.
Hope this helps.
I trying to make a listview which contains a view in every row. This view contains 2 textviews and 1 gridview which is 2 columns. In every column I use a basic layout which is consist of 2 textviews.
This is preview of basic layout which is used in every block of gridview.
Here is its xml; -First view-
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listview_item">
<TextView
android:layout_width="220dp"
android:layout_height="wrap_content"
android:textSize="18dp"
android:text="Item Name"
android:id="#+id/list_item"
android:maxLines="1"
android:layout_marginLeft="5dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Price"
android:textSize="18dp"
android:id="#+id/item_price"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="5dp"/>
</RelativeLayout>
Here is my second view which contains 2 textview and 1 gridview.
Here its xml; -Second view-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listview_item"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="40dp"
android:text="Kategori"
android:id="#+id/categories_title_list_layout"/>
<GridView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/gridView_list_layout"
android:numColumns="2"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not: Lorem ipsum dolor sit amet.."
android:textSize="18dp"
android:id="#+id/categories_note"/>
</LinearLayout>
Here is my last view; -Third view-
This listview's every row takes shape of my second view which takes shape of first view.
Here is my adapter which are create this views.
For first view I use this adapter;
public class ItemListAdapter extends BaseAdapter {
private Context context;
private ArrayList<Item> items;
public ItemListAdapter(Context context, ArrayList<Item> items) {
super();
this.context = context;
this.items = items;
}
public int getCount() {
return items.size();
}
public Object getItem(int i) {
return items.get(i);
}
public long getItemId(int i) {
return i;
}
public View getView(final int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if(view == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.listview_item_content, null);
holder.title = (TextView) view.findViewById(R.id.list_item);
holder.price = (TextView) view.findViewById(R.id.item_price);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
Typeface face=Typeface.createFromAsset(context.getAssets(), "fonts/roboto.ttf");
holder.title.setTypeface(face, Typeface.BOLD);
holder.title.setText(items.get(i).getName());
holder.title.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.price.setText(Global.getLocalizedPriceStringByLocale(Shop.getLocale(), items.get(i).getPrice()));
holder.price.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
return view;
}
public class ViewHolder {
public TextView title;
public TextView price;
}
}
(This adapter takes item's title and price and puts them in first view.)
My second adapter which create second view is here;
public class CategoryListAdapter extends BaseAdapter {
private Context context;
//private ArrayList<Item> items;
private ArrayList<Category> currentCategory;
public CategoryListAdapter(Context context, ArrayList<Category> category) {
super();
this.context = context;
currentCategory = category;
}
public int getCount() {
return currentCategory.size();
}
public Object getItem(int i) {
return currentCategory.get(i);
}
public long getItemId(int i) {
return i;
}
public View getView(final int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.grid_list_layout, null);
holder.title = (TextView) view.findViewById(R.id.categories_title_list_layout);
holder.gridView = (GridView) view.findViewById(R.id.gridView_list_layout);
holder.note = (TextView) view.findViewById(R.id.categories_note);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
Typeface face = Typeface.createFromAsset(context.getAssets(), "fonts/roboto.ttf");
holder.title.setTypeface(face, Typeface.BOLD);
if(currentCategory.get(i).getName().equals("")){
holder.title.setText("Diğer");
}else{
holder.title.setText(currentCategory.get(i).getName());
}
holder.title.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.note.setText(currentCategory.get(i).getDeepNote());
holder.note.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.gridView.setAdapter(new ItemListAdapter(context, currentCategory.get(i).getItems()));
holder.gridView.setBackgroundColor(Color.parseColor(Shop.getInstance().getItemGridBackgroundColor()));
holder.gridView.getBackground().setAlpha(180);
return view;
}
public class ViewHolder {
public TextView title;
public GridView gridView;
public TextView note;
}
}
And I use this adapter to create ListView.
Here is my problem. This works really slow. I mean ListView freezes for a moment then slide down when I try to move down.
And there is another problem about GridView's height. My GridView's height is wrap_content but it doesn't behave like wrap_content. It shows bigger or smaller GridView.
For example; under "Diğer" title there should be a GridView which contains only 1 item as you can see, but it can not show the complete text. And under "Adet Ürünler" there should be 190 items but it only views 20 of them.
These are my problems. Sorry for my coding. If you can not understand my code, please ask me.
Thanks for your helps.
This answer won't give you a definitive solution, not because I'm not willing, but because it's impossible (and even harder without not just viewing your code, but knowing it very well). But from my experience I can tell you that those kind of memory leaks doesn't occur just due to directly referenced objects - objects you declare (and keep referencing another classes/objects) in turn depends on many other classes and so on, and probably you're seeing a memory leak due to an incorrect handling of any of your instances which at the same time reference other instances.
Debugging memory leaks is a often a very hard work, not just because as I said above it sometimes doesn't depend directly on what you've declared, but also because finding a solution might not be trivial. The best thing you can do is what you already seem to be doing: DDMS + HPROF. I don't know how much knowledge you have, but although it's not a universal method, this link helped me so much to find memory leaks in my code.
Although it seems trivial, the best way to debug those kind of things is progresively remove portions of your code (overall, those which implies working with instances of other classes) and see how the HPROF report change.
as far as i understand your problem, it's low performance during scrolling the list you've shown in the screenshot?
a solution to your problem would need you to rewrite your adapter and layouts. Your performance problem is due to the rather large list-items you use (e.g. a grid inside a single list item with 190 items), that have to be loaded during scrolling the list, together with a rather low re-usability of list-items.
in addition, i prefer not to use view-holders.
To get rid of the grids, you could use a list of objects (or wrappers like shown below), that contains the 'title', 'note' and the single grid rows in between. you would have to overwrite some of the adapters methods, to use multiple viewtypes inside one listview (like shown below).
perhaps you'll also need some more code, to map your model into the new list, but after all, your performance should be back to normal.
only disadvantage i know of (and have no fast solution for) is due to the different height in single list items, the scrollbar of the whole list shows sometimes a strange behaviour (like: height of scrollbar indicator changes during scroll)
wrapper
public class ListItemWrapper {
ListItemType type;
Object content;
public ListItemWrapper(ListItemType type, Object content) {
this.type = type;
this.content = content;
}
public enum ListItemType { Title,Note, Content;}
}
ListAdapter
public class ListAdapter extends ArrayAdapter<ListItemWrapper> {
private LayoutInflater inflater;
public ListAdapter(Context context, int resource) {
super(context, resource);
inflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ListItemWrapper item = getItem(position);
switch (item.type) {
case Content: return getViewForGridRow((GridRowContent)item.content, convertView);
case Note: return getViewForNote((String)item.content, convertView);
case Title: return getViewForTitle((String) item.content, convertView);
}
return convertView; //this case should never happen
}
private View getViewForTitle(String content, View convertView) {
if (convertView == null) {
//TODO inflate a new view for the title
convertView = inflater.inflate(R.layout.titleRowLayout, null);
}
//TODO set textview value for the title
return convertView;
}
private View getViewForNote(String content, View convertView) {
if (convertView == null) {
//TODO inflate a new view for the note
convertView = inflater.inflate(R.layout.noteRowLayout, null);
}
//TODO set textview value for the note
return convertView;
}
private View getViewForGridRow(GridRowContent item, View convertView) {
if (convertView == null) {
//TODO inflate a new view for a single grid row
convertView = inflater.inflate(R.layout.gridRowLayout, null);
}
//TODO set values of the grid row (e.g. textview items)
return convertView;
}
#Override
public int getItemViewType(int position) {
return getItem(position).type.ordinal();
}
#Override
public int getViewTypeCount() {
return ListItemType.values().length;
}
}
I simplified parts of the model, but i hope you'll get the point.
as you can see, i don't use a custom BaseAdapter, since the arrayadapter already manages my collection. i only need to tell the list, that there are different item view types and which item in the collection uses which view type.
Additionally, there's no need to use any holders, since all holding and managing the different views is already done by the adapter - e.g. we only need to inflate views if the given convertView is not already cached within the adapter.
using this way should be much more memory efficient and performance should increase, since much less views have to be inflated in a single step.
I hope this helps,
Christian
Edit
can't explain the disapearing gridview in the center, but that shouldn't happen anymore
I have tried to hide items in a custom list adapter. I can hide the visibility of the text but I cannot hide the whole list item. It still shows the dividers etc. I have tried:
tv.setVisibility(View.INVISIBLE);
tv.setVisibility(View.GONE);
convertView.setVisibility(View.INVISIBLE);
convertView.setVisibility(View.GONE);
When I use the convertView i get a null pointer exception.
You can set ContentView with No Element.
In getView() of Your Custom Adapter.
if(condition)
{
convertView=layoutInflater.inflate(R.layout.row_null,null);
return convertView;
}
else
{
convertView=layoutInflater.inflate(R.layout.row_content,null);
return convertView;
}
your XML row_null.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
You have 3 ways to do this:
Remove the items from your list inside or outside the adapter.
Inside the adapter you can do it in the Constructor.
private List<String> list;
public MyAdapter(Context context, List<String> list) {
list.remove(0);
list.remove(1);
list.remove(<whateverPositionYouLike>);
this.list = list;
}
You need to figure out how many items you want to hide and need to build a similar logic.
#Override
public int getCount() {
// In this adapter, we are supposed to hide the first item of the list view
// Returning 0 if no of items are already 0
if(list.size() <=1)
return 0;
// if number of items are more than one, returning one item less
return list.size() - 1;
}
#Override
public Object getItem(int position) {
// skipping the position
return list.get(position + 1);
}
`
#Override
public View getView(final int position, View v, ViewGroup arg2) {
// this is important, as we are supposed to skip the first item of listview
final int localPosition = position +1;
ViewHolderItem holder;
if (v == null) {
holder = new ViewHolderItem();
v = inflater.inflate(R.layout.list_row, null);
v.setTag(holder);
} else {
holder = (ViewHolderItem) v.getTag();
}
return v;
}
`
Answer provided by #hims_3009
You cannot in the way you are trying to, you'll need to use a custom Adapter and implement in there the logic for showing/not showing a line.
If you want to hide a row in a listview, you need to delete data in that position. For example if you use array adapter and want to hide the row on 5. position. You have to delete line from your array and the call notifyDatasetChanged() method.
(You can delete data from array by using tempArray)
or use this way
private List<String> items = new ArrayList<String>();
// make list a new array, clearing out all old values in it.
for(i=0; i<10; i++)
{
if((i != 5) && (i != 6)){
// if k = 5 or 6, dont add those items to the list
items.add(something[i]); /// whatever your list items are.
}
}
ArrayAdapter<String> itemList = new
ArrayAdapter<String>(this,R.layout.itemlistlayout);
setListAdapter(itemlist);
If you want to hide an entire item you need to build some logic into your getView() method to make it skip over various parts of your data.
lets say you have an ArrayAdapter and I want to hide the data that is at index 2. Your getView() method could look something like this.
#Override
public View getView(int pos, View convertView, ViewGroup, parent){
View mView = convertView;
//TODO: Check if convertView was null, and inflate
// or instantiate if needed.
//Now we are going to set the data
mTxt = mView.findViewById(R.id.mTxt);
if(pos >= 2){
//If position is 2 or above, we ignore it and take the next item.
mTxt.setText(this.getItem(pos + 1).toString());
}else{
//If position is below 2 then we take the current item.
mTxt.setText(this.getItem(pos).toString());
}
return mView;
}
Note that this example is generalized, it is not meant to be able to be dropped directly into your project. I had to make assumptions about some things which I don't know the truth on. You can use the same concept as I've shown here though and modify it to your situation to be able to "hide" rows in your ListView.
If you already have a custom list adapter you can just call notifyDataSetChanged() on the adapter and the adapter itself has of course to implement the logic to filter out the views for the rows you want to hide. These are the methods that come to my mind that need to reflect that logic:
#Override
public Object getItem(int position) {
return mItems.get(position);
}
#Override
public int getCount() {
return mItems.size();
}
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
// here goes your logic to hide a row
Additionally you might have to change getItemId() as well.
I think I have a much easier / safer solution: you just have to "embed" your item in a Layout, and change the visibility of this parent layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<!-- Embed ListView Item into a "parent" Layout -->
<LinearLayout
android:id="#+id/parentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<!-- This is the normal content of your ListView Item -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="World" />
</LinearLayout>
</LinearLayout>
Then in your code just do:
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater li = mActivity.getLayoutInflater();
view = li.inflate(R.layout.my_listview_item, null);
}
LinearLayout parentLayout = (LinearLayout) view.findViewById(R.id.parentLayout);
if (shouldDisplayItem(position)) {
parentLayout.setVisibility(View.VISIBLE);
} else {
parentLayout.setVisibility(View.GONE);
}
return view;
}
This way you always use/reuse the same item, and just hide/show it.
This is the first time I need to use the ArrayAdapter<T> to show a multi-item Row-Layout. After a lot of successful work with different adapters this one is driving me crazy.
getView(..., position, ...) always returns 0-7[EDIT] so I never see elements in my ArrayList that are on position >= 7[/EDIT]. I know, this is the visible position, but how do I select the correct object in the ArrayList?
EDIT: Currently I only get the first 8 elements out of my array because position only comes in from 0-7 - even on a 50 element ArrayList. I don't see a way to position within the ArrayList without a "real" position.
The docs say the following - but I don't get it. Did somebody successfully implement an ArrayAdapter<T> with a complex layout? What do the doc mean and how should I implement it?
If you want to use a more complex
layout, use the constructors that also
takes a field id. That field id should
reference a TextView in the larger
layout resource. However the TextView
is referenced, it will be filled with
the toString() of each object in the
array. You can add lists or arrays of
custom objects. Override the
toString() method of your objects to
determine what text will be displayed
for the item in the list. To use
something other than TextViews for the
array display, for instance,
ImageViews, or to have some of data
besides toString() results fill the
views, override getView(int, View,
ViewGroup) to return the type of view
you want.
Many thanks in advance
hjw
Here's the code so far:
public class HappyAdapter extends ArrayAdapter<Happy> {
static class ViewHolder {
private ImageView imageView;
private TextView textViewBottom;
private TextView textViewTop;
}
private ArrayList<Happy> arrayListHappy;
private DrawableCache drawableCache = DrawableCache.getInstance();
private int layout;
#Override
public int getCount() {
return arrayListHappy.size();
}
#Override
public View getView(int position, View contentView, ViewGroup viewGroup) {
// position always 0-7
View view = null;
ViewHolder viewHolder = null;
if (contentView == null) {
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = layoutInflater.inflate(layout, null);
if (view != null) {
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) view.findViewById(R.id.happyactivity_row_image_left);
viewHolder.textViewBottom = (TextView) view.findViewById(R.id.happyactivity_row_text_bottom);
viewHolder.textViewTop = (TextView) view.findViewById(R.id.happyactivity_row_text_top);
view.setTag(viewHolder);
}
} else {
view = contentView;
viewHolder = (ViewHolder) contentView.getTag();
}
if (viewHolder != null) {
Happy happy = arrayListHappy.get(position);
if (happy != null) {
viewHolder.imageView.setUrl(happy.getImageThumbnail());
drawableCache.fetchDrawable(happy.getImageThumbnail(), viewHolder.imageView);
viewHolder.textViewBottom.setText(String.valueOf(position));
viewHolder.textViewTop.setText(String.valueOf(viewHolder.position));
}
}
return view;
}
public HappyAdapter(Context context, int layout, ArrayList<Happy> arrayListHappy) {
super(context, layout, arrayListHappy);
this.arrayListHappy = arrayListHappy;
this.layout = layout;
}
}
This is part of the Row-Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="horizontal" >
<ImageView
android:id="#+id/happyactivity_row_image_left"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="fill_parent" />
<TextView
style="#style/TextViewStandard"
android:id="#+id/happyactivity_row_text_top"
android:layout_weight="1" />
<TextView
style="#style/TextViewStandard"
android:id="#+id/happyactivity_row_text_bottom"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
I can't add comments yet so have to answer. As others have noted it isn't really clear what's not working. However, you are only seeing positions 0-7 being inflated as this is the only portion of the list that is currently visible, as you noted yourself. Other rows (with higher position numbers) won't be inflated until you scroll down the list.
Although it uses the BaseAdapter class, rather than ArrayAdapter that you are using, you could look at List14.java in the ApiDemos sample code (ApiDemos/src/com/example/android/apis/view/List14.java) as the principle is the same. When using ArrayAdapter though, you don't need to override all the methods that this sample code does (just getView).