I feel a bit stupid as i can't find the answer to this question, which makes me think i'm actually asking the wrong question. However, here goes...
I have a list view, and a listviewitem defined in xml, with a couple of fields, nothing special. All set to visible.
Then I bind to my ListView using a custom ArrayAdapter, and want to hide one of my text views, on row 5. However, it seems to be hiding my TextView on item 0 and item 5. Which is a bit odd? I've simplified the code, to reproduce the problem and hopefully someone will be able to help me...
My Adapter
public class MenuScreenAdapter extends ArrayAdapter<String>
{
private List<String> _items;
private Context _context;
public MenuScreenAdapter(Context context, List<String> items)
{
super(context, R.layout.list_menu_item, items);
_context = context;
_items = items;
}
private MenuScreenAdapter(Context context, int textViewResourceId)
{
super(context, textViewResourceId);
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
if (v == null)
{
LayoutInflater vi = (LayoutInflater) _context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_menu_item, null);
}
String o = _items.get(position);
if (o != null)
{
TextView tt = (TextView) v.findViewById(R.id.list_menu_item_name);
if (tt != null)
tt.setText(o);
if (position == 5)
tt.setVisibility(View.GONE);
}
return v;
}
}
My Binding Code
// Load everything up that we need
List<String> items = new ArrayList<String>();
items.add("One");
items.add("Two");
items.add("Three");
items.add("Four");
items.add("Five");
items.add("Six");
items.add("Seven");
items.add("Eight");
items.add("Nine");
items.add("Ten");
// Get the ListView, and set it's adapter. The HomeScreenAdapter
// takes care of the rest
ListView homeScreenListView = (ListView) _mainActivity.findViewById(R.id.view_home_list);
homeScreenListView.setOnItemClickListener(ItemSelected);
homeScreenListView.setAdapter(new MenuScreenAdapter(_mainActivity.getBaseContext(), items));
Thanks in advance!
Since row views are reused by ArrayAdapter, once the View.GONE is set, it will cary on to the next row, where this view will be reused. In your case, you set View.GONE to textview in the fifth row, moved list a little and arrayadapter decided to reuse your fifth row layout to display the first row, since no changes were done to it, the textView still remains hidden.
Just do the:
if (position == 5) {
tt.setVisibility(View.GONE);
} else {
tt.setVisibility(View.VISIBLE);
}
P.S. If you still haven't, watch a presentation about ListViews from google. Tons of usefult info there. ListViews
Related
I have a list view which uses a custom ArrayAdapter. The items of the ListView are RelativeLayouts. The "Light" views which are stored in "lightsOnThisTrack" list of a "Track" object are added afterward to its corresponding RelativeLayouts.
The thing is that if I add more items to the ListView, the views that were previously added to the relativeLayouts start to repeat on the newly added items. On the other hand, the TextView "trackText" is not being repeated, as can be seen in the example. As I've read on other posts, I know that it's a problem related to the way the ViewHolder pattern is implemented, but I cannot spot where the problem is.
public class TrackListAdapter extends ArrayAdapter<Track> {
private static final String TAG = "TrackListAdapter";
private LayoutInflater layoutInflater;
public ArrayList<Track> trackArrayList;
Context mContext;
RelativeLayout relativeLayout;
public TrackListAdapter(Context context, ArrayList<Track> trackArrayList) {
super(context, 0, trackArrayList);
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.mContext = context;
this.trackArrayList = trackArrayList;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View rowView = convertView;
ViewHolder viewHolder;
if (rowView == null) {
rowView = layoutInflater.inflate(R.layout.track_list_item, null);
viewHolder = new ViewHolder();
viewHolder.relativeLayout = (RelativeLayout) rowView.findViewById(R.id.relativeLayout);
viewHolder.trackText = new TextView(mContext);
viewHolder.trackText.setTextColor(Color.GRAY);
viewHolder.trackText.setX(100);
viewHolder.trackText.setY(20);
viewHolder.trackText.setTextSize(18);
viewHolder.relativeLayout.addView(viewHolder.trackText);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
viewHolder.track = trackArrayList.get(position);
if (viewHolder.track.getName() == null)
viewHolder.trackText.setText(" NUMBER " + position);
else
viewHolder.trackText.setText(viewHolder.track.getName());
for (int i = 0; i < viewHolder.track.getNumberOfLights(); i++) {
Light light = viewHolder.track.lightsOnThisTrackList.get(i);
if (light.getParent() != null) {
if (!light.getParent().equals(viewHolder.relativeLayout)) {
ViewGroup viewGroup = (ViewGroup) light.getParent();
if (viewGroup != null) viewGroup.removeView(light);
viewHolder.relativeLayout.addView(light);
}
} else {
viewHolder.relativeLayout.addView(light);
}
}
notifyDataSetInvalidated();
notifyDataSetChanged();
return rowView;
}
public static class ViewHolder {
Track track;
TextView trackText;
RelativeLayout relativeLayout;
}
public View getViewByPosition(int pos, ListView listView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1;
if (pos < firstListItemPosition || pos > lastListItemPosition) {
return listView.getAdapter().getView(pos, null, listView);
} else {
final int childIndex = pos - firstListItemPosition;
return listView.getChildAt(childIndex);
}
}
}
The problem is not the ViewHolder. The problem is that you are not taking into account what happens when your view is recycled.
Suppose for position 0 you add two Lights to the Relativelayout. Then the user scrolls and the view gets recycled to another position (let's say position 10). The RelativeLayout you are given already has two Lights in it before you do anything.
You either need to remove all the previous Lights first, or you need to be able to re-use ones that are there (and still you might have to remove some in case the row you're creating has fewer Lights than are already present).
The TextView is not repeated because you are not creating a TextView every time the view is recycled; you are only creating it when a new row is being inflated.
A few other suggestions:
There should be no reason to call notifyDataSetInvalidated() and notifyDataSetChanged() inside of getView().
I discourage the use of holding lists of Views (in this case, Lights) in your data model. You don't have a clear separation between data and presentation, and I think it will only complicate your code. It would be easier to just store how many lights a Track needs and handle the actual Views separately.
I would also try to avoid creating, adding, and removing Views inside of getView(). For instance, if you know there's a small, limited number of lights a Track can have (suppose it's five), then it's easy enough to have that many corresponding views in the row layout already and just toggle their visibility appropriately. Or, you can make a custom View that knows how to draw up to that number of lights and you just change the number inside of getView().
I have a List of Objects(POIs- Point of Interested), and now I want to display them in a `ListView.
And the layout of the each item is a little complex.
For an POI object, I will display its name address distance and picutre(if any) and etc.
After google and search at Stackoverflow, it seems that I can use the ArrayAdapter.
As shown in this examle, I have to create a Adapter which extends ArrayAdapter, for example :
private class POIAdapter extends ArrayAdapter<POI> {
private ArrayList<POI> items;
public POIAdapter(Context context, int textViewResourceId, ArrayList<POI> items) {
super(context, textViewResourceId, items);
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
}
POI o = items.get(position);
if (o != null) {
TextView tt = (TextView) v.findViewById(R.id.toptext);
TextView bt = (TextView) v.findViewById(R.id.bottomtext);
if (tt != null) {
tt.setText("Name: "+o.getName()); }
if(bt != null){
bt.setText("Address: "+ o.getAddress());
}
}
return v;
}
}
As you can see, I have to refer to the view elements in this adapter, so I think it is not the best choice because the layout of the POI item may change someday, then I have to change this adapter accordingly.
Then I found the SimpleCursorAdapter which can map columns from a cursor to views in an XML file, but it seems that I have to create my own Cursor.
So I wonder if which is better for implemention and possible extension?
You may want to choose ArrayAdapter if:
If you're using an array to hold the data.
The layout has some logic in it apart from simple mapping of data to UI elements (e.g. some elements are hidden, etc.)
SimpleCursorAdapter may be better if:
You already have a cursor with your data and your layout needs are simple.
It's hard to give a very general guidance as lots depends on exact requirements. Also, think about what you may want to do in the future. ArrayAdapter makes it easier to create complex layouts even if you need to make code changes to achieve that.
This problem has been stuck for a while in my head.
What I need to do:
Show a listview with alternating resources for the items in the listView.
What is my problem:
So far I can alternate resources and show no data, or show the data but not alternate resources. The first item works well every time, but not form there onwards. I think I'm very close but I just can't think what is going wrong...
What have I done:
I have used a custom simple cursor adapter.
Where is the code:
public class DialogCursor extends SimpleCursorAdapter {
private LinearLayout wrapper;
private TextView burbuja;
public DialogCursor(Context context, int layout, Cursor c, String[] from,
int[] to, int flags) {
super(context, layout, c, from, to, flags);
// TODO Auto-generated constructor stub
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
Context context = parent.getContext();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.dialogo_row, parent, false);
}
burbuja = (TextView) row.findViewById(R.id.idiomaselec);
wrapper = (LinearLayout) row.findViewById(R.id.wrapper);
//get reference to the row
View view = super.getView(position, convertView, parent);
Log.d("Dialogo","enters getview");
Log.d("Dialogo",Integer.toString(position));
//check for odd or even to set alternate colors to the row background
if(position % 2 == 0){
Log.d("Dialogo","Even");
burbuja.setBackgroundResource(R.drawable.bubble_green);
wrapper.setGravity(Gravity.LEFT);
}
else {
Log.d("Dialogo","not even");
burbuja.setBackgroundResource(R.drawable.bubble_yellow);
wrapper.setGravity(Gravity.RIGHT);
}
return row;
}
}
The cursor adapter is called from this other class (just showing relevant part)
String[] from = new String[] { DialogoTable.TABLE_DIALOGO + "." + columna };
// Fields on the UI to which we map
final int[] to = new int[] { R.id.idiomaselec};
Log.d("Dialogo","entra en fillData2");
getLoaderManager().initLoader(0, null, this);
if (bot) {
Log.d("Dialogo","entra en fillData2.5");
getLoaderManager().restartLoader(0, null, this);
}
adapter2 = new DialogCursor(this, R.layout.dialogo_row, null, from, to, 0);
setListAdapter(adapter2);
And the output:
If I return row (last line of code)
I get the background resources in the right place but with no data
If I return view (last line of code)
I get the data but only the first item has the right background resources.
One last note:
I have followed this example
http://adilsoomro.blogspot.com/2012/12/android-listview-with-speech-bubble.html
but I dont want to create a class message since I wnat the data from my DB.
Thank you for your help :)
In a similar case I was able to have a custom cursorAdapter alternate resources based on the cursor position. I put the following code in my bindView where entryView is the passed in view. I am overriding getView at all.
if(cursor.getPosition()%2 == 1){
entryView.findViewById(R.id.title_relative).setBackgroundColor(getResources().getColor(R.color.orange));
}else{
entryView.findViewById(R.id.title_relative).setBackgroundColor(getResources().getColor(R.color.blue));
}
I need to display a list of A objects (think List<A>). A has the following structure:
class A {
List<B> bList;
List<C> cList;
}
All three lists can be of arbitrary length. bList and cList should be displayed in their entire length in each row of the list of As. Each list is backed by a SQLite cursor. It's a sort of calendar view. The following image illustrates the idea:
Now, I'm wondering what's the best way to achieve this "in the Android way". I tried multiple things:
ListView for A with nested ListViews for B and C: Not recommended, hard to disable the scrolling behaviour of B and C.
ListView for A with LinearLayout for B and C and programmatically adding child views to the LinearLayouts in the Adapter: I have to manage Cursor updates for B and C and adjust the height of the rows myself, lots of view management code in the Adapter where it does not belong.
Composing everything of nested LinearLayouts: Same problem as 2, even more Cursors to deal with.
Maybe there's a different way where I can fully take advantage of existing functionality?
I already had a look at similar questions on StackOverflow. The top two suggestions seem to be:
Spread data over multiple Activities/Fragments: Considered, not an option because not user friendly (in this case).
Use ExpandableListView: Does not seem to be applicable to the data structure, the list of Bs and Cs should be visible from the beginning.
To implement this type of view you need to implement two things.
ListView listView;
IArrayAdapter iArrayAdapter;
Initialize listView with id provided in xml.
Activity.this.runOnUiThread(new Runnable() {
public void run() {
iArrayAdapter = new IArrayAdapter(Activity.this,
R.layout.list_item, "list of items group it from Bean");
listView.setAdapter(iArrayAdapter);
iArrayAdapter.notifyDataSetChanged();
}
});
list_item is another layout which contaion type of display you need to display in list.
IArrayAdapter is class extending ArrayAdapter
public class IArrayAdapter extends ArrayAdapter<IBean> {
private final Activity context;
private final ArrayList<IBean> iBeans;
private int resourceId;
public InboxArrayAdapter(Activity context, int resourceId,
ArrayList<IBean> iBeans) {
super(context, resourceId, inboxBeans);
this.context = context;
this.iBeans = iBeans;
this.resourceId = resourceId;
}
/*
* TO update View
*
* #see android.widget.ArrayAdapter#getView(int, android.view.View,
* android.view.ViewGroup)
*/
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
if (rowView == null) {
LayoutInflater layoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = layoutInflater.inflate(resourceId, null);
final IBean iBean = iBeans.get(position);
final ImageView imageView = (ImageView) rowView
.findViewById(R.id.message);
final TextView rowTxt = (TextView) rowView
.findViewById(R.id.senderName);
final TextView rowTxt1 = (TextView) rowView
.findViewById(R.id.senderMessage);
final TextView rowTxt2 = (TextView) rowView
.findViewById(R.id.senderTime);
final CheckBox check = (CheckBox) rowView.findViewById(R.id.check);
.....set text here.....
return rowView;
}
}
imageView, rowtext, etc are part of layout list_item
ANd IBean is java bean class contain your 5 iTem in a list.
Any item you don't want left it blank.
I have a ListFragment where I want certain rows to be a certain color. I basically followed this: Creating a ListView and setting the background color of a view in each row
However, getView is never called. Does anyone know why?
public class TrackerFragment extends ListFragment
{
private String[] list;
#Override
public void onActivityCreated(Bundle myBundle)
{
super.onActivityCreated(myBundle);
list = null;
ListView lv = getListView();
setListAdapter(null);
setEmptyText("Touch a connection to view tracker information.");
lv.setTextFilterEnabled(true);
}
public void updateList(String[] list)
{
this.list = list;
setListAdapter(new ColoredArrayAdapter(getActivity(),R.layout.list_item,list));
}
}
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="7dp"
android:textSize="14sp"
android:id="#+id/line">
</TextView>
I am updating the list like this from my activity:
TrackerFragment tf = (TrackerFragment) fragmentManager.findFragmentById(R.id.tracker1);
tf.updateList(result);
My ColoredArrayAdapter
public class ColoredArrayAdapter extends ArrayAdapter{
private String[] list;
public ColoredArrayAdapter(Context context, int textViewResourceId,
Object[] objects) {
super(context, textViewResourceId, objects);
list = new String[objects.length];
for (int i = 0; i < list.length; i++)
list[i] = (String) objects[i];
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View vi = convertView;
ViewHolder holder;
if (convertView == null)
{
vi = LayoutInflater.from(getContext()).inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.line = (TextView) vi.findViewById(R.id.line);
vi.setTag(holder);
}
else
holder = (ViewHolder) vi.getTag();
for (int i = 0; i < list.length; i++)
{
if (list[i].contains("OUT OF LOCK"))
{
System.out.println("OUT OF LOCK");
holder.line.setText(list[i]);
//holder.line.setTextColor(R.color.white);
holder.line.setBackgroundResource(R.color.red);
}
else if(list[i].contains("IN LOCK"))
{
System.out.println("In LOCK");
holder.line.setText(list[i]);
//holder.line.setTextColor(R.color.white);
holder.line.setBackgroundResource(R.color.green);
}
else
holder.line.setText(list[i]);
}
return vi;
}
}
What am I doing wrong?
Edit: added list_item.xml, where line is found.
Edit2: added extended array adapter
Now my problem is that, every row is either all green or red, when I just want certain individual rows to be either red or green. Also, none of the text is showing up.
Your current getView implementation should be moved into a ListAdapter implementation instead of your TrackerFragment class. Since you're using ArrayAdapter, you can subclass that and put the code in there. ArrayAdapter already implements getView, but you'll override it to provide your specialized behavior.
The reason you're getting a NullPointerException is because you're calling getView and passing in the list view, which does not have a tag associated with it -- so holder = (ViewHolder) vi.getTag(); assigns null to holder. That said, you shouldn't be calling getView directly. The system will call that for you whenever it needs to display a list item. When the system calls the getView method, it initially passes in null to have the views created, and every call where convertView is not null is a view created by that method.
Looks like the same problem as the post you linked: the getView() method isn't nested inside the class.
Or your code doesn't show anything that would call it either.
The more I look over this, the more I wonder about the basic premise you are using. I think you're making it overly complicated. I would do it like this:
public View getView(View convertView)
{
View vi = convertView;
TextView viText = null;
if (vi == null)
vi = LayoutInflater.from(getActivity()).inflate(R.layout.list_item, null);
viText = (TextView)vi.findViewById(R.id.line);
if (viText == null) return vi;
String viString = viText.getText().toString();
if (viString.contains("OUT OF LOCK"))
{
viText.setBackgroundResource(R.color.red);
}
else if (viString.contains("IN LOCK"))
{
viText.setBackgroundResource(R.color.green);
}
return vi;
}
I don't think you are using the holder in the way you think... the loop you have in there will just loop through setting the background resource to whatever the last trigger to set the backgroundResource is, no matter what.
If I have missed the point in this, let me know. But, my basic thought would be to remove as much complexity as you can until it works, and if you've had to remove something important, slowly add it back in.