Endless adapter for android ListView - android

I use Ion library to receive data from server. I have problem, that my code makes get request multiple times, so I have data duplication in my list.
My adapter code:
public class ArticleAdapter extends ArrayAdapter<Article> {
Future<List<Article>> loadingOfPrevious;
static class ArticleHolder {
ImageView articleImage;
TextView articleCaption;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ArticleHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.list_view_item, null);
holder = new ArticleHolder();
holder.articleImage = (ImageView) convertView.findViewById(R.id.articleImage);
holder.articleCaption = (TextView) convertView.findViewById(R.id.articleCaption);
convertView.setTag(holder);
} else {
holder = (ArticleHolder) convertView.getTag();
}
Article article = getItem(position);
holder.articleCaption.setText(article.getTitle());
Ion.with(holder.articleImage)
.placeholder(R.drawable.placeholder)
.error(R.drawable.default_article_image)
//.crossfade(true)
.load(article.getImageUrl());
// we're near the end of the list adapter, so load more items
if (position >= getCount() - 3) {
loadPrevious(getItem(getCount() - 1).getId());
}
return convertView;
}
private void loadPrevious(long id) {
// don't attempt to load more if a load is already in progress
if (loadingOfPrevious != null && !loadingOfPrevious.isDone() && !loadingOfPrevious.isCancelled()) {
return;
}
String url = "http://example.com/rest-api/"
url = url + "?id=" + id;
url = url + "&count=" + 30;
// This request loads a URL as JsonArray and invokes
// a callback on completion.
final String articleUrl = url;
loadingOfLatest = Ion.with(getContext())
.load(articleUrl)
.as(new TypeToken<List<Article>>() {
})
.setCallback(new FutureCallback<List<Article>>() {
#Override
public void onCompleted(Exception e, List<Article> result) {
// this is called back onto the ui thread, no Activity.runOnUiThread or Handler.post necessary.
if (e != null) {
Toast.makeText(getContext(), "Error.", Toast.LENGTH_LONG).show();
return;
}
// add the article
if (result != null) {
Collections.reverse(result);
for (int i = 0; i < result.size(); i++) {
add(result.get(i));
}
notifyDataSetChanged();
}
}
});
}
}
I think the problem is in this part:
// we're near the end of the list adapter, so load more items
if (position >= getCount() - 3) {
loadPrevious(getItem(getCount() - 1).getId());
}

The way I did this was to use an OnScrollListener on the ListView. When the user scrolls and firstVisibleItem + visibleItemCount is near your list limit, start your server data load.
Once the data load is complete, update your list adapter with the new data and call onNotifyDataSetChanged(). The list will then redisplay with your updated data.

You are right! It's because of this code.
if (position >= getCount() - 3) {
loadPrevious(getItem(getCount() - 1).getId());
}
According to your condition, "loadPrevious()" will be called twice.
"getView()" works to return one view for each time. So for the last two items, your condition is true and then call "loadPrevious()" twice.
So change and try like this
if (position >= getCount() - 2) {
loadPrevious(getItem(getCount() - 1).getId());
}
OR
recheck this code
if (loadingOfPrevious != null && !loadingOfPrevious.isDone() && !loadingOfPrevious.isCancelled()) {
return;
}
BTW, I want to suggest to try other ways, like using library, customize listview
Android Endless List

Related

For loop causes the application may be doing too much work on its main thread

Hi i am using multiple for loop to add view dynamically in adapter class. It causes the application may be doing too much work on its main thread.
When i click the particular item i am calling notifyItemChanged at that time app getting slow. I am using runOnUiThread to update the views in adapter but its still freeze the app for some time and getting the warning message the application may be doing too much work on its main thread
Code :
Adapter Class
private void bind() {
try {
((Activity) mContext).runOnUiThread(() -> {
mFlowLayout.removeAllViews();
for (Element element : elementList) {
prepareElement(element, mFlowLayout);
}
mFlowLayout.removeViewAt(mFlowLayout.getChildCount() - 1);
});
} catch (Exception e) {
//e.print("");
}
}
private void prepareElement(Element element, FlowLayout flowLayout) {
if (element.getSentenceList() != null) {
if (element.getSentenceList().size() > 0) {
updateSentenceList(element, element.getSentenceList().get(0).getPhraseList(),
flowLayout, element.getSentenceList().get(0).getTemplateId());
} else {
addView(element.getElementName(), flowLayout);
}
} else {
addView(element.getElementName(), flowLayout);
}
}
private void updateSentenceList(Element element, List<PhraseList> phraseList, FlowLayout flowLayout,
String templateId) {
if (phraseList.size() > 0) {
prepareSuggestions(element, phraseList, flowLayout, templateId);
} else {
addView(element.getElementName(), flowLayout);
}
}
private void prepareSuggestions(Element element, List<PhraseList> suggestions, FlowLayout flowLayout, String templateId) {
for (int i = 0; i < suggestions.size(); i++) {
PhraseList phraseList = suggestions.get(i);
if (phraseList.getHighlight() != null) {
if (phraseList.getHighlight().equalsIgnoreCase("Normal")
&& !phraseList.isClicked()) {
addView(phraseList, flowLayout, i, suggestions.size(), templateId, element);
}
} else {
if (!phraseList.isClicked()) {
addView(phraseList, flowLayout, i, suggestions.size(), templateId, element);
}
}
}
}
private void addView(PhraseList suggestion, FlowLayout flowLayout, int position, int totalSize,
String templateId, Element element) {
View currentView = LayoutInflater.from(mContext).inflate(R.layout.item_flow_layout
, flowLayout, false);
View indicatorView = LayoutInflater.from(mContext).inflate(R.layout.custom_indicator
, flowLayout, false);
TextView textView = currentView.findViewById(R.id.text_view_element_with_attribute);
textView.setBackgroundResource(R.drawable.bottom_line_black);
if (suggestion.getIsKey()) {
textView.setText(suggestion.getPhrase());
textView.setTypeface(FontCache.getInstance(mContext).getFont(FontCache.Font.REGULAR));
} else {
textView.setText(Utils.changeStringCase(suggestion.getKey()));
textView.setTypeface(FontCache.getInstance(mContext).getFont(FontCache.Font.BOLD));
}
textView.setOnClickListener(onClickView -> {
((RichElementSelectionActivity) mContext).onClickItem(element, templateId);
});
((Activity)mContext).runOnUiThread(() -> {
flowLayout.addView(currentView);
if (position == totalSize - 1) {
flowLayout.addView(indicatorView);
}
});
}
Please give any suggestion. Thanks
You should use RecyclerView instead
Heavy Task should be done in background thread: Asynctask, RxAndroid,...

Recyclerview Dynamic Sections not using any 3rd lib

i want to add header to recycle view i am trying to achieve it using
#Override
public int getItemViewType(int position) {
// depends on your problem
if (position == 0 || position == 4) {
return 2;
} else {
return 1;
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
if (viewType == 1) {
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.cardview_row, null);
return new ViewHolder(itemLayoutView);
} else if (viewType == 2) {
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.cardview_row1, null);
return new ViewHolders(itemLayoutView);
}
return null;
}
but how i can do it in run time like when i don't know position when it should show section like i have json
{
"DATA": [
"hi",
"heloo",
"bye"
],
"MAHA": [
"ans",
"rs",
"re",
"jab",
"bad"
]
}
where data and maha is section i want to display other elements
currently i am making arraylist of all elements and adding hardcore
value for section but how i can do this viva using above json
What you need is an expandable recyclerview with parents(headers) and children(entries). Here you go:
https://github.com/bignerdranch/expandable-recycler-view
If you want to show the entries always and dont profit from the expandable feature, just do: expAdapter.expandAllParents().
I know you dont want to use 3rd library parties but in my opinion this is the best way to deal with it and saves you a lot of time. Moreover if someone else has the same question he maybe finds this solution useful.
Create a class like this for your JSON data, and create one Section class per node (in your example, one for DATA and another for MAHA):
class Section {
String header;
List<String> itemList;
}
Then create your custom adapter:
public class SectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Section> sectionList;
public SectionedRecyclerViewAdapter(List<Section> sectionList) {
this.sectionList = sectionList;
}
#Override
public int getItemViewType(int position) {
int currentPos = 0;
for (Section section : sectionList) {
// check if position is in this section
// +1 for the header, -1 because idx starts at 0
if (position >= currentPos && position <= (currentPost + section.itemList.size() + 1 - 1)) {
if (position == currentPos) {
return 2; // header
} else {
return 1; // item
}
}
// +1 for the header
currentPos += section.itemList.size() + 1;
}
throw new IndexOutOfBoundsException("Invalid position");
}
// use the onCreateViewHolder method from your question...
}
I know you don't want to use a 3rd party lib, but you can check how the getItemViewType method of SectionedRecyclerViewAdapter is done here.

Update value on adapter

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.

Refresh listView with multiple view types after delete/remove item

I have a ListView with a custom adapter that extends BaseAdapter and has 2 view types. When I run my adapter.removeRow(position) method, the data for the adapter is correctly updated, and the list reflects this, but the view types are not correctly updated. The Adapter is backed by
ArrayList<Map<String, String>> rows = new ArrayList<Map<String, String>>();
and I have a subset
List<Integer> flashSet = new ArrayList<Integer>();
which is a list of all the positions that are of ViewType 1 (as opposed to the standard view type 0).
Here is my adapter removeRow(position) method:
public void removeRow(int position) {
if (getItemViewType(position) == TYPE_FLASH) {
flashSet.remove(position);
}
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashPosition -= 1;
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
rows.remove(position);
notifyDataSetChanged();
}
Here is my getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
FlashHolder flashHolder;
ClipHolder clipHolder;
int type = getItemViewType(position);
if (convertView == null) {
if (type == TYPE_CLIP) {
convertView = rowInflater.inflate(R.layout.clip_note_row_layout, null);
clipHolder = new ClipHolder();
flashHolder = null;
clipHolder.textView = (TextView)(convertView.findViewById(R.id.clip_text));
convertView.setTag(clipHolder);
} else {
convertView = rowInflater.inflate(R.layout.flash_row_layout, null);
clipHolder = null;
flashHolder = new FlashHolder();
flashHolder.front = (TextView)(convertView.findViewById(R.id.flash_text));
flashHolder.back = (TextView)(convertView.findViewById(R.id.clip_text));
convertView.setTag(flashHolder);
}
} else {
if (type == TYPE_CLIP) {
clipHolder = (ClipHolder)convertView.getTag();
flashHolder = null;
} else {
clipHolder = null;
flashHolder = (FlashHolder)convertView.getTag();
}
}
if (type == TYPE_CLIP) {
clipHolder.textView.setText(rows.get(position).get("clip"));
} else {
flashHolder.front.setText(rows.get(position).get("flash_text"));
flashHolder.back.setText(rows.get(position).get("clip"));
}
return convertView;
}
I know that I could create a new adapter, give it the updated ArrayList and call listView.setAdapter(adapter) but this seems total overkill when I'm simply trying to remove one item from a potentially long list. See pics for a before and after deleting:
Then I delete the first item. The word "which" was hidden behind the "Let's watch it" item and now the "inspired by…" item is hidden behind a blank item 3.
So, data is updating, view types aren't. Thanks for the help!
I figured it out. This will be useful to no one as I don't expect others to make the same mistake.
I naively thought that by doing this
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashPosition -= 1;
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
I was changing the actual value stored in the List<Integer> flashSet = new ArrayList<Integer>();
In fact, I need to do the following instead:
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashSet.remove((Object)flashPosition);
flashPosition -= 1;
flashSet.add(flashPosition);
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
Try this, After delete or add item you need to call adapter refresh.
Youradapter.notifyDataSetChanged();
use yourlistview.invalidateViews()
instead of
notifyDataSetChanged();
it works for me.

Lazy download images into gridView

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 :)

Categories

Resources