The images loaded by this custom adapter placed at wrong positions i.e correct movie banner is not placed at correct list view item. and keeps on changing for a while.
here is my custom adapter with ASYNCTASK which is loading images from URL
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androlizer.yify.torrent.R;
import androlizer.yify.torrents.models.UpcomingMovieListModel;
public class UpcomingMoviesCustomAdapter extends ArrayAdapter<UpcomingMovieListModel> {
Context context;
public UpcomingMoviesCustomAdapter(
Context context, int resource, List<UpcomingMovieListModel> objects) {
super(context, resource, objects);
this.context = context;
}
static class ViewHolder
{
TextView movieTitle_textView;
TextView uploader_textView;
TextView date_textView;
ImageView movie_icon_imageView;
ImageView imdb_url_imageView;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
// getting data
final UpcomingMovieListModel movie = getItem(position);
if (convertView == null)
{
convertView = View.inflate(context, R.layout.movie_upcoming_row, null);
holder = new ViewHolder();
holder.movieTitle_textView = (TextView) convertView.findViewById(R.id.movie_upcoming_movie_title);
holder.uploader_textView = (TextView) convertView.findViewById(R.id.movie_upcoming_uploader);
holder.date_textView = (TextView) convertView.findViewById(R.id.movie_upcoming_date);
holder.imdb_url_imageView = (ImageView)convertView.findViewById(R.id.movie_upcoming_imageView_imdblink);
holder.movie_icon_imageView = (ImageView)convertView.findViewById(R.id.movie_upcoming_movie_image_view);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
if (movie != null)
{
holder.movieTitle_textView.setText(movie.getM_title());
holder.uploader_textView.setText(movie.getUploader());
SimpleDateFormat origFormat= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//Store it as a date object
Date date = null;
try {
date = origFormat.parse(movie.getDate_added());
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Output it as a string that uses the new format
SimpleDateFormat newFormat= new SimpleDateFormat("MMMMMMMMM dd, yyyy 'at' hh:mm a");
String desiredDateFormat = newFormat.format(date);
holder.date_textView.setText(desiredDateFormat);
holder.imdb_url_imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(movie.getImdb_url())));
}
});
}
new ImageLoader().execute(convertView.g, movie.getM_cover());
return convertView;
}
public class ImageLoader extends AsyncTask<Object, String, Bitmap> {
private View view;
private Bitmap bitmap = null;
#Override
protected Bitmap doInBackground(Object... parameters) {
// Get the passed arguments here
view = (View) parameters[0];
String uri = (String)parameters[1];
// Create bitmap from passed in Uri here
// ...
try {
URL req = new URL(uri);
bitmap = BitmapFactory.decodeStream(req.openConnection()
.getInputStream());
} catch (Exception e) {
// TODO: handle exception
}
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null && view != null) {
ImageView splash = (ImageView) view.findViewById(R.id.movie_upcoming_movie_image_view);
splash.setImageBitmap(bitmap);
}
}
}
}
I think the problem is that you don't stop a previous ImageLoader when a list item is reused: when a list item is reused another ImageLoader is attached to it but without removing the previous that was attached to the same list item instance.
Because of this can happen that the first ImageLoader could finish its job after the last one that sets the wrong image. Also you need to cache the downloaded images otherwise already downloaded images will downloaded again.
The right thing to do(TM) should be to set the image bitmap in the holder instance related to the list item and instead to stop the loader, make it acts on the holder.
What is happening here is that you are loading your images and setting them to the ImageView for your current list item, but not storing that Bitmap anywhere else. ListItems in Android are reused so that when you scroll, the device doesn't have to store many ListItems in memory. As a result, the OS will set new images to your list items as you scroll, which won't be the ones that you want.
The solution is to store your Bitmaps in the ArrayList that backs your ListView. In this case, it is List<UpcomingMovieListModel> objects that holds your data for the list view. There are several changes that need to be made here, so I'm not going to provide the full code, but I will describe the process.
Extend your UpcomingMovieListModel object to have a field called movieIcon.
Pass the current UpcomingMovieListModel object to your AsyncTask and have the AsyncTask set movieIcon to the downloaded image.
In your getView method, set the ImageView to the value of movie.movieIcon if it is not null.
In onPostExecute for your AsyncTask call this.notifyDataSetChanged() after you set movieIcon to the newly downloaded image. This tells the ListView that you have changed the underlying data, and it should refresh to show the icon for the current movie in each item on the screen.
Do not call setImageBitmap directly in onPostExecute. This will be taken care of in getView when you notify that the dataset has changed.
If you change these things, you should see all the right images, because you'll be storing them correctly for the underlying data.
Related
I'm having trouble displaying the right String from an ArrayList in my ListView.
My Array (m_aDataList) looks like this:
m_aDataList {ArrayList}
{...}0
m_cText = "R;21;9;River Street 2;12154;;1;.......
m_cTimeStamp = 1556553367492
m_nID = 7
m_nStatus = 0
m_nType = 10002
{...}1
....
This is how I'm currently trying to Display the ArrayList in my ListView:
ArrayAdapter<Message> tuAdapter;
MessageManager tu = new MessageManager();
ArrayList<Message> list = tu.getMessageData();
tuAdapter = new ArrayAdapter<Message>(this, android.R.layout.simple_list_item_1, list);
lvOrders.setAdapter(tuAdapter);
It technically works and adds something like this to my ListView:
de.telematik.testapp.entities.Message#232d9c76
But what I'm trying to do is, only show Messages where m_nType == 10002 and Display them like this in my ListView:
Order: 12154 (order number from m_cText)
Checking weither or not m_nType is = 10002 or 10000 shouldn't be the problem. But how do I get the order number out of the String m_cText and then display it in my ListView?
In any case, thanks for your help.
You are using a basic default list adapter, but you are passing it an array of objects (Message). In order to control what displays in the list, you will need to create your own custom adapter class, that extends (probably base adapter) one of the standard adapter classes. You will call it similarly to the way you pass your list to your simple list adapter. Here is a sample custom list adapter:
ClaimListAdapter.java
package com.mycompany.myapp.adapter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import com.mycompany.myapp.R;
import com.mycompany.myapp.ClaimListFragment;
import com.mycompany.myapp.model.ClaimItem;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;
public class ClaimListAdapter extends BaseAdapter {
private Context context;
private ArrayList<ClaimItem> claimItems;
ClaimListFragment fragment;
public ClaimListAdapter(ClaimListFragment fragment, Context context, ArrayList<ClaimItem> claimItems){
this.context = context;
this.claimItems = claimItems;
this.fragment = fragment;
}
#Override
public int getCount() {
return claimItems.size();
}
#Override
public Object getItem(int position) {
return claimItems.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#SuppressLint("InflateParams")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// This method gets called for every item in the list when it is about to be displayed in the list
// This helps you get a reference to the layout of the list item
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater)
context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.claim_list_item, null);
}
// If you have a button that you want to perform some function, such as delete, and then call
// a method back in the parent to update the items in the array, this is one way to do it
Button btnDelete = (Button) convertView.findViewById(R.id.claim_delete_in_list);
// Save the position of the item in the list, so, you know which item was clicked
btnDelete.setTag(position);
btnDelete.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Integer position = (Integer)v.getTag();
// Call this method which is back in the parent, which in this case is a fragment, but could be an Activity
fragment.deleteItemList(position);
}
});
btnDelete.setVisibility(View.GONE);
// Here you get a reference to the various TextViews in your layout
TextView txtTitle = (TextView) convertView.findViewById(R.id.claim_title);
TextView txtStatus = (TextView) convertView.findViewById(R.id.claim_status);
TextView txtDate = (TextView) convertView.findViewById(R.id.claim_date);
TextView txtDistance = (TextView) convertView.findViewById(R.id.claim_distance);
TextView txtAmount = (TextView) convertView.findViewById(R.id.claim_amount);
// Here you get the various strings out of your object for display
String claim_title = claimItems.get(position).getDocumentID();
if (claim_title == null) {
claim_title = "";
}
String claim_status = claimItems.get(position).getClaimStatus();
String claim_date = claimItems.get(position).getClaimDate();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = null;
try {
date = (Date) dateFormat.parse(claim_date);
dateFormat = new SimpleDateFormat("M/d/yyyy");
String formattedDate = dateFormat.format(date);
txtDate.setText(formattedDate);
} catch (ParseException e) {
e.printStackTrace();
}
double total_miles = claimItems.get(position).getTotalMiles();
double total_amount = claimItems.get(position).getTotalAmount();
// Here you set the values in the TextView for display
txtTitle.setText(claim_title);
txtStatus.setText(claim_status);
txtDistance.setText("" + total_miles + "mi");
txtAmount.setText("$" + total_amount);
return convertView;
}
}
Alternatively, you could create a string array containing only the order numbers from your array of messages, and pass that array to your list adapter instead of the array of messages.
After I create or receive a message send the message then save it to realm. Afterwards I need to update my threads ListView on the threads page and bring the newest messages to the top. I already have it so the thread list shows the updated preview and updated date, but it stays in it's inital ListView position. I tried to requery realm to get all info and reorder by the lastUpdated time, but it doesn't seem to work. Do I need to wipe the old thread list then repopulate it to get it to update?
I have the update triggered on the onResume()
#Override
protected void onResume() {
super.onResume();
updateListview = true;
updateList();
}
Here's my update
#UiThread
public void updateList() {
try {
if (updateListview) {
thread_realm = Realm.getInstance(this);
results = thread_realm.where(ZipListModel.class).findAllSorted("zipupdated", RealmResults.SORT_ORDER_DESCENDING);
adapter = new ZipListAdapter(this, results);
threadsListView.setAdapter(adapter);
adapter.notifyDataSetChanged();
if (results.size()==0){
createZipHint.setVisibility(View.VISIBLE);
} else {
createZipHint.setVisibility(View.INVISIBLE);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Adapter
package com.admin.zipline.adapters;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.support.v7.widget.CardView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.admin.zipline.R;
import com.admin.zipline.activities.ZipListPage;
import com.admin.zipline.model.ZipListModel;
import org.androidannotations.annotations.ViewById;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ZipListAdapter extends ArrayAdapter<ZipListModel>
{
List<ZipListModel> items;
Context context;
Typeface semiBold;
Typeface light;
Typeface regular;
String[] months={};
public ZipListModel ziplist;
ArrayList<String> ziplistNames,ziplistParticipantsaids;
public ZipListAdapter(Context context, List<ZipListModel> threadslist) {
super(context,R.layout.zip_adapter_view,threadslist);
this.context = context;
this.items=threadslist;
}
#Override
public View getView(final int position, View view, ViewGroup parent) {
ViewHolder holder ;
if (view == null) {
holder =new ViewHolder();
LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.zip_adapter_view, parent, false);
light = Typeface.createFromAsset(context.getAssets(), "ProximaNova-Light.otf");
regular = Typeface.createFromAsset(context.getAssets(), "ProximaNova-Regular.otf");
semiBold = Typeface.createFromAsset(context.getAssets(),"ProximaNova-Semibold.otf");
//TODO
/*for showing the author image
* */
//holder.thread_image = (ImageView)view.findViewById(R.id.author_avatar);
holder.thread_text = (TextView) view.findViewById(R.id.threadtext);
holder.thread_name = (TextView) view.findViewById(R.id.threadname);
holder.last_updated = (TextView) view.findViewById(R.id.lastupdated);
holder.zip_members=(TextView)view.findViewById(R.id.ziplist_members);
holder.thread_text.setTypeface(light);
holder.thread_name.setTypeface(semiBold);
holder.zip_members.setTypeface(regular);
view.setTag(holder);
}else{
holder =(ViewHolder)view.getTag();
}
try{
ziplist = items.get(position);
ziplistNames = new ArrayList<String>();
ziplistParticipantsaids=new ArrayList<>();
if (ziplist != null) {
if (ziplist.getMessagesListsmodel().first().getText()!=null){
holder.thread_text.setText(ziplist.getMessagesListsmodel().first().getText());
}
if (ziplist.getMessagesListsmodel().first().getCreatedAt()!=null){
holder.last_updated.setText(getDate(ziplist.getMessagesListsmodel().first().getCreatedAt()));
}
for (int i = 0; i < ziplist.getParticipantsmodel().size(); i++) {
ziplistNames.add(ziplist.getParticipantsmodel().get(i).getName());
ziplistParticipantsaids.add(ziplist.getParticipantsmodel().get(i).getParticipantId());
}
String members="";
for (int i=0;i<ziplistNames.size();i++){
members+=ziplist.getParticipantsmodel().get(i).getFirstName()+", ";
}
if (members.length() > 3){
members=members.substring(0,members.length()-2);
}
holder.zip_members.setText(members);
if(ziplist.getZipname().isEmpty()){
holder.thread_name.setText(members);
} else {
holder.thread_name.setText(ziplist.getZipname());
}
}
}
catch (Exception e){
e.printStackTrace();
}
view.setBackgroundColor(Color.parseColor(ziplist.getZipColor()));
return view;
}
String getDate(Date date) {
try {
Date d = date;
months=context.getResources().getStringArray(R.array.months);
return months[d.getMonth()] + " " + (d.getDate());
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public class ViewHolder{
ImageView thread_image;
TextView thread_text,thread_name,last_updated,zip_members;
// CardView cardView;
}
}
The question is not quite clear to me but i can suggest some common ways to do this.
Assume your ZipListModel defined like this:
public class ZipListModel extends RealmObject {
private String title;
private Date date;
private String Author;
... getters and setters...
}
The easiest way to show your models in the ListView is using RealmBaseAdapter. You can find document here. And example.
eg.:
public class ZipListAdapter extends RealmBaseAdapter<ZipListModel> implements ListAdapter {
public ZipListAdapter(Context context, int resId,
RealmResults<ZipListModel> realmResults,
boolean automaticUpdate) {
super(context, realmResults, automaticUpdate);
}
...
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Update your views with the realmResults
...
ZipListModel zlm = realmResults.get(position);
authorTextView.setText(zlm.getAuthor);
...
}
...
}
The benifit of using RealmBaseAdapter is the realmResults can be updated automatically when the Realm transaction commited which means in your case, whenever you update your Realm by
thread_realm = Realm.getInstance(this);
thread_realm.beginTransaction()
// Change something in your Realm
...
thread_realm.commitTransaction()
The ZipListAdapter's notifyDataSetChanged will be called automatically and the listView will be updated.
BTW, you can still stay with your own adpater and simply update it by using RealmChangeListener. See example .
Try use RealmBaseAdapter.UpdateData(RealmResult<T> result). My project has similar requirement and it works for me
https://realm.io/docs/java/latest/api/io/realm/RealmBaseAdapter.html
I feel like I'm very close to a solution... I'm getting no errors or crashes, but my text file from my SD card is simply not showing.
Intention: I am trying to read ALL files from a particular directory in my Documents directory on my SD card, then once each file is read, it should be loaded into my ListView via my adapter. I have a model set up (PoemListItem.java), that should retrieve the title of the file, and the contents of the file, both in Strings. I know that it reaches the file in the directory, because when I take away the for loop that adds default text (in case there are not many files in the directory) into the ArrayList<>, then the number of lines in the list are exactly equal to the number of files in the directory. So that part works at least. Only problem is there is nothing showing. Why?
Thanks for your help!
Here you can see that one list item displays (since there is one file in the SD card directory of choice), but that title and text are hint code from the xml, not the actual file title and contents. I assume the hint text is overridden when you import file contents instead? It doesn't seem to be here... The actual title and text that are supposed to be there instead is simply: File name and Test 2... instead of Title and Quote or poem, here.
TextTab.java
package org.azurespot.cutecollection;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import org.azurespot.R;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
/**
* Created by mizu on 2/8/15.
*/
public class TextTab extends Fragment {
private ArrayList<PoemListItem> poems = new ArrayList<>();
private ListViewPoemAdapter adapter;
private ListView listView;
String[] allSDCardFiles = null;
StringBuilder text;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.text_tab, container, false);
adapter = new ListViewPoemAdapter(getActivity(), poems);
// Attach the adapter to a ListView
listView = (ListView) v.findViewById(R.id.text_list_view);
listView.setAdapter(adapter);
// read contents of SD card
loadSDCard();
// add the default icon/lines remaining to ArrayList, if less than 24 files on SD card
for (int i = 0; i < (24 - allSDCardFiles.length); i++) {
PoemListItem sampleItem = new PoemListItem(" ", " ");
adapter.add(sampleItem);
i++;
}
setupListViewListener();
return v;
}
private void loadSDCard(){
// gets directory CuteWords rom sd card
File baseDir = Environment.getExternalStorageDirectory();
File cuteWordsDir = new File(baseDir, "/Documents/CuteWords");
// lists all files in CuteWords, loads in Files[] array
File[] files = cuteWordsDir.listFiles();
for (File singleFile : files) {
//Read text from file, put each line into StringBuilder
text = new StringBuilder();
try {
BufferedReader br = new BufferedReader(new FileReader(singleFile));
String line;
while ((line = br.readLine()) != null) {
text.append(line);
text.append('\n');
// add file name and text in file to adapter, to display in ListView
PoemListItem wordsFromFile = new PoemListItem(singleFile.getName(),
text.toString());
adapter.add(wordsFromFile);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// get number of files in CuteWords directory
allSDCardFiles = new String[files.length];
}
// so you can edit any of the list items
private void setupListViewListener() {
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter,
View item, int pos, long id){
// code to edit then save typed text
}
});
listView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> aView, View item,
int pos, long id) {
poems.remove(pos);
adapter.notifyDataSetChanged();
// writeItems();
return true;
}
});
}
}
Here is my model:
PoemListItem.java
package org.azurespot.cutecollection;
/**
* Created by mizu on 3/12/15.
*/
public class PoemListItem {
private String title;
private String poem;
public PoemListItem(){}
public PoemListItem(String t, String p){
this.title = t;
this.poem = p;
}
public String getTitle(){
return title;
}
public void setTitle(String title){
this.title = title;
}
public String getPoem(){
return poem;
}
public void setPoem(String poem){
this.poem = poem;
}
}
You probably don't need to see the adapter, but just in case:
ListViewPoemAdapter.java
package org.azurespot.cutecollection;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import org.azurespot.R;
import java.util.ArrayList;
/**
* Created by mizu on 2/8/15.
*/
public class ListViewPoemAdapter extends ArrayAdapter<PoemListItem> {
private Context context;
private EditText poemText;
private EditText poemTitle;
private ImageView poemPlaceholder;
public ListViewPoemAdapter(Context context, ArrayList<PoemListItem> poems) {
super(context, 0, poems);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
PoemListItem poemListItem = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext())
.inflate(R.layout.text_listview_row, parent, false);
}
poemTitle = (EditText) convertView.findViewById(R.id.et_text_title);
poemText = (EditText) convertView.findViewById(R.id.et_text);
poemPlaceholder = (ImageView)convertView.findViewById(R.id.icon_placeholder_poem);
poemPlaceholder.setBackgroundResource(R.drawable.ic_poem_placeholder);
poemPlaceholder.setScaleType(ImageView.ScaleType.CENTER_CROP);
poemPlaceholder.setLayoutParams(new LinearLayout.LayoutParams(150, 150));
// poemTitle.setText(poemListItem.getTitle());
// poemText.setText(poemListItem.getPoem());
return convertView;
}
}
Your arraylist is empty.
Add "wordsFromFile" in arraylist instead of adapter.
Found the mistake! My adapter code that was referencing my model (to hook up my views) was not complete. When you create a model to use for your custom ListView (that has a separate xml file for the list row), then you must include in your model what goes into your list items. In my case, I had two EditText items representing the title and the poem text itself. So my model was missing these lines:
PoemListItem poemListItem = getItem(position);
poemTitle.setText(poemListItem.getTitle());
poemText.setText(poemListItem.getPoem());
Below, you can see them in context. As soon as I uncommented them, I found my List populated with the title and text body in the files from the SD Card. The only reason I had them commented out, was so my hint text from my xml EditTexts would show. Now the hint text does not show on the placeholder lines of the list, but that's a compromise I can live with. So most of my code was right, I just had a few missing lines in my adapter.
ListViewPoemAdapter.java
package org.azurespot.cutecollection;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import org.azurespot.R;
import java.util.ArrayList;
/**
* Created by mizu on 2/8/15.
*/
public class ListViewPoemAdapter extends ArrayAdapter<PoemListItem> {
private Context context;
private EditText poemText;
private EditText poemTitle;
private ImageView poemPlaceholder;
public ListViewPoemAdapter(Context context, ArrayList<PoemListItem> poems) {
super(context, 0, poems);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
PoemListItem poemListItem = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext())
.inflate(R.layout.text_listview_row, parent, false);
}
poemTitle = (EditText) convertView.findViewById(R.id.et_text_title);
poemText = (EditText) convertView.findViewById(R.id.et_text);
poemPlaceholder = (ImageView)convertView.findViewById(R.id.icon_placeholder_poem);
poemPlaceholder.setBackgroundResource(R.drawable.ic_poem_placeholder);
poemPlaceholder.setScaleType(ImageView.ScaleType.CENTER_CROP);
poemPlaceholder.setLayoutParams(new LinearLayout.LayoutParams(150, 150));
poemTitle.setText(poemListItem.getTitle());
poemText.setText(poemListItem.getPoem());
return convertView;
}
Hi I'm trying to create a GridView using images from an online depository using Picasso.
I also want the user to be able to click on the image and it load up full screen to view.
So far I have managed to get it working almost perfectly. The only problem is the image that shows when they click on the grid is not the one that they clicked on, in fact it randomly chooses an image each time.
I was hoping somebody could take a look at my code and tell me where I am going wrong.
Thanks.
So this is my MainActivity class:
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView gv = (GridView) findViewById(R.id.grid_view);
gv.setAdapter(new GridViewAdapter(this));
gv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {
// Sending image id to FullScreenActivity
Intent i = new Intent(getApplicationContext(), FullImageActivity.class);
// passing array index
i.putExtra("id", position);
startActivity(i);
}
});
}
}
This is my GridView class:
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static android.widget.ImageView.ScaleType.CENTER_CROP;
final class GridViewAdapter extends BaseAdapter {
final Context context;
final List<String> urls = new ArrayList<String>();
public GridViewAdapter(Context context) {
this.context = context;
// Ensure we get a different ordering of images on each run.
Collections.addAll(urls, Info.URLS);
Collections.shuffle(urls);
// Triple up the list.
ArrayList<String> copy = new ArrayList<String>(urls);
urls.addAll(copy);
urls.addAll(copy);
}
#Override public View getView(int position, View convertView, ViewGroup parent) {
SquaredImageView view = (SquaredImageView) convertView;
if (view == null) {
view = new SquaredImageView(context);
view.setScaleType(CENTER_CROP);
}
// Get the image URL for the current position.
String url = getItem(position);
// Trigger the download of the URL asynchronously into the image view.
Picasso.with(context) //
.load(url) //
.placeholder(R.drawable.placeholder) //
.error(R.drawable.error) //
.fit() //
.into(view);
return view;
}
#Override public int getCount() {
return urls.size();
}
#Override public String getItem(int position) {
return urls.get(position);
}
#Override public long getItemId(int position) {
return position;
}
}
And finally my FullScreen Class:
import com.squareup.picasso.Picasso;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ImageView;
public class FullImageActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.full_image);
// get intent data
Intent i = getIntent();
// Selected image id
int position = i.getExtras().getInt("id");
GridViewAdapter ia = new GridViewAdapter(this);
ImageView iv = (ImageView) findViewById(R.id.full_image_view);
Picasso.with(this) //
.load(ia.getItem(position)) //
.placeholder(R.drawable.placeholder) //
.error(R.drawable.error) //
.fit()
.centerCrop()//
.into(iv);
}
}
I believe this is your culprit:
Collections.addAll(urls, Info.URLS);
Collections.shuffle(urls); // this reshuffles on every new instance
GridViewAdapter ia = new GridViewAdapter(this); // your full screen activity is creating a new instance.
Since I cannot comment yet, the answer is No. When you say startActivity, it will create a new instance of the FullScreenActivity, and then in that FullScreenActivity you are also instantiating a new Adapter which in turn does the shuffle work.
Please put in a breakpoint if you are in doubt.
You reinitialized the adapter for whatever reason here GridViewAdapter ia = new GridViewAdapter(this); That's when the shuffling occurs, in the constructor.
You should not have an adapter in your 2nd Activity. Adapters are for lists. You should simply pass that Activity the image URL.
GridView gv = (GridView) findViewById(R.id.grid_view);
GridViewAdapter adapter = new GridViewAdapter(this);
gv.setAdapter(adapter);
gv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {
// Sending image url to FullScreenActivity
Intent i = new Intent(getApplicationContext(), FullImageActivity.class);
i.putExtra("url", adapter.getItem(position));
startActivity(i);
}
});
and
public class FullImageActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.full_image);
// get intent data
Intent i = getIntent();
string url = i.getExtras().getString("url");
ImageView iv = (ImageView) findViewById(R.id.full_image_view);
Picasso.with(this) //
.load(url) //
.placeholder(R.drawable.placeholder) //
.error(R.drawable.error) //
.fit()
.centerCrop()//
.into(iv);
}
}
You are creating an adapter twice - once in MainActivity, second time in FullImageActivity. Each time it is created shuffled, that's the reason. Nice copy-paste from Picasso sample btw ;)
Although there are many tutorials out there, I've found it really difficult to implement an AsyncTask to load images from URI's (obtained from a content provider) into a custom adapter.
I get the basic gist, which is to have a class containing an AsyncTask, do the bitmap creation in the 'doInBackground', and then set the ImageView in the onPostExecute.
The problem for me, being new to android & programming, is that I don't know how to pass in my Uri's to the AsyncTask for each item, how to create the bitmap, and how to return it to the adapter.
I've only gotten this far with the actual AsyncTask class (ImageLoader):
package another.music.player;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.widget.ImageView;
public class ImageLoader extends AsyncTask<String, String, Bitmap> {
private ImageView imageView;
private Bitmap bitmap = null;
#Override
protected Bitmap doInBackground(String... uri) {
// Create bitmap from passed in Uri here
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
And my custom adapter looks like this:
package another.music.player;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore.Audio.AlbumColumns;
import android.provider.MediaStore.Audio.AudioColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;
class AlbumAdapter extends CursorAdapter {
public AlbumAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
private static Uri currentSongUri;
#Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView albumArt = (ImageView) view.getTag(R.id.albumArt);
TextView text1 = (TextView) view.getTag(R.id.artistTitle);
TextView text2 = (TextView) view.getTag(R.id.albumTitle);
TextView text3 = (TextView) view.getTag(R.id.totalSongs);
albumArt.setImageBitmap(null);
text1.setText(cursor.getString(cursor
.getColumnIndex(AudioColumns.ARTIST)));
text2.setText(cursor.getString(cursor
.getColumnIndex(AudioColumns.ALBUM)));
text3.setText(cursor.getString(cursor
.getColumnIndex(AlbumColumns.NUMBER_OF_SONGS)));
String currentAlbumId = cursor.getString(cursor
.getColumnIndex(BaseColumns._ID));
Integer currentAlbumIdLong = Integer.parseInt(currentAlbumId);
Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
currentSongUri = ContentUris.withAppendedId(artworkUri,
currentAlbumIdLong);
//Run ImageLoader AsyncTask here, and somehow retrieve the ImageView & set it.
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.albumitem, null);
view.setTag(R.id.albumArt, view.findViewById(R.id.albumArt));
view.setTag(R.id.artistTitle, view.findViewById(R.id.artistTitle));
view.setTag(R.id.albumTitle, view.findViewById(R.id.albumTitle));
view.setTag(R.id.totalSongs, view.findViewById(R.id.totalSongs));
return view;
}
}
I would be very grateful if somebody could show me how to proceed with this.
Thanks.
You need to pass your AsyncTask the view so that it can update it when it completes:
//Run ImageLoader AsyncTask here, and let it set the ImageView when it is done.
new ImageLoader().execute(view, uri);
And modify AsyncTask so that it can handle mixed parameter types:
public class ImageLoader extends AsyncTask<Object, String, Bitmap> {
private View view;
private Bitmap bitmap = null;
#Override
protected Bitmap doInBackground(Object... parameters) {
// Get the passed arguments here
view = (View) parameters[0];
String uri = (String)parameters[1];
// Create bitmap from passed in Uri here
// ...
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null && view != null) {
ImageView albumArt = (ImageView) view.getTag(R.id.albumArt);
albumArt.setImageBitmap(bitmap);
}
}
}
I haven't tested this code but it should give you an idea.
Why are you doing setImage in AsyncTask? You can do it in a thread. I don't think in this condition AsyncTask would be good. Better you do it in different thread.