I am creating a ListView using Holder
when I scroll slowly everything works perfectly fine and Endless ListView works great but when I open the page and scroll fast to end of the List ListViewfail to scroll further without any error at Logcat.Here is my Adapter class getView method
#Override
public View getView(final int i, View view, ViewGroup viewGroup) {
View row=view;
Holder holder=null;
if(row==null){
LayoutInflater inflater= (LayoutInflater)mContext.getSystemService(mContext.LAYOUT_INFLATER_SERVICE);
row= inflater.inflate(R.layout.half_movie,viewGroup,false);
holder=new Holder(row);
row.setTag(holder);
}else{
holder= (Holder) row.getTag();
}
//here setting all holers
Picasso.with(mContext).load("https://image.tmdb.org/t/p/w185"+temp.getImageUrl()).into(holder.poster);
if(reachedEndOfList(i)) loadMoreData();
return row;
}
private boolean reachedEndOfList(int position) {
// can check if close or exactly at the end
return position == list.size() - 4;
}
private void loadMoreData() {
if(isNetworkAvailable()) {
new MyAdapter.AsyncTaskParseJson().execute(url);
}else{
Toast.makeText(mContext, "Sorry No internet Connection Found!", Toast.LENGTH_SHORT).show();
}
}
public class AsyncTaskParseJson extends AsyncTask<String,String ,void> {
JSONArray dataJsonArr = null;
ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(mContext,
"Movie Pal!",
"Loading Movies List..");
}
#Override
protected void doInBackground(String... arg0) {
String yourJsonStringUrl = arg0[0];
yourJsonStringUrl=yourJsonStringUrl+"&page="+pageNmber;
JSONParser jParser = new JSONParser();
JSONObject json = jParser.getJSONFromUrl(yourJsonStringUrl.replace(" ","%20"));
System.out.println(json);
try {
JSONArray jsonArray = json.getJSONArray("results");
for (int i=0; i<jsonArray.length(); i++) {
Movie newMovie = new Movie();
//setting to class object
list.add(newMovie);
}
pageNmber++;} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
#Override
protected void onPostExecute() {
progressDialog.dismiss();
}
}
I don't know what i am missing here please help!
You need to call notifyDataSetChanged (or similar function like notifyDataSetInsert) when the new data is loaded so it knows that more data exists and it should allow more scrolling.
Also, you may not be adding enough padding (just 4 items) between the end of the list and when you fetch more data. That may result in stuttering scrolling- you'll reach the end and stop until more data is downloaded, then you can scroll again. As an example- I start fetching new data 10 items before the end, and I'm just hitting a local db, not a remote server.
Related
i have parsed Json data from the server. On which im showing all the data in listview and i have Load more option below the ListView. Now when i click load more option, this application reload whole list and did not show previous list data. Please help me find out the solution. Here is footer view click listener :
lFooter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
page += 1;
new ParseIssues().execute();
listView.removeFooterView(v);
}
});
in above code ParseIssues class parse json values and displays all the data in ListView Here is code for onPostExecute of AsynkTask class :
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if(page < totalPage){
listView.addFooterView(v);
}
listAdapter = new ListAdapterForSearch(activity, mainList);
listView.setAdapter(listAdapter);
// get listview current position - used to maintain scroll position
int currentPosition = listView.getFirstVisiblePosition();
listView.setSelection(currentPosition);
}
Here is BaseAdapter class:
public class ListAdapterForSearch extends BaseAdapter {
private Activity activity;
private ArrayList<HashMap<String, String>> data;
private static LayoutInflater inflater = null;
public ListAdapterForSearch(Activity a, ArrayList<HashMap<String, String>> d) {
activity = a;
data = d;
inflater = LayoutInflater.from(activity);
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object getItem(int position) {
return data.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
View vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.list_row_item, null);
}
TextView title = (TextView)vi.findViewById(R.id.title);
HashMap<String, String> hash = new HashMap<String, String>();
hash = data.get(position);
title.setText(hash.get("title"));
return vi;
}
}
The answer to your question lies with the variable "mainList", which the adapter uses. You need to add the new data to it, basically this list needs to contain all the data you want to show in your ListView.
You should set adapter on create, after that first add loaded data to mainlist(add new loaded data) & then after loading notify adapter like listadapter.notifydatachange()
Edit: Because every time you are creating adapter that's why you are facing this problem, instead after loading just notify your adapter..
Check in your code you may have clear your mainList.
I wonder if there is any way to update an information of a single item within a listview. Basically I press the button inside the adapter and it makes a new request, the request will set this returns the value of the adapter. It is a system of "like".
I do not want to call the asynchronous method that gets the list, it takes much again. The code to get all the items in the database is this:
protected ArrayList<Feed> doInBackground(MyTaskParams... params) {
page = params[0].page;
mFilter = params[0].filter;
backgroundItems = new ArrayList<Feed>();
ParseQuery<ParseObject> query = ParseQuery.getQuery("FeedPost");
ParseObject parseObject;
try {
responseList = query.find();
for (int i = 0; i < responseList.size(); i++) {
parseObject = responseList.get(i);
backgroundItems.add(new Feed(parseObject.getObjectId(),
parseObject.getString("Title"),
parseObject.getString("Description"),
parseObject.getString("CompleteText"),
parseObject.getString("imageURL"),
parseObject.getString("Link_on_Site"),
parseObject.getNumber("like_count")));
}
} catch (ParseException e) {
exceptionToBeThrown = e;
}
return backgroundItems;
}
You didn't show your adapter code or your task for updating the like count in the database, so I'm going to assume you're using an AsyncTask. In the getView or bindView function is where you need to update it.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
if (convertView == null)
{
//inflate layout and initialize holder
}
else
{
holder = (ViewHolder)convertView.getTag();
}
//get item at position
MyItem item = getItem(position);
if (item != null)
{
holder.likeButton.setOnClickListener(
new OnClickListener()
{
#Override
public void onClick(View v)
{
//update database and set new value
IncrementLikeCountTask task = new IncrementLikeCountTask()
{
#Override
public void onPostExecute(int newValue)
{
item.setLikeCount(newValue);
notifyDataSetChanged();
}
}.execute(position);
}
}
);
}
return convertView;
}
So I assume you're persisting a list of Feed objects in your adapter.
What you need to do is after communicating with the server modify the like_count field of the appropriate Feed object in your adapter and call notifyDataSetChanged() on the adapter.
This will trigger a refresh of the ListView and render your list item with the updated value.
I have an app that loads the traffic report from a server and shows it in a timeline (like twitter), the app is configured to load the data from the server every 10 seconds using this code:
#Override
public void onResume() {
super.onResume();
autoUpdate = new Timer();
autoUpdate.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
new DownloadJSON2().execute(); // this is the class that downloads the data from the server.
}
});
}
}, 0, 10000); // updates each 10 secs
}
#Override
public void onPause() {
autoUpdate.cancel();
super.onPause();
}
The problem is that if I scroll down the list and I'm reading the older posts, the list refreshes and send me to the top of it. I want to download that data like ajax and append it to the top. Or just to tell me that is X new reports and a button to show it.
How can I acomplish this?
BTW I'm extremely new to android programming.
Thanks in advance.
EDIT: Thanks to Vasily Sochinsky i've added the following:
public class DownloadJSON extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
// Create a progressdialog
mProgressDialog = new ProgressDialog(MainActivity.this);
// Set progressdialog title
mProgressDialog.setTitle("Cargando reportes en tiempo real");
// Set progressdialog message
mProgressDialog.setMessage("Por favor espere");
mProgressDialog.setIndeterminate(false);
// Show progressdialog
mProgressDialog.show();
}
#Override
protected Void doInBackground(Void... params) {
// Create an array
arraylist = new ArrayList<HashMap<String, String>>();
// Retrieve JSON Objects from the given URL address
jsonobject = JSONfunctions
.getJSONfromURL("http://server.com/timeline.php");
try {
// Locate the array name in JSON
jsonarray = jsonobject.getJSONArray("datos");
for (int i = 0; i < jsonarray.length(); i++) {
HashMap<String, String> map = new HashMap<String, String>();
jsonobject = jsonarray.getJSONObject(i);
// Retrive JSON Objects
// jsonobject1 = jsonobject.getJSONObject("contenido");
map.put("imagen", jsonobject.getString("imagen"));
map.put("quien", jsonobject.getString("quien"));
map.put("fecha", jsonobject.getString("fecha"));
map.put("reporte", jsonobject.getString("reporte"));
// map.put("imgs", jsonobject1.getString("imgs"));
// map.put("video", jsonobject1.getString("video"));
// Set the JSON Objects into the array
arraylist.add(map);
}
} catch (JSONException e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void args) {
// Locate the listview in listview_main.xml
listview = (ListView) findViewById(R.id.listview);
// Pass the results into ListViewAdapter.java
adapter = new ListViewAdapter(MainActivity.this, arraylist);
// Set the adapter to the ListView
listview.setAdapter(adapter);
adapter.notifyDataSetChanged();
// Close the progressdialog
mProgressDialog.dismiss();
}
}
But the listview is not updating with the new data, how can i achieve this?
EDIT 2: Thanks to cogentapps that gave me a solution, but i still can not add it to the code, where should i add it?
I've tried this but eclipse shows many errors:
protected void onPostExecute(Void args) {
// Locate the listview in listview_main.xml
listview = (ListView) findViewById(R.id.listview);
// Pass the results into ListViewAdapter.java
adapter = new ListViewAdapter(MainActivity.this, arraylist);
// Set the adapter to the ListView
adapter.addAll();
listview.notifyDataSetChanged();
// Close the progressdialog
mProgressDialog.dismiss();
}
This is the code from my ListViewAdapter, where should i add the adapter.add()?
public class ListViewAdapter extends BaseAdapter {
// Declare Variables
Context context;
LayoutInflater inflater;
ArrayList<HashMap<String, String>> data;
ImageLoader imageLoader;
HashMap<String, String> resultp = new HashMap<String, String>();
String coment;
public String img;
public int imga;
public ListViewAdapter(Context context,
ArrayList<HashMap<String, String>> arraylist) {
this.context = context;
data = arraylist;
imageLoader = new ImageLoader(context);
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
public View getView(final int position, View convertView, ViewGroup parent) {
// Declare Variables
TextView quien;
ImageView imagen;
TextView reporte;
TextView fecha;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.listview_item, parent, false);
// Get the position
resultp = data.get(position);
// Locate the TextViews in listview_item.xml
// Locate the ImageView in listview_item.xml
imagen = (ImageView) itemView.findViewById(R.id.imagen);
fecha = (TextView) itemView.findViewById(R.id.fecha);
quien = (TextView) itemView.findViewById(R.id.quien);
reporte = (TextView) itemView.findViewById(R.id.reporte);
// Capture position and set results to the TextViews
// Capture position and set results to the ImageView
// Passes flag images URL into ImageLoader.class
imageLoader.DisplayImage(resultp.get(MainActivity.IMAGEN), imagen);
fecha.setText(resultp.get(MainActivity.FECHA));
reporte.setText(resultp.get(MainActivity.REPORTE));
quien.setText(resultp.get(MainActivity.QUIEN));
// Capture ListView item click
/*
itemView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// Get the position
resultp = data.get(position);
Intent intent = new Intent(context, SingleItemView.class);
// Pass all data rank
intent.putExtra("imagen", resultp.get(MainActivity.IMAGEN));
intent.putExtra("quien", resultp.get(MainActivity.QUIEN));
intent.putExtra("fecha", resultp.get(MainActivity.FECHA));
// Start SingleItemView Class
context.startActivity(intent);
}
});
*/
return itemView;
}
}
Take a look at the answer to this question for an explanation / example of how to maintain scroll position after adding items to a list: Retaining position in ListView after calling notifyDataSetChanged
Basically, you make a note of the position and scroll offset of the first visible item in the list before adding new data. Then afterwards, you restore the previous position and scroll offset.
However, because you're adding new items at the top of the list, the position returned by getFirstVisiblePosition() may refer to a different item after refreshing. The item that was at position 0 before refreshing may now be position 10. To fix that, you need to determine the number of new items returned by timeline.php and add it to index, like this:
mList.setSelectionFromTop(index + newItemCount, top);
You could probably determine which items are new by comparing the date field.
(By the way, if timeline.php only returns the most recent set of items, you could run into trouble if the user has scrolled to an older item which is no longer returned by timeline.php. After setting the new data, the old item will no longer be present, so you won't be able to scroll to it.
To fix that, you could keep the old ArrayList around and only add new items to it in doInBackground(). And call notifyDataSetChanged() in doInBackground(). That way, the adapter will still have access to older items.)
In my application I need to download a lot of pictures from urls and display them in a gridView. (It can be between 1-200 pictures). I don't want to download all pictures at once. I read about lazy downloading and my question is: Can i get only one part of the Json, download the pictures in a different thread, and only if the user scroll down the gridView, I will continue to the other parts of the Json, and so on?
Edit: Hi again. I want to implement multi select in this gridView and i'm having difficulty to implement the code in the getView() method of the adapter. This is the example i'm using:example. How can I combine this code in my getView() method:
public View getView(int position, View convertView, ViewGroup parent) {
CheckableLayout l;
ImageView i;
if (convertView == null) {
i = new ImageView(Grid3.this);
i.setScaleType(ImageView.ScaleType.FIT_CENTER);
i.setLayoutParams(new ViewGroup.LayoutParams(50, 50));
l = new CheckableLayout(Grid3.this);
l.setLayoutParams(new GridView.LayoutParams(GridView.LayoutParams.WRAP_CONTENT,
GridView.LayoutParams.WRAP_CONTENT));
l.addView(i);
} else {
l = (CheckableLayout) convertView;
i = (ImageView) l.getChildAt(0);
}
ResolveInfo info = mApps.get(position);
i.setImageDrawable(info.activityInfo.loadIcon(getPackageManager()));
return l;
}
public class CheckableLayout extends FrameLayout implements Checkable {
private boolean mChecked;
public CheckableLayout(Context context) {
super(context);
}
public void setChecked(boolean checked) {
mChecked = checked;
setBackgroundDrawable(checked ?
getResources().getDrawable(R.drawable.blue)
: null);
}
public boolean isChecked() {
return mChecked;
}
public void toggle() {
setChecked(!mChecked);
}
}
my getView() code:
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
View vi = convertView;
if(convertView == null) {
vi = inflater.inflate(com.egedsoft.instaprint.R.layout.item_clickable, null);
holder = new ViewHolder();
holder.imgPhoto = (ImageView)vi.findViewById(com.egedsoft.instaprint.R.id.imageClickable);
vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}
if (!arrayUrls.get(position).getThumbnailUrl().isEmpty()){
imageLoader.DisplayImage(arrayUrls.get(position).getThumbnailUrl(), holder.imgPhoto);
}
return vi;
}
This is how I fetch multiple photos in my activity. You can use parts of it for fit your logic. I use this to fetch Facebook Images from an Album. So my needs are (I am assuming) different from your needs. But again, the logic may be of use to you.
Note: This will be lengthy. ;-)
These are the global declarations for use through the ACtivity:
// HOLD THE URL TO MAKE THE API CALL TO
private String URL;
// STORE THE PAGING URL
private String pagingURL;
// FLAG FOR CURRENT PAGE
int current_page = 1;
// BOOLEAN TO CHECK IF NEW FEEDS ARE LOADING
Boolean loadingMore = true;
Boolean stopLoadingData = false;
This is the code block that fetches the initial set of Images:
private class getPhotosData extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... arg0) {
// CHANGE THE LOADING MORE STATUS TO PREVENT DUPLICATE CALLS FOR
// MORE DATA WHILE LOADING A BATCH
loadingMore = true;
// SET THE INITIAL URL TO GET THE FIRST LOT OF ALBUMS
URL = "https://graph.facebook.com/" + initialAlbumID
+ "/photos&access_token="
+ Utility.mFacebook.getAccessToken() + "?limit=10";
try {
HttpClient hc = new DefaultHttpClient();
HttpGet get = new HttpGet(URL);
HttpResponse rp = hc.execute(get);
if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String queryAlbums = EntityUtils.toString(rp.getEntity());
JSONObject JOTemp = new JSONObject(queryAlbums);
JSONArray JAPhotos = JOTemp.getJSONArray("data");
// IN MY CODE, I GET THE NEXT PAGE LINK HERE
getPhotos photos;
for (int i = 0; i < JAPhotos.length(); i++) {
JSONObject JOPhotos = JAPhotos.getJSONObject(i);
// Log.e("INDIVIDUAL ALBUMS", JOPhotos.toString());
if (JOPhotos.has("link")) {
photos = new getPhotos();
// GET THE ALBUM ID
if (JOPhotos.has("id")) {
photos.setPhotoID(JOPhotos.getString("id"));
} else {
photos.setPhotoID(null);
}
// GET THE ALBUM NAME
if (JOPhotos.has("name")) {
photos.setPhotoName(JOPhotos.getString("name"));
} else {
photos.setPhotoName(null);
}
// GET THE ALBUM COVER PHOTO
if (JOPhotos.has("picture")) {
photos.setPhotoPicture(JOPhotos
.getString("picture"));
} else {
photos.setPhotoPicture(null);
}
// GET THE PHOTO'S SOURCE
if (JOPhotos.has("source")) {
photos.setPhotoSource(JOPhotos
.getString("source"));
} else {
photos.setPhotoSource(null);
}
arrPhotos.add(photos);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
// SET THE ADAPTER TO THE GRIDVIEW
gridOfPhotos.setAdapter(adapter);
// CHANGE THE LOADING MORE STATUS
loadingMore = false;
}
}
This is to detect when the user has scrolled to the end and fetch new set of images:
// ONSCROLLLISTENER
gridOfPhotos.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int lastInScreen = firstVisibleItem + visibleItemCount;
if ((lastInScreen == totalItemCount) && !(loadingMore)) {
if (stopLoadingData == false) {
// FETCH THE NEXT BATCH OF FEEDS
new loadMorePhotos().execute();
}
}
}
});
And finally, this is how I fetch the next set of images:
private class loadMorePhotos extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... arg0) {
// SET LOADING MORE "TRUE"
loadingMore = true;
// INCREMENT CURRENT PAGE
current_page += 1;
// Next page request
URL = pagingURL;
try {
HttpClient hc = new DefaultHttpClient();
HttpGet get = new HttpGet(URL);
HttpResponse rp = hc.execute(get);
if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String queryAlbums = EntityUtils.toString(rp.getEntity());
// Log.e("PAGED RESULT", queryAlbums);
JSONObject JOTemp = new JSONObject(queryAlbums);
JSONArray JAPhotos = JOTemp.getJSONArray("data");
// IN MY CODE, I GET THE NEXT PAGE LINK HERE
getPhotos photos;
for (int i = 0; i < JAPhotos.length(); i++) {
JSONObject JOPhotos = JAPhotos.getJSONObject(i);
// Log.e("INDIVIDUAL ALBUMS", JOPhotos.toString());
if (JOPhotos.has("link")) {
photos = new getPhotos();
// GET THE ALBUM ID
if (JOPhotos.has("id")) {
photos.setPhotoID(JOPhotos.getString("id"));
} else {
photos.setPhotoID(null);
}
// GET THE ALBUM NAME
if (JOPhotos.has("name")) {
photos.setPhotoName(JOPhotos.getString("name"));
} else {
photos.setPhotoName(null);
}
// GET THE ALBUM COVER PHOTO
if (JOPhotos.has("picture")) {
photos.setPhotoPicture(JOPhotos
.getString("picture"));
} else {
photos.setPhotoPicture(null);
}
// GET THE ALBUM'S PHOTO COUNT
if (JOPhotos.has("source")) {
photos.setPhotoSource(JOPhotos
.getString("source"));
} else {
photos.setPhotoSource(null);
}
arrPhotos.add(photos);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
// get listview current position - used to maintain scroll position
int currentPosition = gridOfPhotos.getFirstVisiblePosition();
// APPEND NEW DATA TO THE ARRAYLIST AND SET THE ADAPTER TO THE
// LISTVIEW
adapter = new PhotosAdapter(Photos.this, arrPhotos);
gridOfPhotos.setAdapter(adapter);
// Setting new scroll position
gridOfPhotos.setSelection(currentPosition + 1);
// SET LOADINGMORE "FALSE" AFTER ADDING NEW FEEDS TO THE EXISTING
// LIST
loadingMore = false;
}
}
And this is the helper class to SET and GET the data collected from the queries above:
public class getPhotos {
String PhotoID;
String PhotoName;
String PhotoPicture;
String PhotoSource;
// SET THE PHOTO ID
public void setPhotoID(String PhotoID) {
this.PhotoID = PhotoID;
}
// GET THE PHOTO ID
public String getPhotoID() {
return PhotoID;
}
// SET THE PHOTO NAME
public void setPhotoName(String PhotoName) {
this.PhotoName = PhotoName;
}
// GET THE PHOTO NAME
public String getPhotoName() {
return PhotoName;
}
// SET THE PHOTO PICTURE
public void setPhotoPicture(String PhotoPicture) {
this.PhotoPicture = PhotoPicture;
}
// GET THE PHOTO PICTURE
public String getPhotoPicture() {
return PhotoPicture;
}
// SET THE PHOTO SOURCE
public void setPhotoSource(String PhotoSource) {
this.PhotoSource = PhotoSource;
}
// GET THE PHOTO SOURCE
public String getPhotoSource() {
return PhotoSource;
}
}
If you also want the adapter code, let me know. I use Fedor's Lazy Loading method in the adapter.
Phew. Hope any of this helps. If you have further question, feel free to ask. :-)
EDIT: Adapter code added:
public class PhotosAdapter extends BaseAdapter {
private Activity activity;
ArrayList<getPhotos> arrayPhotos;
private static LayoutInflater inflater = null;
ImageLoader imageLoader;
public PhotosAdapter(Activity a, ArrayList<getPhotos> arrPhotos) {
activity = a;
arrayPhotos = arrPhotos;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return arrayPhotos.size();
}
public Object getItem(int position) {
return arrayPhotos.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
View vi = convertView;
if(convertView == null) {
vi = inflater.inflate(R.layout.photos_item, null);
holder = new ViewHolder();
holder.imgPhoto = (ImageView)vi.findViewById(R.id.grid_item_image);
vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}
if (arrayPhotos.get(position).getPhotoPicture() != null){
imageLoader.DisplayImage(arrayPhotos.get(position).getPhotoPicture(), holder.imgPhoto);
}
return vi;
}
static class ViewHolder {
ImageView imgPhoto;
}
}
EDIT: Added steps to show Progress while loading:
Add a ProgressBar to you XML where you have the GridView right below it. Play around with the weight if it causes any problems.
<LinearLayout
android:id="#+id/linlaProgressBar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<ProgressBar
style="#style/Spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp" />
</LinearLayout>
In your Java, declare the Linearlayout linlaProgressBar as Global and cast it in the onCreate() and set it's visibility as linlaProgressBar.setVisibility(View.GONE);
And in the onPreExecute() use it like this:
#Override
protected void onPreExecute() {
// SHOW THE BOTTOM PROGRESS BAR (SPINNER) WHILE LOADING MORE PHOTOS
linlaProgressBar.setVisibility(View.VISIBLE);
}
And finally add, this in the onPostExecute()
// HIDE THE BOTTOM PROGRESS BAR (SPINNER) AFTER LOADING MORE ALBUMS
linlaProgressBar.setVisibility(View.GONE);
You can, take a look to Using adapter Views and GridView from Android Documentation.
The most important thing is that the adapter call the method getView passing only the position of the entries showing on screen, and asking for different positions when user scrolls.
The easy way to do is download the required image on the getView method of your adapter with and AsyncTask.
There is an example
Talking from experience, it's tricky to achieve smooth scrolling (and overall responsiveness) while consuming memory reasonably.
It would be a good idea to look for existing solutions first, e.g., start here:
Lazy load of images in ListView
We ended up with a custom proprietary solution. It is a background thread that queues download requests and downloads and caches on the external storage only the images that are still visible. When a new image arrives, the view gets notified and decides when to notify the adapter to update.
It also saves the bandwidth, which was important in some cases.
I found IceMAN's answer very useful, but I also recommend avoid using two AsyncTasks and you can make this easily.
You need to create a universal method to fetch needed data, where you can make an if/else condition (as an example):
movies = fetchMovie.execute(sort).get();
if (movies == null) {
movieList = new ArrayList<>();
} else if (addMovies) {
movieList.addAll(movies);
} else {
movieList = movies;
}
addMovies is a boolean in your onScroll method.
In AsyncTask provide current page in query URL and voila - you made your code smaller :)
I am trying to add ListView items one by one. So if I have say -- 60 items -- the application would add the views to the list view one at a time -- thus showing the user that the application is loading more things.
This is my code:
try {
JSONArray j = getTaggsJSON();
Log.v(TAG, String.valueOf(j.length()));
a = new createSpecialAdapter(this, R.layout.individual_tagg_view,
R.layout.list_item, view, j);
ListView v = this.getListView();
v.setStackFromBottom(true);
setListAdapter(a);
new addViewsToList().execute(j);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ServiceException e) {
e.printStackTrace();
}
}
private class addViewsToList extends AsyncTask<JSONArray, View, List<View>> {
protected List<View> doInBackground(JSONArray... jsonArrays) {
List<View> v = new ArrayList<View>();
Log.v(TAG, String.valueOf(jsonArrays[0].length()));
for (int x = 0; x < jsonArrays[0].length(); x++) {
try {
Log.v(TAG, jsonArrays[0].getJSONObject(x).toString());
v.add(ViewAdapter.createTaggView(jsonArrays[0]
.getJSONObject(x), c));
} catch (JSONException e) {
e.printStackTrace();
}
publishProgress(v.get(x));
}
return v;
}
protected void onProgressUpdate(View... v) {
Log.v(TAG, "I'm updating my progress!");
a.notifyDataSetChanged();
}
protected void onPostExecute(List<View> views) {
Log.v(TAG, "i'm done!");
Log.v(TAG, String.valueOf(views.size()));
}
}
So, it looks like my code is printing out the views correctly, and putting them in the list correctly, however my activity displays nothing. What am I doing wrong? I thought setting the adapter before -- when there are no views in the list -- and then updating the adapter that the list was changed was the right way to go.... Obviously not.. Can anyone help me?
If you need clarification on my questions please let me know.
EDIT: Here is the adapter code to supplement my question.
private class SpecialAdapter extends ArrayAdapter<JSONArray> {
JSONArray json;
public SpecialAdapter(Context context, int textViewResourceId,
int listItem, JSONArray j) {
super(context, textViewResourceId);
this.json = j;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Log.v(TAG,"I'm inside the getView method.");
try {
JSONObject info = json.getJSONObject(position);
Log.v(TAG,info.toString());
if (convertView == null) {
LayoutInflater i = (LayoutInflater) RecentTaggs.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = i.inflate(R.layout.individual_tagg_view, parent,
false);
holder = new ViewHolder();
holder.username = (TextView) convertView
.findViewById(R.id.userName);
holder.review = (TextView)convertView.findViewById(R.id.review_text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.review.setText(info.getString("review"));
return convertView;
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
My output doesn't output any of my Log.v statements, and doesn't show that its even inside the GetView method.
There is certainly something wrong
with your Adapter. You should never
save Views that should displayed in a
ListView on your own. Only save the
data that is needed to create the
View and overwrite the getView method
of your Adapter to create the needed
view.
The second thing is the list that you
are using in the background task is
visible in the scope of the
doInBackground method only. No change
to this list can have an effect on
your UI.
You are passing the JsonArray to your Adapter before starting the background thread. If your Adapter would be working correctly the ListView should be fully functional before the background thread even starts working. You should pass an empty JsonArray into the ListView and if you are extending an ArrayAdapter you can use addItem in your publish progress method.
Some a little bit unrelated things about the naming of your Adapter class. You called it createSpecialAdapter. This sounds like a method name but it is a classname. Try to start all class names with an uppercase letter and give them names of the things they represent like SpecialAdapter.