I want to create a gridview like android market, I want to populate this with images from a database on internet. it need to work with androidv4.support, since I want to run 2.2 until 4.0.
Someone said, that isnt possible to create a gridview in pre-4.0, is it true?
However It only works when I put use setListAdapter(), but it shows only one image per line, like a listview, when I change to gridview.setAdapter(), it doenst work anymore.
Here is my try:
This is the ListFragment class:
public static class ArrayListFragment extends ListFragment implements OnScrollListener{
ImageAdapter adapter = new ImageAdapter();
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LayoutInflater gridInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = gridInflater.inflate(R.layout.imagelist, null);
GridView gridView = (GridView) v.findViewById(R.id.list);
ImageDownloader.Mode mode = ImageDownloader.Mode.CORRECT;
// ImageAdapter imageAdapter = new ImageAdapter();
adapter.getImageDownloader().setMode(mode);
setListAdapter(adapter);
// gridView.setAdapter(adapter);
getListView().setOnScrollListener(this);
}
This is ImageAdapter class:
public class ImageAdapter extends BaseAdapter {
private Context mContext;
private final ImageDownloader imageDownloader = new ImageDownloader();
public static int count = 10;
private final String[] URLS = {
"http://lh5.ggpht.com/_mrb7w4gF8Ds/TCpetKSqM1I/AAAAAAAAD2c/Qef6Gsqf12Y/s144-c/_DSC4374%20copy.jpg",
"http://lh5.ggpht.com/_Z6tbBnE-swM/TB0CryLkiLI/AAAAAAAAVSo/n6B78hsDUz4/s144-c/_DSC3454.jpg",
"http://lh3.ggpht.com/_GEnSvSHk4iE/TDSfmyCfn0I/AAAAAAAAF8Y/cqmhEoxbwys/s144-c/_MG_3675.jpg",
"http://lh6.ggpht.com/_Nsxc889y6hY/TBp7jfx-cgI/AAAAAAAAHAg/Rr7jX44r2Gc/s144-c/IMGP9775a.jpg",
"http://lh3.ggpht.com/_lLj6go_T1CQ/TCD8PW09KBI/AAAAAAAAQdc/AqmOJ7eg5ig/s144-c/Juvenile%20Gannet%20despute.jpg",
};
public int getCount() {
return count;
}
public String getItem(int position) {
return URLS[position];
}
public long getItemId(int position) {
return URLS[position].hashCode();
}
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) {
v = LayoutInflater.from(mContext).inflate(R.layout.image_text_view,null);
v.setLayoutParams(new GridView.LayoutParams(200,200));
ImageView imageview = (ImageView)v.findViewById(R.id.image);
imageview.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageview.setPadding(6, 6, 6, 6);
imageDownloader.download(URLS[position], imageview);
}
else {
v = convertView;
}
return v;
}
public ImageDownloader getImageDownloader() {
return imageDownloader;
}
}
It could help a lot if anyone have a sample. Thanks
This should work fine, you just can't use a gridView in a ListFragment - just use a plain old Fragment instead, if you're going to be manually managing the grid anyway
Also, the point of checking if convertView is null is to do view recycling - the OS only declares enough views to fill the screen and no more, so if you scroll then it can reuse the view instead of having to inflate a new one. Change up your getView() like so to take advantage:
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) {
v = LayoutInflater.from(mContext).inflate(R.layout.image_text_view,null);
v.setLayoutParams(new GridView.LayoutParams(200,200));
}
else {
v = convertView;
}
ImageView imageview = (ImageView)v.findViewById(R.id.image);
imageview.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageview.setPadding(6, 6, 6, 6);
imageDownloader.download(URLS[position], imageview);
return v;
}
Also, getView isn't a function of BaseAdapter, try switching to ArrayAdapter instead. As a side note, always use #Override when you think you're overriding a base function - that way the compiler will give you an error if you make a mistake
Using a GridView in a Fragment shouldn't be any different than using it in an Activity. One glaring error I see with your code is that you are inflating a layout in onActivityCreated and then promptly ignore it. Instead you should do all of your view initialization in onCreateView which conveniently provides a LayoutInflater for your use.
As for its current behavior, it makes a lot of sense why it's acting how it is. I believe that ListFragment inflates a layout that contains a ListView if the programmer doesn't provide one (which you currently are not). The ImageAdapter you are setting is then used to provide the Views to the ListView.
So move all of your code that is in onActivityCreated to onCreateView and it should work. You shouldn't need to override onActivityCreated at all unless you need to do something with special with the Activity when your Fragment is attached to it.
And as for using GridView pre 4.0 - GridView has been around since API level 1 so I'd bet that it's fine to use it for all Android API levels.
Related
I have a listview that is populated via an adapter. I need one of the items (which are just bits of text) in the listview to have a different background compared to the others (to mark it as the currently selected one).
I have all of the background logic, I just need a way to say listview.setBackgroundById(int position)
or something like that.
How do I do this?
This needs to be as simple as possible, 'cause all of the other things done in the Activity are currently working perfectly. :)
As asked, this is my Adapter that I'm using:
public class SampleAdapter extends ArrayAdapter<SampleItem> {
private String title;
public SampleAdapter(Context context) {
super(context, 0);
}
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_station, null);
}
TextView title = (TextView)convertView.findViewById(R.id.station_name);
font.setFont(title, 2, getActivity());
title.setText(getItem(position).title);
RelativeLayout info = (RelativeLayout)convertView.findViewById(R.id.info_relative_button);
info.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
MainActivity.setCurrentTab(41);
MainActivity.setBackDisabled(true);
Log.e("current tab:",String.valueOf(MainActivity.getCurrentTab()));
Fragment fragment = new StationInfo();
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
UserManager.getInstance().setStationId(getItem(position).id);
}
});
return convertView;
}
}
The SampleItem has 2 String fields, title and id, it's very simple.
You need to use a custom list adapter and have it return views with your desired background. Create a class extending ListAdapter or any of the existing SimpleAdapter etc and override getView to inflate a suitable view for your element, and add any logic you need to set the background of that view.
There is no way to tell the listview itself to decorate some of its elements by id or position.
Update: I just noticed you added the list adapter code.
Since you are already implementing getView, to change the background of your element simply call convertView.setBackgroundColor, or have two different views inflated depending on the situation.
(BTW it's really bad practice to call static methods on your activity like in your onClickListener.)
In ListView adapter:
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
if(view==null)
....
//for example every even list item to be grey, every odd to be white
if(((position+1)%2)==0)
view.setBackgroundColor(mContext.getResources().getColor(R.color.grey));
else view.setBackgroundColor(mContext.getResources().getColor(android.R.color.white));
Hope you get an idea...
I have a weird situation with a custom ArrayAdapter.
When I try to update the adpater with new data, instead of the data being updated, the new data are inserted to the beginning of the listview and the old data are remaining and visible once you scroll the listview.
UPDATE
It seems that the problem is caused by the ArrayList from the fragment bundle.
If I don't set the listview in the onCreateView from the fragment bundle, my update code works fine, but now I'm puzzled why this:
ArrayList<Collection> cityStoresList = fragmentBundle.getParcelableArrayList("stores");
mStoresList.addAll(cityStoresList);
is causing the items to always remain on the list?
END OF UPDATE
Here are parts of the code: (Collection is a custom object model class)
ArrayList<Collection> mStoresList = new ArrayList<Collection>();
/** List Adapter */
private StoresListAdapter mListAdapter;
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
boolean attach = false;
if (container == null) {
attach = true;
}
Bundle fragmentBundle = getArguments();
ArrayList<Collection> cityStoresList = fragmentBundle.getParcelableArrayList("stores");
mStoresList.addAll(cityStoresList);
//inflater code not added here, but is present
mListAdapter = new StoresListAdapter(getActivity(), mStoresList);
mListView.setAdapter(mListAdapter);
return layout;
}
My custom adapter is as follows:
public class StoresListAdapter extends ArrayAdapter<Collection> {
public StoresListAdapter(Context c, ArrayList<Collection> array) {
super(c, 0, array);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// View from recycle
View row = convertView;
// Handle inflation
if (row == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.row_store, null);
}
// Get the Store
Collection store = getItem(position);
//rest of code follows
return row;
}
}
Now when I want to update my adapter I use the following:
public void updateAdapter(ArrayList<Collection> storesList, final int listIndex) {
mStoresList.clear();
mStoresList.addAll(storesList);
mListAdapter.notifyDataSetChanged();
}
And this creates the issue I mentioned. The new items appear fine, but the previous ones are still visible and added after the new ones.
It's like adding the new items in the ArrayList as the first items, instead of just replacing the old ones.
Any ideas, suggestions?
Ok, finally found the problem.
Because the whole thing is within a fragment, the oncreateView is actually called when I'm attaching the array, so what happens is that my updateAdapter method is called, the items are added and displayed, before the view is actually visible.
Then the oncreateView method is fired and the original bundle items are being added to the Arraylist....
I try to develop an Android App which allows the user to fetch data from flickr and show it in a GridView (with some nice 3D-Animation). After some adventures i got it almost running, but now I'm stuck.
Here's the problem:
I got a UI Thread "LoadPhotosTask" which gets the pictures from flickr, just like the open source application photostream. In the method onProgressUpdate(LoadedPhoto... value) of that subclass I call addPhoto(). Until now everythings fine - I got some nice Bitmap and Flickr.photo data with all the information I need.
#Override
public void onProgressUpdate(LoadedPhoto... value) {
addPhoto(value);
}
On the other hand I have got a GridView. Now I want to fill it with the Photos. It has got an adapter called ImageAdapter (which extends BaseAdapter, see this tutorial). If I use an array inside the ImageAdapter class I can populate the GridView with some sample images. But if I want to populate it at runtime, I don't know what to do.
How do I have to set up the getView method in the ImageAdapter? I was trying to fill the array inside the ImageAdapter class with my values in addPhoto, but it doesn't display anything.
So first of all I was setting up the array with the amount of Photos i wanted to display in the grid like that (code is inside the ImageAdapter class):
// class variable
private ImageView[] mThumbIds;
[...]
public void setupArray(int count) {
this.mThumbIds = new ImageView[count];
}
Then I call this method with the lenght of my photolist:
final Flickr.PhotoList list = params[0];
final int count = list.getCount();
int helper = 0;
imagead.setupArray(count);
Afterwards I call the getView method manually inside the addPhoto method:
private void addPhoto(LoadedPhoto... value) {
ImageView image = (ImageView) mInflater.inflate(
R.layout.grid_item_photo, null);
image.setImageBitmap(value[0].mBitmap);
image.setTag(value[0].mPhoto);
imagead.setmThumbIds(image, value[0].mPosition);
imagead.getView(value[0].mPosition, null, mpicturesGrid);
}
That is the getView method inside ImageAdapter:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { // if it's not recycled, initialize some
// attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(EDGE_LENGTH,
EDGE_LENGTH));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(0, 0, 0, 0);
imageView.setVisibility(View.VISIBLE);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageDrawable(mThumbIds[position].getDrawable());
imageView.setTag(mThumbIds[position].getTag());
return imageView;
}
You are missing a key part.
When you use an Adapter you have a method called notifyDataSetChanged().
The logic you are missing there is the following:
When creating the Adapter for the GridView stay with a reference for the list that the adapter will use. Something like:
private ArrayList<Photo> mPhotos;
private BaseAdapter mAdapter;
private GridView mGridView;
onCreate:
/* other things here */
mAdapter = new MyAdapter(mPhotos);
mGridView.setAdapter(mAdapter);
What you addPhoto should do is the following:
mPhotos.add(photo);
mAdapter.notifyDataSetChanged();
That's it.
I'm looking into writing a custom adapter to populate a listview with 3 textviews per line. I've found quite a bit of example code to do this, but the one that seemed the best was at: http://www.anddev.org/custom_widget_adapters-t1796.html
After a few minor tweaks to fix some compiler issues with the latest Android SDK, I got it running, only to get the exception:
ERROR/AndroidRuntime(281): java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView
So I did a lot of research and found lots of possible reasons and fixes for this. None of which changed a thing. My adapter code is currently:
public class WeatherAdapter extends BaseAdapter {
private Context context;
private List<Weather> weatherList;
public WeatherAdapter(Context context, int rowResID,
List<Weather> weatherList ) {
this.context = context;
this.weatherList = weatherList;
}
public int getCount() {
return weatherList.size();
}
public Object getItem(int position) {
return weatherList.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Weather weather = weatherList.get(position);
//LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.weather_row, null, true);
TextView cityControl = (TextView)v.findViewById( R.id.city );
TextView temperatureControl = (TextView)v.findViewById( R.id.temperature );
ImageView skyControl = (ImageView)v.findViewById( R.id.sky );
return v;
}
}
So I have tried the commented out way of getting the inflater, and the currently uncommented out. I have tried passing "parent" to inflate as well as null, and passing "true", "false" and omitting completely the last parameter. None of them have worked, and all examples I've found so far have been from 2008 which I get the feeling are a bit outdated.
If anyone could help with this then I would love to resolve the issue.
I believe this line is at fault:
View v = inflater.inflate(R.layout.weather_row, null, true);
You need instead:
View v = inflater.inflate(R.layout.weather_row, parent, false);
The false makes the inflated view independent of the parent, not attached to it, which very oddly seems to be the accepted design for custom views within AdapterViews. Why this is so, I find utterly baffling, but the pattern above worked for me.
I'm a beginner also so take this answer with a pinch of salt - if it doesn't work, move on and Google some more. Not familiar with AdapterView since I traditionally have a ListView or GridView and a custom Adapter extended off a BaseAdapter and then listView.setAdapter(myCustomAdapter).
You could try making something like this inside the WeatherAdapter class:
public void addToList(Weather mWeather) {
weatherList.add(mWeather);
}
Then in the class that calls WeatherAdapter:
weatherAdapter.addToList(weatherToAdd);
weatherAdapter.notifyDataSetChanged();
Also you need to optimize it more in the getView method:
http://www.youtube.com/watch?v=wDBM6wVEO70
For the AdaptertView addView method:
void addView(View child)
This method is not supported and
throws an
UnsupportedOperationException when
called." (From Android documentation)
Probably the inflating procedure calls the addView method and this is not possible from an AdapterView, or its AdapterView.
From the documentation:
"An Adapter object acts as a bridge
between an AdapterView and the
underlying data for that view. The
Adapter provides access to the data
items. The Adapter is also responsible
for making a View for each item in the
data set".
I think that the inflating operation could be done from a simple Activity that models your view and all other operations, for example, retrieving data and showing data in other classes.
Hope it will be helpful!
#Axel22's answer is key, but there are a few other things missing from your code. First, you should be extending either BaseAdapter or ArrayAdapter, depending on your preference. Second, you want to get in the practice of using a ViewHolder to avoid making excessive calls to findViewById, and (most importantly) recycling your View.
private Class ViewHolder {
public TextView cityControl;
public TextView temperatureControl;
public ImageView skyControl;
public ViewHolder(TextView cityControl, TextView temperatureControl, ImageView skyControl) {
this.cityControl = cityControl;
this.temperatureControl = temperatureControl;
this.skyControl = skyControl;
}
Your getView function can recycle views and utilize the ViewHolder class as follows:
public View getView(int position, View convertView, ViewGroup parent) {
Weather weather = weatherList.get(position);
// This is how you attempt to recycle the convertView, so you aren't
// needlessly inflating layouts.
View v = convertView;
ViewHolder holder;
if (null == v) {
v = LayoutInflater.from(getContext()).inflate(R.layout.weather_row, parent, false);
TextView cityControl = (TextView)v.findViewById( R.id.city );
TextView temperatureControl = (TextView)v.findViewById( R.id.temperature );
ImageView skyControl = (ImageView)v.findViewById( R.id.sky );
holder = new ViewHolder(cityControl, temperatureControl, skyControl);
v.setTag(holder);
} else {
holder = (ViewHolder) v.getTag();
}
holder.cityControl.setText("Metropolis");
holder.temperatureControl.setText("78");
holder.skyControl.setImageResource(R.drawable.daily_planet);
return v;
}
For tons more examples (and other optimization tips), see this blog post (or just google for ViewHolder).
I'm having problems with some BaseAdapter code that I adapted from a book. I've been using variations of this code all over the place in my application, but only just realized when scrolling a long list the items in the ListView become jumbled and not all of the elements are displayed.
It's very hard to describe the exact behavior, but it's easy to see if you take a sorted list of 50 items and start scrolling up and down.
class ContactAdapter extends BaseAdapter {
ArrayList<Contact> mContacts;
public ContactAdapter(ArrayList<Contact> contacts) {
mContacts = contacts;
}
#Override
public int getCount() {
return mContacts.size();
}
#Override
public Object getItem(int position) {
return mContacts.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if(convertView == null){
LayoutInflater li = getLayoutInflater();
view = li.inflate(R.layout.groups_item, null);
TextView label = (TextView)view.findViewById(R.id.groups_item_title);
label.setText(mContacts.get(position).getName());
label = (TextView)view.findViewById(R.id.groups_item_subtitle);
label.setText(mContacts.get(position).getNumber());
}
else
{
view = convertView;
}
return view;
}
}
You are only putting data in the TextView widgets when they are first created. You need to move these four lines:
TextView label = (TextView)view.findViewById(R.id.groups_item_title);
label.setText(mContacts.get(position).getName());
label = (TextView)view.findViewById(R.id.groups_item_subtitle);
label.setText(mContacts.get(position).getNumber());
to be after the if/else block and before the method return, so you update the TextView widgets whether you are recycling the row or creating a fresh one.
To further clarify the answer of CommonsWare, here is some more info:
The li.inflate operation (needed here for parsing of the layout of a row from XML and creating the appropriate View object) is wrapped by an if (convertView == null) statement for efficiency, so the inflation of the same object will not happen again and again every time it pops into view.
HOWEVER, the other parts of the getView method are used to set other parameters and therefore should NOT be included within the if (convertView == null){ }... else{ } statement.
In many common implementation of this method, some textView label, ImageView or ImageButton elements need to be populated by values from the list[position], using findViewById and after that .setText or .setImageBitmap operations.
These operations must come after both creating a view from scratch by inflation and getting an existing view if not null (e.g. on a refresh).
Another good example where this solution is applied for a ListView ArrayAdapter appears in https://stackoverflow.com/a/3874639/978329