I'm trying to populate a ListView with Strings (food item names) on the left side, and item count numbers on the right. I've made a CustomAdapter class (see below) which extends BaseAdapter after following some tutorials, where I could see (via print statements) that at least the getCount() method is called, but my getView() method doesn't get called and my ListView doesn't appear on my app. What am I doing wrong? I assume that it's because I don't know how to make getView() run in the right place. It works fine when I use a normal ArrayAdapter with an array of strings.
Here is how I tried populating the ListView in my MainActivity class:
CustomAdapter customAdapter = new CustomAdapter();
ListView lv1 = (ListView) findViewById(R.id.listView);
lv1.setAdapter(customAdapter);
customAdapter.notifyDataSetChanged();
Now, here is my CustomAdapter class:
import java.util.ArrayList;
import java.util.List;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class CustomAdapter extends BaseAdapter {
List<foodItem> mItemList = getDataForListView();
public CustomAdapter(List<foodItem> list){
this.mItemList = list;
// tried this as an alternative
}
#Override
public int getCount() {
return mItemList.size();
}
#Override
public foodItem getItem(int arg0) {
return mItemList.get(arg0);
}
#Override
public long getItemId(int arg0) {
return arg0;
}
#Override
public View getView(int arg0, View arg1, ViewGroup parent) {
System.out.println("Item list size: " + mItemList.size()); // doesn't show anything in console
if(arg1==null) {
LayoutInflater inflater = (LayoutInflater) LayoutInflater.from(parent.getContext());
arg1 = inflater.inflate(R.layout.listrow, parent ,false);
// R.layout.listrow is an XML file defined and formatted at 60sp tall
// as that is how tall I want each item in the ListView to be
}
TextView foodItem = (TextView)arg1.findViewById(R.id.foodTitle);
TextView likeCount = (TextView)arg1.findViewById(R.id.likeCount);
foodItem i = mItemList.get(arg0);
foodItem.setText(i.getItem());
likeCount.setText(i.getLikes());
return arg1;
}
public List<foodItem> getDataForListView() {
List<foodItem> itemList = new ArrayList<foodItem>();
// Test method populating with some data
for(int i=0; i<50; i++) {
itemList.add(new foodItem(i, "Item " + i));
}
return itemList;
}
}
I also tried adding my own constructor, but the result was the same. Any help would be greatly appreciated!
EDIT: The layout for my main activity:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
tools:context="com.example.inventoryList.MainActivity" >
<ListView
android:id="#+id/foodOptions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp" />
</RelativeLayout>
Last, here is my listrow.xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="60sp"
android:orientation="horizontal"
android:padding="5sp" >
<TextView
android:id="#+id/foodTitle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:textSize="12sp"
android:text="123"
android:gravity="center" />
<TextView
android:id="#+id/likeCount"
android:layout_width="12sp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:text="#+string/likeCount"
android:textSize="10sp"
tools:ignore="SmallSp" />
</RelativeLayout>
You are Re-declaring List<foodItem> itemList in getDataForListView. This overrides the member variable. Change to
itemList = new ArrayList<>();
Also you should do this in the constructor, not a member method and prepending an m (so mItemList) to your member variables is good style and helps prevent this problem. So all together:
private class CustomAdapter extends BaseAdapter {
private List<foodItem> mItemList; //camel case isn't usually applied to classes os this should be FoodItem
public CustomAdapter() {
mItemList = new ArrayList<>();
for (...) {
...
}
}
...
}
Better still, pass the List to the Constructor:
private class CustomAdapter extends BaseAdapter {
private List<FoodItem> mItemList;
public CustomAdapter(List<FoodItem> itemList) {
mItemList = itemList;
}
...
}
I fixed it. My Main Activity has three tabs, and for each I set a tab listener that changes what's in the adapter. I had assumed that these tab listeners wouldn't be invoked upon starting the app, but apparently that was not true. In consequence, from things I had commented out in testing, I had been called setAdapter() and passing in an empty adapter.
Thanks for the help all, sorry that it turned out to be a mistake in code that I hadn't provided.
I had the similar problem, and I resolved by putting
adapter.notifyDataSetChanged();
OUTSIDE of my try-catch block.
Implement a constructor for your adapter so you can pass an items list to it:
public CustomAdapter(List<foodItem> itemList) {
this.itemList = itemList;
}
Create your list beforehand and pass it to the adapter constructor:
ArrayList<foodItem> items = buildItemsList();
CustomAdapter customAdapter = new CustomAdapter(items);
ListView lv1 = (ListView) findViewById(R.id.listView);
lv1.setAdapter(customAdapter);
If you need a mutable adapter, implement a method (in the adapter) to add items and notify data set changes after that:
public void add(foodItem item) {
itemList.add(item);
notifyDataSetChanged();
}
You can do the same to remove and update items, just remember to notify changes so the list view will refresh.
Related
I have a problem with the following exception:
exception:java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.widget.ListAdapter)' on a null object reference
I have an Adapter class:
public class SimpleAdapter extends BaseAdapter {
private LayoutInflater lInflater;
private String [] simpleValueList;
public SimpleAdapter(Context context, String[] simpleValueList) {
lInflater = LayoutInflater.from(context);
this.simpleValueList = simpleValueList;
}
#Override
public int getCount() {
return simpleValueList.length;
}
#Override
public Object getItem(int position) {
return simpleValueList[position];
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = lInflater.inflate(R.layout.test, parent, false);
TextView textSimple = view.findViewById(R.id.simple_list);
textHours.setText(simpleValueList[position]);
}
return view;
}
}
my CustomView class:
public class SimpleView extends View {
private String[] valueList = {"aa", "2", "bb", "3"};
private ListView listView;
private SimpleAdapter simpleAdapter;
public SimpleView(Context context, ViewGroup mviewGroup) {
super(context);
inflate(context, R.layout.test, mviewGroup);
listView = findViewById(R.id.simple_list);
simpleAdapter = new SimpleAdapter(context, valueList);
listView.setAdapter(simpleAdapter);
}
}
my Activity class:
public class SimpleActivity extends AppCompatActivity {
private SimpleView simpleView;
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout linearLayout = findViewById(R.id.custom_view);
simpleView = new SimpleView(this, linearLayout);
linearLayout.addView(simpleView);
}
}
test. xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="100dp"
android:layout_height="220dp"
android:layout_gravity="center">
<ListView
android:id="#+id/simple_list"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#color/orange" />
</LinearLayout>
my activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/custom_view"
android:layout_width="400dp"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
If I do not add the Adapter in the SimpleView class then it works. But if I add the Adapter in the SimpleView class I get the exception:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.widget.ListAdapter)' on a null object reference. (at ...here: listView.setAdapter(simpleAdapter); (simpleView = new SimpleView(this, linearLayout));
The problem is that the code is calling the SimpleView's version of findViewById which is using test.xml, but the ListView is located in activity_main.xml in the SimpleActivity.
listView = findViewById(R.id.custom_view1);
This will return null because there is no view with the ID R.id.custom_view1 inside SimpleView.
There probably is not any need for the SimpleView class. The following lines of code could be moved into onCreate in SimpleActivity:
listView = findViewById(R.id.custom_view1);
simpleAdapter = new SimpleAdapter(context, valueList);
listView.setAdapter(simpleAdapter);
If you do want to create a CustomView on the other hand, all the views you are working with need to be in R.layout.test. You could in theory, move the ListView in there, but I think it is probably unnecessary. I would just inflate, find it and set the adapter in onCreate of the Activity.
If you do want an abstraction to contain the ListView, I would investigate putting it into a Fragment.
EDIT:
If it has to be done with a custom view then make sure the following is in test.xml:
<ListView
android:id="#+id/custom_view1"
android:layout_width="71dp"
android:layout_height="77dp"
android:orientation="vertical">
It is important to use this ID android:id="#+id/custom_view1 so that it matches up with listView = findViewById(R.id.custom_view1); in the Java code.
To void the crash you are getting now:
public SimpleView(Context context, ViewGroup mviewGroup) {
super(context);
View layout = inflate(context, R.layout.test, mviewGroup);
listView = layout.findViewById(R.id.simple_list);
simpleAdapter = new SimpleAdapter(context, valueList);
listView.setAdapter(simpleAdapter);
}
Make sure you do the find on the value being returned from inflate.
=== Code Dump ===:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/custom_view_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
test.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="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:layout_gravity="center">
<ListView
android:id="#+id/simple_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="100dp"
android:layout_height="220dp"
android:layout_gravity="center">
<TextView
android:id="#+id/txt"
android:layout_width="100dp"
android:layout_height="100dp"/>
</LinearLayout>
ActivityMain.java:
package com.example.myapplication2000;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout linearLayout = findViewById(R.id.custom_view_container);
SimpleView simpleView = new SimpleView(this, linearLayout);
linearLayout.addView(simpleView);
}
}
SimpleView.java:
package com.example.myapplication2000;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class SimpleView extends View {
private String[] valueList = {"aa", "2", "bb", "3"};
private ListView listView;
private SimpleAdapter simpleAdapter;
public SimpleView(Context context, ViewGroup mviewGroup) {
super(context);
View layout = inflate(context, R.layout.test, mviewGroup);
listView = layout.findViewById(R.id.simple_list);
simpleAdapter = new SimpleAdapter(context, valueList);
listView.setAdapter(simpleAdapter);
}
public class SimpleAdapter extends BaseAdapter {
private LayoutInflater lInflater;
private String [] simpleValueList;
public SimpleAdapter(Context context, String[] simpleValueList) {
lInflater = LayoutInflater.from(context);
this.simpleValueList = simpleValueList;
}
#Override
public int getCount() {
return simpleValueList.length;
}
#Override
public Object getItem(int position) {
return simpleValueList[position];
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = lInflater.inflate(R.layout.row, parent, false);
TextView textSimple = view.findViewById(R.id.txt);
textSimple.setText(simpleValueList[position]);
}
return view;
}
}
}
Sorry, I might have changed one or two of the names. I hope it does not cause any confusion.
Also, you can clean-up the layout. There are a few 'extra' LinearLayouts that I picked up from pasting in your code, but they are not really needed.
But this code is displaying 4 list items on my device.
How do I create a working ListView in Android?
I am not looking for you to just fix my code, but am looking for a simple working example of a ListView in Android so I can understand the process of creating one and working with it. But I have included my code so you can see where I am coming from and what I have been trying.
I have done the following and had no success:
--
Made a xml layout with only a TextView item in it:
<?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:id="#+id/dir_text_view"
/>
Created the following class as per the instructions at the following tutorial:
http://www.vogella.com/tutorials/AndroidListView/article.html
public class DataTempleArrayAdapter extends ArrayAdapter<String> {
HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
public DataTempleArrayAdapter(Context context, int textViewResourceId,
List<String> objects) {
super(context, textViewResourceId, objects);
for (int i = 0; i < objects.size(); ++i) {
mIdMap.put(objects.get(i), i);
}
}
#Override
public long getItemId(int position) {
String item = getItem(position);
return mIdMap.get(item);
}
#Override
public boolean hasStableIds() {
return true;
}
}
And in the main activity I have a snippet of code where I attempt to add a list of strings to the ArrayList associated with the DataTempleArrayAdapter here:
int i;
for (i=0;i<dirContents.length;i++) {
dirList.add(dirContents[i]);
//Toast.makeText(this, dirList.get(i), Toast.LENGTH_SHORT).show();
}
adapter.notifyDataSetChanged();
dirList is successfully populated, while the adapter doesn't update the ListView at all.
--
Before you ask for it, here I am including the rest of the relevant code:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
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"
tools:context="org.hacktivity.datatemple.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
android:hint="#string/directory"
android:ems="10"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:id="#+id/dirEditText" />
<Button
android:text="→"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:id="#+id/dirButton"
android:onClick="populateDirList" />
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:id="#+id/dirListView" />
</LinearLayout>
</RelativeLayout>
And alas the MainActivity class:
public class MainActivity extends AppCompatActivity {
ListView dirListView;
EditText et;
DataTempleArrayAdapter adapter;
ArrayList<String> dirList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dirListView = (ListView) findViewById(R.id.dirListView);
et = (EditText) findViewById(R.id.dirEditText);
dirList = new ArrayList<String>();
dirListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getApplicationContext(),
"Click ListItem Number " + position, Toast.LENGTH_LONG)
.show();
populateDirList(view);
}
});
ArrayList<String> dirList = new ArrayList<String>();
adapter = new DataTempleArrayAdapter(this,
R.id.dir_text_view, dirList);
dirListView.setAdapter(adapter);
}
public void populateDirList (View view) {
File f;
// NO INPUT.
if (et.getText().toString().equals("")) {
Toast.makeText(this, "empty string", Toast.LENGTH_SHORT).show();
return;
}
f = new File(et.getText().toString());
if (f == null) { return; }
String dirContents[] = f.list();
if (dirContents == null) { return; }
dirList = new ArrayList<String>(Arrays.asList(f.list()));
adapter.clear();
int i;
for (i=0;i<dirContents.length;i++) {
dirList.add(dirContents[i]);
//Toast.makeText(this, dirList.get(i), Toast.LENGTH_SHORT).show();
}
adapter.notifyDataSetChanged();
}
}
One of the best resources for understanding ListView is indeed the
one you mentioned from Vogella
Another cool resource to understand how the the
notifyDataSetChanged() method works in ListView this post from StackOverflow
For a short, simple explanation of how to use CustomLayouts in
ListView (without the ViewHolder pattern) check another of the best
references available: Mkyong
Discussing the benefits of the ViewHolder pattern in ListView:
check this StackOverflow post
Concise example and explanation of the ViewHolder pattern in
ListView: check this example from JavaCodeGeeks
And to fix your code I think the answer given before is only part of the problem:
You must indeed comment the line
//ArrayList<String> dirList = new ArrayList<String>();
because, like #F43nd1r mentioned this would also be a different instance of a list passed into the adapter
but there is more, when you do this:
dirList = new ArrayList<String>(Arrays.asList(f.list()));
you are instantiating a new, different, list, the old reference held by the adapter will NOT be changed... it will still hold the OLD object list
you should perhaps substitute it for something like:
dirList.clear();
dirList.addAll(Arrays.asList(f.list()));
Hope this helps!
Excerpt from your code:
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
dirList = new ArrayList<String>();
//...
ArrayList<String> dirList = new ArrayList<String>();
adapter = new DataTempleArrayAdapter(this,
R.id.dir_text_view, dirList);
//...
}
I bet you already see what the problem is, but in case you don't: You have a field and a local variable with the same name. You pass the local variable to the adapter. It is only naturally that the adapter does not react to changes on the field, as it has no knowledge of its existence.
I think what you have done wrong is to supply a UI Component to the Array Adapter with:
adapter = new DataTempleArrayAdapter(this, R.id.dir_text_view, dirList);
The second item should not be an ID, but a layout file. Android have already implemented a List item layout with a textview that you can use: android.R.layout.simple_list_item_1.
so replace your row with
adapter = new DataTempleArrayAdapter(this, android.R.layout.simple_list_item_1, dirList);
and you are one step closer.
(This way you don't need your "xml layout with only a TextView item in it")
I try to make simple application with sliding images. I use this article http://www.androidbegin.com/tutorial/android-viewpager-gallery-images-and-texts-tutorial/.
This is my xml with viewpager:
main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<android.support.v4.view.ViewPager
android:id="#+id/pages"
android:layout_height="match_parent"
android:layout_width="match_parent">
</android.support.v4.view.ViewPager>
</LinearLayout>
this is my xml with image:
item.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="#+id/image"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="#drawable/ic_launcher" />
</RelativeLayout>
this is my adapter:
ViewerPagerAdapter.java
public class ViewerPagerAdapter extends PagerAdapter {
private Context mContext;
private ArrayList<CueModel> mArray;
private LayoutInflater mInflater;
private ImageView mImage;
public ViewerPagerAdapter(Context _context){
mContext = _context;
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setArray(ArrayList<Bitmap> arr){
mArray = arr;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
View itemView = mInflater.inflate(R.layout.item, container, false);
mImage = (ImageView)itemView.findViewById(R.id.image);
mImage.setImageBitmap(mArray.get(position));
container.addView(itemView);
return itemView;
}
#Override
public int getCount() {
return mArray.size();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0.equals(arg1);
}
this is my activity:
OneActivity.java
(mArrays full of image)
public class OneActivity extends Activity {
ArrayList<Bitmap> mArrays;
ViewerPagerAdapter mAdapter;
ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main.xml);
mPager = (ViewPager) findViewById(R.id.pages);
mAdapter = new ViewerPagerAdapter(OneActivity.this);
mAdapter.setArrays(mArrays);
mPager.setAdapter(mAdapter);
}
}
I don't understand what I did wrong. instantiateItem doesn't call.
PS.Difference between my app and
article I have a few activity(and my arraylist initialized by items)
Thank you
#instantiateItem() is called whenever the ViewPager needs a new View. Initially, it'll call #instantiateItem() twice for the View in the center and view to the right if the adapter's getCount() > 0. That's why in your #setArray() method you need to call #notifyDataSetChanged() every time you change the array. You also need to call it every time you add or remove items from the list. If your array is empty in #onCreate(), then #getCount() == 0 so the ViewPager thinks it's empty and won't bother.
I know this is very old post but I am answering this becoz someone would get benefited. I got the same issue - instantiateItem () was not getting called.
After trying so many thing I manage to finally found the mistake I did in my code and it was a silly one I forgot to set adapter to viewpager:
viewPager.setAdapter(adapter)
For modern versions (2020+) where the error message is
Binary XML file line #(lineNumber): Error inflating class android.viewpager.widget.ViewPager
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
you might want to update
android.support.v4.view.ViewPager to
androidx.viewpager.widget.ViewPager
In my case, the size of the list was zero. Therefore, this method was not calling. When the list was not empty, it worked properly.
I'm trying to create an element within a ListView which displays a few TextViews and a custom view in which I plan to draw to the canvas by overriding onDraw(). The method I'm attempting works fine with just the TextViews, but fails when I try and use the custom element (Graph), so the problem definitely lies here.
I have the following XML file which I use as the layout for each ListView item:
year_row.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/year_row_linear"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="6dp"
>
<TextView android:id="#+id/year_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20dip"
android:textColor="#android:color/white"
/>
<com.android.gradetracker.Graph android:id="#+id/year_graph"
android:layout_width="fill_parent"
android:layout_height="50dip"
/>
<TextView android:id="#+id/year_average"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#android:color/white"
/>
<TextView android:id="#+id/year_progress"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
My Graph class is as follows:
Graph.java:
package com.android.gradetracker;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
public class Graph extends View {
public Graph(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
// to test if it works
canvas.drawColor(Color.WHITE);
super.onDraw(canvas);
}
}
My main class which sets up the elements of the ListView then contains the following code:
private ArrayList <HashMap<String,Object>> list = new ArrayList <HashMap<String,Object>>();
private SimpleAdapter adapter;
private ListView listView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listView = (ListView) findViewById(R.id.listview);
listView.setClickable(true);
String[] from = {"name", "graph", "average", "progress"};
int[] to = new int[] {R.id.year_name, R.id.year_graph, R.id.year_average, R.id.year_progress};
adapter = new SimpleAdapter(this.getApplicationContext(), list, R.layout.year_row, from, to);
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
}
public void addYear(String name) {
Graph graph = new Graph(this);
HashMap<String,Object> map = new HashMap<String,Object>();
map.put("name", name);
map.put("graph", graph);
map.put("average", "--%");
map.put("progress", "--/--%");
list.add(map);
adapter.notifyDataSetChanged();
}
As I said, removing all references to the Graph class, the application works fine. I'm just having trouble getting Graph to display.
Logcat gives the error:
08-04 17:09:50.415: ERROR/AndroidRuntime(397): android.view.InflateException: Binary XML file line #16: Error inflating class com.android.gradetracker.Graph
Thanks for any help in advance.
Okay, the main problem seemed to be that SimpleAdaptor does not allow you to use custom views. I had to create a custom adaptor which extends BaseAdapter in order to display the custom view, with the following code for the getView() method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.year_row, parent, false);
}
TextView name = (TextView) convertView.findViewById(R.id.year_name);
Graph graph = (Graph) convertView.findViewById(R.id.year_graph);
TextView average = (TextView) convertView.findViewById(R.id.year_average);
TextView progress = (TextView) convertView.findViewById(R.id.year_progress);
ArrayList<Object> row = list.get(position);
name.setText(String.valueOf(row.get(0)));
graph = (Graph)row.get(1);
average.setText(String.valueOf(row.get(2)));
progress.setText(String.valueOf(row.get(3)));
return convertView;
}
You need a constructor in your Graph class.
Start By calling super
Change the onDraw function of the Graph class
#Override
protected void onDraw(Canvas canvas) {
//start with super
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
}
Below is a link to the bug I am experiencing with my Android application. Rather than trying to explain it via a huge wall of text, I figured a simple video would be much more direct and easier to understand.
http://www.youtube.com/watch?v=9V3v854894g
I've been beating my head against a wall on this problem for a day and a half now. I only found that it could be solved by changing the XML layout just recently which makes absolutely no sense to me. I have no idea how to properly fix it, or a way to band-aid the problem since I need the nested layouts in my application.
Thank you everyone for all your help!
Here is the code:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.AdapterView.OnItemSelectedListener;
public class Builder extends Activity {
private Spinner mCompSelect;
private Spinner mNameSelect;
private int[] mCompColorAsBuilt;
private int mComponent;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.builder);
mCompColorAsBuilt = new int[3];
//Attach our objects
mCompSelect = (Spinner) findViewById(R.id.component);
mNameSelect = (Spinner) findViewById(R.id.component_name);
//Attach an adapter to the top spinner
ArrayAdapter<CharSequence> a = ArrayAdapter.createFromResource(this, R.array.cc_components, android.R.layout.simple_spinner_item);
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mCompSelect.setAdapter(a);
//Create a listener when the top spinner is clicked
mCompSelect.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//Save the position
mComponent = position;
//Create a new adapter to attach to the bottom spinner based on the position of the top spinner
int resourceId = Builder.this.getResources().getIdentifier("component"+Integer.toString(mComponent)+"_color", "array", Builder.this.getPackageName());
ArrayAdapter<CharSequence> a = ArrayAdapter.createFromResource(Builder.this, resourceId, android.R.layout.simple_spinner_item);
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mNameSelect.setAdapter(a);
//Set the position of the bottom spinner to the saved position
mNameSelect.setSelection(mCompColorAsBuilt[mComponent]);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
//Attach an adapter to the bottom spinner
int resourceId = this.getResources().getIdentifier("component"+Integer.toString(mComponent)+"_color", "array", this.getPackageName());
ArrayAdapter<CharSequence> b = ArrayAdapter.createFromResource(this, resourceId, android.R.layout.simple_spinner_item);
b.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mNameSelect.setAdapter(b);
mNameSelect.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//Save the position of the bottom spinner
mCompColorAsBuilt[mComponent] = position;
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
}
XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<Spinner
android:id="#+id/component"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/finish"
android:drawSelectorOnTop="true"
android:prompt="#string/component_spinner" />
<LinearLayout
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<Spinner
android:id="#+id/component_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawSelectorOnTop="true"
android:prompt="#string/component_name_spinner" />
</LinearLayout>
</RelativeLayout>
As a hack, try calling invalidate() on the affected Spinner. First, try after you call setSelection(). If that fails, try using postDelayed() on the Spinner to call invalidate() a bit later (e.g., 50ms).
In addition, I encourage you to create a demonstration project with two activities (or maybe just the one activity with two layouts) that illustrates this behavior, and post it and an explanation to http://b.android.com.