Grid view gallery reading from SD card - java

I have a grid view that loads all the images from a specific folder into itself. I'm trying to get it so that when a user click an individual image within the grid view it sets an enlarged version of itself within an image view. I think what I need to try and do is when the grid view is created and the images loaded into it, I need to attach an array to both the imageview created holding the image within the gridview and the location of it within the SD card. So when an image is loaded; save location of image and attach it to the imageview within gridview. What I have so far loads the last picture loaded from the SD card into the enlarged image view.
Main.java
public class Main extends Activity{
ImageView photoHolder;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery);
photoHolder = (ImageView) findViewById(R.id.photoHolderView);
photoHolder.setVisibility(View.GONE);
GridView gridView = (GridView) findViewById(R.id.grid_view);
gridView.setVisibility(View.VISIBLE);
gridView.setAdapter(new ImageAdapter(this));
gridView.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> a, View view, int position, long id) {
photoHolder.setVisibility(View.VISIBLE);
photoHolder.setImageURI(ImageAdapter.uri);
}
});
}
}
ImageAdapter.java
public class ImageAdapter extends BaseAdapter {
public static Uri uri;
private Context mContext;
File root = new File(Environment.getExternalStorageDirectory()
+ File.separator + "Custom" + File.separator);
private File[] fileName = root.listFiles();
int count = fileName.length;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return fileName.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
uri = Uri.fromFile(fileName[position]);
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some
// attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageURI(uri);
return imageView;
}
}
On a side note it crashes quite a lot due to memory leaks, any idea how I would create a "sample/thumbnail" size image whilst loading it from the SD card?

You are right in stating that you need to keep track of the filenane/location for each of the thumbnail. The easiest way would be to amend your getView method and add one line:
imageView.setImageURI(uri);
imageView.setTag(uri); //new line added
return imageView;
Then, in your onItemClickListener you get the clicked imageview - and get its tag - that will be the filename on the SD card:
public void onItemClick(AdapterView<?> a, View view, int position, long id) {
photoHolder.setVisibility(View.VISIBLE);
photoHolder.setImageURI((Uri)view.getTag());
}
As for the memory issue when loading the bitmap, you can use BitmapFactory.Options to indicate the scaling factor - have a look at this page on Android developer website - it contains the sample code you need.

Related

scroll lag in Custom ListView

My project is a tuition management book in this I list all students with their photos, names, IDs, classes in Activity by using a custom listView it runs well but when adding more student it showing lag in listview scrolling. using the below adapter I am facing scroll lag in listView. I reduce the size of the image(under 500kb)which store in the SQLite database but still it's not working smoothly. **
Adapter code
public class MyListAdapter extends BaseAdapter {
private Context context;
private int layout;
private ArrayList<slmodel> recordlist;
public MyListAdapter(Activity context, int layout, ArrayList<slmodel> recordlist) {
this.context = context;
this.layout= layout;
this.recordlist = recordlist;
}
#Override
public int getCount() {
return recordlist.size();
}
#Override
public slmodel getItem(int position) {
return recordlist.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
private class ViewHolder{
ImageView imageView;
TextView name,id, sclass;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row=convertView;
ViewHolder holder=new ViewHolder();
if(row==null)
{
LayoutInflater inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row=inflater.inflate(layout,null);
holder.name=row.findViewById(R.id.bname);
holder.id=row.findViewById(R.id.sid);
holder.sclass =row.findViewById(R.id.scourse);
holder.imageView=row.findViewById(R.id.imageView);
row.setTag(holder);
}
else{
holder=(ViewHolder)row.getTag();
}
slmodel model=recordlist.get(position);
holder.name.setText(model.getName());
holder.id.setText(model.getId().toString());
holder.sclass.setText(model.getsclass());
byte[] recordimgae=model.getImgae();
Bitmap bitmap= BitmapFactory.decodeByteArray(recordimgae,0,recordimgae.length);
holder.imageView.setImageBitmap(bitmap);
return row;
}
}
Welcome to SO, and congratulations on posting your first question.
use image loading libraries like Picasso or Glide
with these libraries, you can:
resize your image
use caches to decrease loading time
handle background thread automatically for loading and converting bitmaps.
sample for Glide:
Glide.with(context)
.load(recordImage)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(holder.imageView);

"Flickering images" with ViewHolder pattern when downloading images from server

I want to create a list of Views, where the images shown in each View is downloaded from a server as you scroll the list (lazy loading). This is the code I have got so far:
public class CustomAdapter extends ArrayAdapter<Component> {
private final List<Component> components;
private final Activity activity;
public CustomAdapter(Activity context, List<Component> components) {
super(context, android.R.layout.simple_list_item_1, components);
this.components = components;
this.activity = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
convertView = inflater.inflate(R.layout.item, null, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)convertView.getTag();
}
Component component = components.get(position);
// Don't show any image before the correct one is downloaded
viewHolder.imageView.setImageBitmap(null);
if (component.getImage() == null) { // Image not downloaded already
new DownloadImageTask(viewHolder.imageView).execute(component);
} else {
viewHolder.imageView.setImageBitmap(component.getImage());
}
return convertView;
}
private class ViewHolder {
ImageView imageView;
}
private class DownloadImageTask extends AsyncTask<Component, Void, Component> {
private ImageView imageView;
public DownloadImageTask(ImageView imageView) {
this.imageView = imageView;
}
#Override
protected Component doInBackground(Component... params) {
String url = params[0].getImageURL();
Component component = params[0];
// Download the image using the URL address inside found in component
Bitmap image = ImageDownloader.getImage(url);
// Set the Bitmap image to the component so we don't have to download it again
component.setImage(image);
return component;
}
#Override
protected void onPostExecute(Component component) {
// Update the ImageView with the downloaded image and play animation
imageView.setImageBitmap(component.getImage());
Animation animation = AnimationUtils.loadAnimation(activity, R.anim.fade_in);
imageView.startAnimation(animation);
}
}
}
Basically, when getView() is run it gets data (in this case a Bitmap) from Component (which is used to cache data from the item), unless there is no data. In that case it executes the DownloadImageTask which will download the image and store it inside a Component. Once it's stored it puts the image in the ImageView.
My problem is that when using the ViewHolder pattern the ImageViews, instead of the "wrong way" (calling findViewById() each time), scrolling through the list will make the wrong ImageViews get the downloaded Bitmap. This gif shows how it looks:
Preview
Obviously, I want the images to only appear where they should. Is there any good way to make this work as supposed to?
I used Glide to solve the problem. Thanks everyone for informing me there already existed such wonderful things!
Doing (almost) the same thing was as easy as:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
convertView = inflater.inflate(R.layout.item, null, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
String url = components.get(position).getImageURL();
Glide.with(activity).load(url).crossFade().into(viewHolder.imageView);
return convertView;
}
While others are right that there are many libraries that can handle this for you, it is possible to do it the way you tried initially if you really want to. You just need to make sure you cancel AsyncTasks when the item view is recycled. You can do this by adding the AsyncTask to your ViewHolder class, so you can see if there's something running and cancel it before starting a new one.
private class ViewHolder {
ImageView imageView;
AsyncTask<?,?,?> task;
}
Then in your getView():
#Override
public View getView(int position, View convertView, ViewGroup parent) {
...
// Don't show any image before the correct one is downloaded
viewHolder.imageView.setImageBitmap(null);
if (viewHolder.task != null) { // Existing task may be executing
viewHolder.task.cancel(true);
viewHolder.task = null;
}
if (component.getImage() == null) { // Image not downloaded already
AsyncTask<?,?,?> task = new DownloadImageTask(viewHolder.imageView);
viewHolder.task = task;
task.execute(component);
} else {
viewHolder.imageView.setImageBitmap(component.getImage());
}
return convertView;
}

Best way to load images in a Adapter

I am currently having a little problem with my application. It is a music player and is working perfectly, except for one annoyance. When I run my SongAdapter, it will load a lot of views with the songs that are present on the device. However, this is way too heavy to load on the Main Thread, therefore I tried running it in the background. However, this isn't fast enough.
Main question:
Is there any fast and reliable way to load the images into the views.
Here is the code of my SongAdapter.java:
public class SongAdapter extends BaseAdapter {
//song list and layout
private ArrayList<Song> songs;
private LayoutInflater songInf;
//constructor
public SongAdapter(Context c, ArrayList<Song> theSongs){
songs=theSongs;
songInf=LayoutInflater.from(c);
}
#Override
public int getCount() {
return songs.size();
}
#Override
public Object getItem(int arg0) {
return null;
}
#Override
public long getItemId(int arg0) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//map to song layout
RelativeLayout songLay = (RelativeLayout) songInf.inflate(R.layout.song_list_item, parent, false);
//get title and artist views
TextView songView = (TextView)songLay.findViewById(R.id.titleListTextView);
TextView artistView = (TextView)songLay.findViewById(R.id.artistListTextView);
RelativeLayout relativeLayout = (RelativeLayout)songLay.findViewById(R.id.layoutSelector);
RoundedImageView albumView = (RoundedImageView)songLay.findViewById(R.id.albumListImageView);
//get song using position
Song currSong = songs.get(position);
//get title and artist strings
albumView.setImageBitmap( getAlbumart(currSong, parent.getContext()) );
songView.setText(currSong.getTitle());
artistView.setText(currSong.getArtist());
relativeLayout.setTag( currSong.getID());
return songLay;
}
public Bitmap getAlbumart(Song currentSong, Context context ) {
Bitmap bm = BitmapFactory.decodeResource(context.getResources(), R.drawable.album_default);
long albumId = (long) currentSong.getAlbumId();
try {
final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
Uri uri = ContentUris.withAppendedId(sArtworkUri, albumId);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
if (pfd != null) {
FileDescriptor fd = pfd.getFileDescriptor();
bm = BitmapFactory.decodeFileDescriptor(fd);
}
} catch (Exception e) {
Log.d("Var log", "Error:" + e);
}
return bm;
}
}
Thanks for the help.
aside of using Picasso or any other library to load bitmaps.
below is a code same to recycle views, just replace the setImageBitmap() with the one you are usign now (Picasso or whatever)
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHodler holder = null;
if(convertView == null){
holder = new ViewHodler();
convertView = songInf.inflate(R.layout.song_list_item, parent, false);
holder.songView = (TextView)convertView.findViewById(R.id.titleListTextView);
holder.artistView = (TextView)convertView.findViewById(R.id.artistListTextView);
holder.albumView = (RoundedImageView)convertView.findViewById(R.id.albumListImageView);
holder.relativeLayout = (RelativeLayout)convertView.findViewById(R.id.layoutSelector);
convertView.setTag(holder);
}else{
holder =(ViewHodler) convertView.getTag();
}
//get song using position
Song currSong = songs.get(position);
//get title and artist strings
holder.albumView.setImageBitmap( getAlbumart(currSong, parent.getContext()) ); // replace getAlbumart() with the new way you are using
holder.songView.setText(currSong.getTitle());
holder.artistView.setText(currSong.getArtist());
holder.relativeLayout.setTag( currSong.getID());
return convertView;
}
class ViewHodler{
TextView songView;
TextView artistView;
RelativeLayout relativeLayout;
RoundedImageView albumView;
}
PS: getting RelativeLayout i mean this R.id.layoutSelector
if it's a big view it will load more memory.
Universal Image Loader aims to provide a powerful, flexible and highly customizable instrument for image loading, caching and displaying. It provides a lot of configuration options and good control over the image loading and caching process.
It works for me, hope it will solve your problem.

Error when set an ImageAdapter in Inflate Layout?

I am trying to make a gridview on LayoutInflater, when i test my app, it always crashes.
here is my code :
public class Level1 extends Fragment {
public static Fragment newInstance(Context context) {
Level1 f = new Level1();
return f;
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.grid_layout, null);
GridView gridView = (GridView) root.findViewById(R.id.grid_view);
gridView.setAdapter(new ImageAdapter(root.getContext()));
}
I think, my problem is in "setAdapter". I can't use context "xxx.this". I've try to change setAdapter context with "getContext" and "getApplicationContext" but it still crashes.
when i delete "setAdapter" my app working but without gridView.
My ImageAdapter is look like this :
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public Integer[] mThumbIds = {
R.drawable.pic_1, R.drawable.pic_2,
R.drawable.pic_3, R.drawable.pic_4,
R.drawable.pic_5, R.drawable.pic_6,
R.drawable.pic_7, R.drawable.pic_8,
R.drawable.pic_9, R.drawable.pic_10,
R.drawable.pic_11, R.drawable.pic_12,
R.drawable.pic_13, R.drawable.pic_14,
R.drawable.pic_15
};
// Constructor
public ImageAdapter(Context c){
mContext = c;
}
#Override
public int getCount() {
return mThumbIds.length;
}
#Override
public Object getItem(int position) {
return mThumbIds[position];
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(mThumbIds[position]);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(70, 70));
return imageView;
}
}
Please help me..
Please set a breakpoint on this line GridView gridView = (GridView) root.findViewById(R.id.grid_view); and start debugging your app (in eclipse it's the button on the left side of the button you usually use to start your application). When the breakpoint is reached please step over one step and see if gridView is null. For me this looks like the most reasonable source of your error. But without a detailled error message it's hard to say, so please update your post.
if you are getting OUTOFMEMORY exception its generally because you are using large size images. you will have to sample it down. and in fragment you have to use getactivity() for context.

Parsing error - Android java

I'm trying to fill a gridview dynamically with icons. I Followed the gridview hello tutorial. However the array with the images is not always exactly the same. Depending on the action before, a different image array is given (extracted from soap response), which is constituted of the icon names, e.g. agenda => agenda.png. I wanted to create the array by looping through the array and adding it with R.drawable + icon_name. However R.drawable is not able to parse to the requested Integer array.
public class ImageAdapter extends BaseAdapter
{
private Context mContext;
final ArrayList<String> image = getIntent().getStringArrayListExtra("image");
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return icoontjes.size();
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public ArrayList<Integer> icoontjes;
{
for (int i=0; i<image.size(); i++){
Integer icon= Integer.valueOf("R.drawable."+image.get(i));
icoontjes.add(icon);
}
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(icoontjes.get(position));
return imageView;
}
}
You can use
for (int i=0; i < image.size(); i++) {
Integer icon = getResources().getIdentifier(image.get(i), "drawable", "your.package");
icoontjes.add(icon);
}
where your.package is the base package of your android application (the package in which you have your static final R class defined.
This way the icon variable will hold the id of your drawable based on your image.get(i).
i can give u an answer but you shouldn't (really shouldn't) do this ...
instead of
Integer icon= Integer.valueOf("R.drawable."+image.get(i));
try
R.class.getField("R.drawable."+image.get(i)).getInt(null)

Categories

Resources