I am trying to show a lot of images from my server. There is no conventional link since i am using AMazon s3 services. Here is my code to download the images. Since i need to reduce image size, is this better way of achiving smooth scroll or do i need to do something else
public class PinsListAdapter extends BaseAdapter
{
private Activity mContext;
private ArrayList<PingModel> pings = new ArrayList<PingModel>();
private LayoutInflater inflater;
public PinsListAdapter(Activity context, ArrayList<PingModel> pings)
{
super();
this.mContext = context;
this.pings = pings;
inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount()
{
return pings.size();
}
#Override
public Object getItem(int arg0)
{
return null;
}
#Override
public long getItemId(int arg0)
{
return 0;
}
private static class ViewHolder
{
public ImageView vidImgIndicator;
public ImageView pinImg;
public ImageView progress;
}
#Override
public View getView(int position, View convertView, final ViewGroup parent)
{
final PingModel ping = pings.get(position);
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.adapter_pin_row, parent, false);
holder.pinImg = (ImageView) convertView.findViewById(R.id.pinImg);
holder.progress = (ImageView) convertView.findViewById(R.id.progress);
holder.vidImgIndicator = (ImageView) convertView.findViewById(R.id.vidImgIndicator);
Animation anim = AnimationUtils.loadAnimation(mContext, R.anim.rotating_img);
holder.progress.setAnimation(anim);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
final ViewHolder mainHolder = holder;
holder.vidImgIndicator.setVisibility(View.GONE);
final String url = ping.getLocalMediaUrl(mContext);
if (url != null) /* Image is already placed */
{
if (ping.mediaAttachmentType == PingModel.PING_MEDIA_ATTACHMENT_TYPE_PHOTO)
{
if(ping.thumbnail == null)
{
ping.thumbnail = ImageUtils.getBitmapFromFile(url, 80);
}
}
else if (ping.mediaAttachmentType == PingModel.PING_MEDIA_ATTACHMENT_TYPE_VIDEO)
{
if(ping.thumbnail == null)
{
//ping.thumbnail = ThumbnailUtils.createVideoThumbnail(url, MediaStore.Images.Thumbnails.MINI_KIND );
}
if (ping.thumbnail != null)
mainHolder.vidImgIndicator.setVisibility(View.VISIBLE);
}
mainHolder.pinImg.setImageBitmap(ping.thumbnail);
}
else
{
holder.pinImg.setImageDrawable(null);
if (ping.isMediaBeingDownloaded == false)
{
AppManager.getInstance().executor.execute(new Runnable()
{
public void run()
{
ping.isMediaBeingDownloaded = true;
ApiManager.getInstance().pingManager.downloadMediaOfPingFromServer(ping);
ping.isMediaBeingDownloaded = false;
if (ping.mediaAttachmentType == PingModel.PING_MEDIA_ATTACHMENT_TYPE_PHOTO)
{
ping.thumbnail = ImageUtils.getBitmapFromFile(url, 80);
}
else if (ping.mediaAttachmentType == PingModel.PING_MEDIA_ATTACHMENT_TYPE_VIDEO)
{
ping.thumbnail = ThumbnailUtils.createVideoThumbnail(url, MediaStore.Images.Thumbnails.MINI_KIND);
}
mContext.runOnUiThread(new Runnable()
{
#Override
public void run()
{
notifyDataSetChanged();
}
});
}
});
}
}
return convertView;
}
}
Please note the executers. Is this correct and logical way of doing it or am i doing it totally wrong and i need to make any other cache type thing?
image downloading, caching, storing in a scrolling list is a very very complex situation that I advise nobody to do themselves.
Instead you should do what most apps do, use a 3rd party library specialised for the job I'll point you to 3 of the current "best" ones, pick which ever you prefer.
Picasso - https://github.com/square/picasso
Glide - https://github.com/bumptech/glide
Fresco - https://github.com/facebook/fresco
You must always load images on a background thread even the thumbnails, unless they are already in memory.
You can use Picasso and implement your own RequestHandler which downloads the image from S3, it will give you more performance and flexibility.
Related
I have an ImageView in a ListView, the ImageView has a default image and while rendering, I asynchronously download images and load them into ImageViews. It works perfectly, but when I scroll down two items and scroll back, then the default images in the first and second row changing to the image of the sixth and seventh row.
I read these topics, but didn't find the solution.
Image change when i scroll gridview
My images changing during scroll in listview android
ListView images changing During Scroll
public class RssAdapter extends BaseAdapter {
private final List<SyndEntry> items;
private static Context context;
private static Map<String, Bitmap> mBitmapCache = new HashMap<String, Bitmap>();
public RssAdapter(Context context, List<SyndEntry> items) {
this.items = items;
this.context = context;
}
#Override
public int getCount() {
return items.size();
}
#Override
public Object getItem(int position) {
return items.get(position);
}
#Override
public long getItemId(int id) {
return id;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = new ViewHolder();
if (convertView == null) {
convertView = View.inflate(context, R.layout.rss_item, null);
holder.itemTitle = (TextView) convertView.findViewById(R.id.itemTitle);
holder.itemPubDate = (TextView) convertView.findViewById(R.id.itemPubDate);
holder.itemImg = (ImageView) convertView.findViewById(R.id.itemImg);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.itemTitle.setText(items.get(position).getTitle());
holder.itemPubDate.setText(items.get(position).getPublishedDate().toString());
List<SyndEnclosure> encls = items.get(position).getEnclosures();
if(!encls.isEmpty()){
holder.imageUrl = encls.get(0).getUrl();
}
if (!encls.isEmpty() && holder.imageUrl != null && !holder.imageUrl.equals("null")) {
holder.setImage(holder.imageUrl);
}
return convertView;
}
private static class ViewHolder {
TextView itemTitle;
TextView itemPubDate;
ImageView itemImg;
String imageUrl;
public void setImage(String imageUrl) {
this.imageUrl = imageUrl;
Bitmap imageBitmap = mBitmapCache.get(imageUrl);
if(imageBitmap!=null){
itemImg.setImageBitmap(imageBitmap);
} else {
AsyncHttpClient client = new AsyncHttpClient();
client.get(imageUrl, null, fileHandler);
}
}
FileAsyncHttpResponseHandler fileHandler = new FileAsyncHttpResponseHandler(context) {
#Override
public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) {
}
#Override
public void onSuccess(int statusCode, Header[] headers, File response) {
Bitmap imageBitmap = BitmapFactory.decodeFile(response.getPath());
itemImg.setImageBitmap(imageBitmap);
mBitmapCache.put(imageUrl, imageBitmap);
}
};
}
I tried to set a boolean variable to check whether my image was set or not and also tried to use SharedPreferences, but this part of my code didn't run when the image changing.
if(!encls.isEmpty()){
holder.imageUrl = encls.get(0).getUrl();
}
if (!encls.isEmpty() && holder.imageUrl != null && !holder.imageUrl.equals("null")) {
holder.setImage(holder.imageUrl);
}
It seems I solved the problem finally, I post the solution for the future. So, at this part of code
} else {
holder = (ViewHolder) convertView.getTag();
}
the holder object is not that which has to be rendered for the given row. Thus we have to set the parameters of the holder object for the required values. And it was ok, but I didn't set the image related parameters to null if there was no image. So, the two else part were missing:
if(!encls.isEmpty()){
holder.imageUrl = encls.get(0).getUrl();
} else {
holder.imageUrl = null;
}
if (!encls.isEmpty() && holder.imageUrl != null && !holder.imageUrl.equals("null")) {
holder.setImage(holder.imageUrl);
} else {
holder.itemImg.setImageResource(R.mipmap.news_icon);
}
Hey in this program I have set a GridView according to the text it has but when running the app the app lags alot on the emulator as well as on the device. And the error
"The application may be doing too much work on its main thread." is given
I guess thats what causing the problem here.
Here is my ImageAdapter file
public class ImageAdapter extends BaseAdapter {
static class ViewHolder {
ImageView imageView;
TextView textView;
}
private Context context;
String mobile;
private final String[] mobileValues;
int[] imagesArr = new int[]{R.drawable.badminton, R.drawable.cricket, R.drawable.basketball, R.drawable.carrom,
R.drawable.handball, R.drawable.humanfoosball, R.drawable.kabaddi, R.drawable.khokho, R.drawable.chess,
R.drawable.longjump, R.drawable.streetsoccer, R.drawable.shotput, R.drawable.volleyball, R.drawable.tugofwar,
R.drawable.tabletennis, R.drawable.handball, R.drawable.rellayrace};
public ImageAdapter(Context context, String[] mobileValues) {
this.context = context;
this.mobileValues = mobileValues;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
View row = convertView;
if (row == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//row = new View(context);
// get layout from grid_itemxml.xml
row = inflater.inflate(R.layout.grid_item, null);
// set value into textview
holder.textView = (TextView) row.findViewById(R.id.grid_item_label);
holder.textView.setText(mobileValues[position]);
// set image based on selected text
holder.imageView = (ImageView) row.findViewById(R.id.grid_item_image);
mobile = mobileValues[position];
if (mobile.equals("Badminton")) {
holder.imageView.setImageResource(imagesArr[0]);
}
if (mobile.equals("Cricket")) {
holder.imageView.setImageResource(imagesArr[1]);
}
if (mobile.equals("Basketball")) {
holder.imageView.setImageResource(imagesArr[2]);
}
if (mobile.equals("Carrom")) {
holder.imageView.setImageResource(imagesArr[3]);
}
if (mobile.equals("Handball")) {
holder.imageView.setImageResource(imagesArr[4]);
}
if (mobile.equals("Human Foosball")) {
holder.imageView.setImageResource(imagesArr[5]);
}
if (mobile.equals("Kabaddi")) {
holder.imageView.setImageResource(imagesArr[6]);
}
if (mobile.equals("Khokho")) {
holder.imageView.setImageResource(imagesArr[7]);
}
if (mobile.equals("Chess")) {
holder.imageView.setImageResource(imagesArr[8]);
}
if (mobile.equals("Longjump")) {
holder.imageView.setImageResource(imagesArr[9]);
}
if (mobile.equals("Streetsoccer")) {
holder.imageView.setImageResource(imagesArr[10]);
}
if (mobile.equals("Shotput")) {
holder.imageView.setImageResource(imagesArr[11]);
}
if (mobile.equals("Volleyball")) {
holder.imageView.setImageResource(imagesArr[12]);
}
if (mobile.equals("Tugofwar")) {
holder.imageView.setImageResource(imagesArr[13]);
}
if (mobile.equals("Table Tennis")) {
holder.imageView.setImageResource(imagesArr[14]);
}
if (mobile.equals("Relayrace")) {
holder.imageView.setImageResource(imagesArr[15]);
}
} else {
holder = (ViewHolder) row.getTag();
}
return row;
}
#Override
public int getCount() {
return mobileValues.length;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
The images inside your project are taking too much time to load the images, though the android memory resources are limited to app.There are couple of solution that you can try
Keep image size low (downside will be bad quality on bigger screens).
Compress image to required screen resolution size at run time.
The easy way is use image loading libraries. Try this link for details
e.g
Picasso.with(context).load(R.drawable.drawableName).fit().centerCrop().into(imageViewFit)
I have created an app with a listview with arrayadapter that the user can dynamically populate. I store the info entered as a json string in the preferences and when I refresh the app, I get the list with the entries. The thing is that I want the image next to each entry to change after a network operation. The problem I'm facing seems to be that since the elements in the list are added dynamically, I dont seem to find a good way neither to update the imageview on the onPostExecute() method, either to be able to target each entry specifically since they share the same layout ids.
Here is my getView() method inside my adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.product_list_item, null);
holder = new ViewHolder();
holder.deviceName = (TextView) convertView
.findViewById(R.id.txt_pc_name);
holder.deviceIp = (TextView) convertView
.findViewById(R.id.txt_pdt_desc);
holder.devicePort = (TextView) convertView
.findViewById(R.id.txt_pdt_price);
holder.favoriteImg = (ImageView) convertView
.findViewById(R.id.imgbtn_favorite);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Devices device = (Devices) getItem(position);
holder.deviceName.setText(device.getName());
holder.deviceIp.setText(device.getIpOnline());
holder.devicePort.setText(device.getPortOnline() + "");
return convertView;
}
Here is my AsyncTask:
public class Connection extends AsyncTask<String, Void, String> {
private String ipOnline;
private int portOnline;
private String ipWol;
private int portWol;
private String macAddress;
private ImageView img;
private Context context;
public interface AsyncResponse {
void processFinish(String output);
}
public AsyncResponse delegate = null;
public Connection(Context mContext, ImageView Img,String IpOnline, int PortOnline, String IpWol, int PortWol, String MacAddress, AsyncResponse delegate) {
ipOnline = IpOnline;
portOnline = PortOnline;
ipWol = IpWol;
portWol = PortWol;
macAddress = MacAddress;
context = mContext;
// inflate = Inflate;
img = Img;
// spin = spinner;
this.delegate = delegate;
}
public int status;
#Override
protected String doInBackground(String... arg0) {PreferenceManager.getDefaultSharedPreferences(lastContext);
try {
Socket echoSocket = new Socket();
echoSocket.connect(new InetSocketAddress(ipOnline,portOnline),2000);
if(echoSocket.isConnected())
status = 1;
} catch (Exception e) {
status = 0;
}
if(status == 0)
return "0";
else
return "1";
}
#Override
protected void onProgressUpdate(Void... values) {}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
ImageView img = (ImageView) activity.findViewById(R.id.imgbtn_favorite);
if (status == 0)
img.setImageResource(android.R.drawable.presence_offline);
else
img.setImageResource(android.R.drawable.presence_online);
delegate.processFinish(result);
}
}
And here is my call to it:
new Connection(activity, img, product.getIpOnline(), Integer.parseInt(product.getPortOnline()), product.getIpWol(), Integer.parseInt(product.getPortWol()), product.getMacAddress(), new Connection.AsyncResponse() {
#Override
public void processFinish(String output) {
}
}).execute();
You can use view holder pattern or recyclerview .
You need to store the reference of image view in holder and can update the image with the help of this reference instead of id of view
I came up with a solution.
I created a public variable on my adapter and I'm adding all the images:
public List<ImageView> allImages = new ArrayList<ImageView>();
public List<ImageView> getAllImages(){
return this.allImages;
}
this.allImages.add((ImageView) convertView
.findViewById(R.id.imgbtn_favorite));
Then on my fragmented activity onCreateView method I deployed a delayed runnable:
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
updateStatus();
}
}, 500);
the updateStatus() method initializes the images variable and begins network checks to determine which image to use. Then it applies it accordingly.
public void updateStatus() {
List<ImageView> images = deviceListAdapter.getAllImages();
if(count > 0 && images.size() > 0) {
for (int i = 0; i < deviceListAdapter.getCount() ; i++) {
Devices product = (Devices) deviceListAdapter.getItem(i);
if((TextUtils.isDigitsOnly(product.getPortOnline()) && TextUtils.isDigitsOnly(product.getPortWol())) && (!product.getPortOnline().isEmpty() && !product.getPortWol().isEmpty())) {
new Connection(activity, images.get(i), product.getIpOnline(), Integer.parseInt(product.getPortOnline()), product.getIpWol(), Integer.parseInt(product.getPortWol()), product.getMacAddress(), new Connection.AsyncResponse() {
#Override
public void processFinish(Boolean output) {
}
}).execute();
}
}
}
}
It might not be optimal but feel free to add a better solution.
I m facing a problem when I want to load images from an adapter using the ion library.
In fact, I have items with a string corresponding to the url of the iconic image that I want to load for each item on my gridview.
The problem is due to the adapter view management (reusing existing view if I m not wrong), and I dont know how to bypass this...
For example, if I load 10 elements with an image, the first time it works. Then, when I scroll to bottom, and then I scroll to top, the image changes (due to the reuse of the existing view...)
Can you help me ?
This is my adapter code :
public class ProtocoleAdapter extends BaseAdapter {
private Context context;
private List<ProtocoleItem> mListe;
public ProtocoleAdapter(Context context, List<ProtocoleItem> liste) {
this.context = context;
this.mListe = liste;
}
private class ViewHolder {
TextView txtTitre;
ImageView img;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater
.inflate(R.layout.grid_item, null);
holder = new ViewHolder();
holder.txtTitre = (TextView) convertView
.findViewById(R.id.grid_item_label);
holder.img = (ImageView) convertView
.findViewById(R.id.grid_item_image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final ProtocoleItem rowItem = mListe.get(position);
boolean isLoaded = false;
try {
Bitmap bitmap = Ion.with(context)
.load(rowItem.getImage())
.asBitmap()
.get();
isLoaded = true;
holder.img.setImageBitmap(bitmap);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
if (!isLoaded) {
if (position % 5 == 0) {
holder.img.setBackgroundColor(0xff176799);
} else {
if (position % 4 == 0) {
holder.img.setBackgroundColor(0xff2F87B0);
} else {
if (position % 3 == 0) {
holder.img.setBackgroundColor(0xff42A4BB);
} else {
if (position % 2 == 0) {
holder.img.setBackgroundColor(0xff5BC0C4);
} else {
holder.img.setBackgroundColor(0xff78D6C7);
}
}
}
}
}
holder.txtTitre.setText(rowItem.getTitre());
return convertView;
}
Thanks for all !
Have a good day
Get the latest version of Ion and use the following.
Ion.with(holder.img)
.load(rowItem.getImage());
This method will load asynchronously.
Your current usage is blocking the UI thread. Ion should properly handle convertView recycling, so that is not an issue.
This is lazy loading problem, go with Universal image loader: https://github.com/nostra13/Android-Universal-Image-Loader
https://github.com/nostra13/Android-Universal-Image-Loader/blob/master/sample/src/com/nostra13/example/universalimageloader/ImagePagerActivity.java
Help to understand whether there is a memory leak or not.
Fragment
public class NewsFragment extends Fragment {
//some code
private OnNewsFeedsContentClickListener onNewsFeedsContentClickListener = new OnNewsFeedsContentClickListener()
{
#Override
public void onClick(String sYoutubeId, ContentType type)
{
//some code
}
}
}
Adapter
public class HowToAdapter extends BaseAdapter {
public static final String TAG = "HowToAdapter";
private List<ContentSkill> mContentList = null;
private Context mContext = null;
private ImageLoader mImageLoader = null;
private OnNewsFeedsContentClickListener mOnNewsFeedsContentClickListener = null;
public HowToAdapter(Context context, List<ContentSkill> contetnList, OnNewsFeedsContentClickListener onNewsFeedsContentClickListener)
{
mContext = context;
mContentList = contetnList;
mImageLoader = new ImageLoader(mContext, ImageType.HOW_TO);
mOnNewsFeedsContentClickListener = onNewsFeedsContentClickListener;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
HowToView view = null;
if (convertView == null)
{
view = new HowToView(mContext);
}
else
{
view = (HowToView) convertView;
}
ContentSkill content = getItem(position);
if (content != null)
{
ViewHolder holder = (ViewHolder) view.getTag();
final String url = Utils.getYouTubeUrlImageFromUrl(content.getUrl());
final String youtubeId = Utils.getYoutubeIdFromUrl(content.getUrl());
mImageLoader.displayImage(url, holder.pIvThumb);
holder.pIvThumb.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
mOnNewsFeedsContentClickListener.onClick(youtubeId, ContentType.VIDEO);
}
});
}
return view;
}
}
Questions:
The adapter variable final String youtubeId will always be created,
but not killed? That is, each time getView() will be created but
the old youtubeId not clear from memory?
Each time call getView() OnClickListener will also be created, and the old
will be cleaned out of memory?
I think that the variables will be re-created each time and catch-up with the old memory.