Ive searched and cant find anything to do with this problem im getting, im loading images into multiple grid views each inside a tabbed page view. Performance is great but any grid view that has lots of images and needs to scroll the images loaded are incorrect, it dublicates images and loads them into the wrong position. Below is one of the adapters im using for the grid view:
public class PcAdapter extends BaseAdapter {
private Context context;
private Integer[] imageIds = {
R.drawable.pcserioussam, R.drawable.pc_trinetwo,
R.drawable.pc_leftfordead, R.drawable.pc_dungeondefenders,
R.drawable.pc_portaltwo, R.drawable.pc_spaz,
R.drawable.pc_laracroftattoo, R.drawable.pc_goatsim,
R.drawable.pc_deadblock, R.drawable.pc_dynasty,
R.drawable.pc_minecraft, R.drawable.pc_kanelynch,
R.drawable.pc_toy, R.drawable.pc_awesomenauts,
R.drawable.pc_bioniccomm, R.drawable.pc_fastandfurious,
R.drawable.gen_harryone, R.drawable.gen_harrytwo,
R.drawable.gen_watchmen
};
public PcAdapter(Context c) {
context = c;
}
public int getCount() {
return imageIds.length;
}
public Object getItem(int position) {
return imageIds[position];
}
public long getItemId(int position) {
return 0;
}
public View getView(int position, View view, ViewGroup parent) {
ImageView iview;
if(view == null){
iview = new ImageView(context);
Picasso.with(context).load(imageIds[position]).
placeholder(R.drawable.placeholder).
resize(230, 300).centerInside().into(iview);
} else {
iview = (ImageView) view;
}
return iview;
}
}
Any help with this would be much appreciated
Move your picasso code out of the if statement. Currently it will only load a new image if view is null? Put it at the bottom of the if statement just before the return statement.
Related
I'm using listview custom adapter which with row click i'm changing row color. But when i'm scrolling bot and up again it doesnt have the right position.
It changes color in other rows...
public override View GetView(int position, View convertView, ViewGroup parent)
{
DataViewHolder holder = null;
if (convertView == null)
{
convertView = LayoutInflater.From(mContext).Inflate(Resource.Layout.TableItems, null, false);
holder = new DataViewHolder();
holder.txtDescription = convertView.FindViewById<TextView>(Resource.Id.txtDescription);
holder.txtDescription.Click += delegate
{
holder.txtDescription.SetBackgroundColor(Color.Red);
};
convertView.Tag = holder;
}
else
{
holder = convertView.Tag as DataViewHolder;
}
holder.txtDescription.Text = mitems[position].Description;
return convertView;
}
public class DataViewHolder : Java.Lang.Object
{
public TextView txtDescription { get; set; }
}
It looks like it doesnt keep in memory specific row situation.
Don't change the color in the click handler directly, instead change the data from which the adapter draws from and use that to change the color when GetView is called again.
ListView recycles the views it uses to optimize scrolling, instead it just expects the view to represent the data. If you change a color of one view directly, the view then gets recycled and you'll see "another view" (another part of the data) with a different background color.
So in summary: give each data point a color attribute and use that to set the color of each view in GetView, change the data and notify the adapter about the changes to the data.
Edit
I've never used Xamarin but maybe something like this would work
public override View GetView(int position, View convertView, ViewGroup parent)
{
DataViewHolder holder = null;
if (convertView == null)
{
convertView = LayoutInflater.From(mContext).Inflate(Resource.Layout.TableItems, null, false);
holder = new DataViewHolder();
holder.txtDescription = convertView.FindViewById<TextView>(Resource.Id.txtDescription);
holder.txtDescription.Click += delegate
{
// instead of setting the color directly here, just modify the data
(holder.txtDescription.Tag as ItemType).ItemColor = Color.Red
notifyDataSetChanged();
};
convertView.Tag = holder;
}
else
{
holder = convertView.Tag as DataViewHolder;
}
holder.txtDescription.Text = mitems[position].Description;
holder.txtDescription.Tag = mitems[position]; // this so that the click handler knows which item to modify
holder.txtDescription.SetBackgroundColor(mitems[position].ItemColor);
return convertView;
}
public class DataViewHolder : Java.Lang.Object
{
public TextView txtDescription { get; set; }
}
ListView will reuse the item layout, you can use List and View.Tag to avoid the problem caused by reusing.
I have posted my demo on github.
I'm using FancyCoverFlow to show a CoverView in my Android App.
All works fine but the only think I can't do is overlap the items.
This is what I'm trying to do: preview
I tried using
coverFlow.setSpacing(-90);
or other configuration (such as -200 or -50), but setSpacing doesn't change anything in my final result.
This is the Adapter:
coverFlowAdapter = new FancyCoverFlowAdapter() {
#Override
public View getCoverFlowItem(int position, View reuseableView, ViewGroup viewGroup) {
v = reuseableView;
if (v == null) {
v = LayoutInflater.from(activity).inflate(R.layout.coverflo_item, viewGroup, false);
}
//do my stuff...
return v;
}
#Override
public int getCount() {
//do my stuff...
}
#Override
public Home getItem(int position) {
//do my stuff...
}
#Override
public long getItemId(int position) {
return 0;
}
};
These are coverFlow setting:
coverFlow.setAdapter(coverFlowAdapter);
coverFlow.setUnselectedSaturation(0.0f);
coverFlow.setSpacing(0);
coverFlow.setMaxRotation(0);
coverFlow.setScaleDownGravity(0.2f);
coverFlow.setReflectionEnabled(false);
coverFlow.setUnselectedAlpha(1.0f);
coverFlow.setActionDistance(FancyCoverFlow.ACTION_DISTANCE_AUTO);
coverFlow.setUnselectedScale(0.75f);
There is another StackOverflow question about this issue, but is not useful:
Android Cover Flow gallery and remove space between
I tried also other sources, but I found only coverFlow libraries that use image as item, while I need to use my custom layout.
Thanks.
I can download image and display it in ListView. But the problem I am facing is,
When I am loading the images they all get loaded in the first row of the list. It shows being loaded one by one in the first row. While the other rows hold the default image. Its look weird. What to do. The code below:
private class simpsync extends AsyncTask<String, Integer , Bitmap>{
private final WeakReference imageViewReference;
simpsync(ImageView iv){
//imageView=iv;
imageViewReference=new WeakReference(iv);
}
#Override
protected Bitmap doInBackground(String... param) {
Bitmap bmp=CommonFunctions.overlay(CommonFunctions.loadUrlBitmap(param[0]));
return bmp;
}
protected void onPostExecute(Bitmap bitmap) {
//imageView.setImageBitmap(result);
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null) {
ImageView imageView = (ImageView) imageViewReference.get();
if (imageView != null) {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
}
This code is the getView function of class BaseAdapter
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
ViewHolder holder;
if(convertView==null){
/****** Inflate tabitem.xml file for each row ( Defined below ) *******/
//list_book_detail_entry
if(requestType==SearchAndIndex.SEARCH_IN_SEPAERATE)
{
vi = inflater.inflate(R.layout.list_book_detail_buy, parent, false);
//vi = inflater.inflate(R.layout.list_book_detail_buy, null);
}
else{
vi = inflater.inflate(R.layout.list_book_detail_entry, parent, false);
//vi = inflater.inflate(R.layout.list_book_detail_entry, parent, false);
}
/****** View Holder Object to contain tabitem.xml file elements ******/
holder = new ViewHolder();
holder.bookTitle=(TextView)vi.findViewById(R.id.BookTitle);
holder.writer = (TextView) vi.findViewById(R.id.WriterName);
holder.imageUrl=(ImageView)vi.findViewById(R.id.ImageUrl);
holder.isbn=(TextView)vi.findViewById(R.id.BookISBN);
holder.serialNumber=(TextView)vi.findViewById(R.id.BookSerialNumber);
holder.availabilityView=(ImageView)vi.findViewById(R.id.AvailabilityView);
holder.publisher=(TextView)vi.findViewById(R.id.Publisher);
holder.publishingDate=(TextView)vi.findViewById(R.id.PublishingDate);
/************ Set holder with LayoutInflater ************/
vi.setTag( holder );
}
else
holder=(ViewHolder)vi.getTag();
if(data.size()<=0)
{
holder.bookTitle.setText("--");
holder.writer.setText("--");
holder.publisher.setText("--");
holder.publisher.setText("----+--+--");
}
else
{
tempValues=null;
tempValues = ( BookDetailsStruct ) data.get( position );
holder.writer.setText( tempValues.Writer );
holder.publisher.setText(tempValues.Publisher);
holder.publishingDate.setText(tempValues.getIssueDetail(0).publishingDate);
simpsync sp=new simpsync(holder.imageUrl);
if(requestType==SearchAndIndex.SEARCH_IN_SEPAERATE)
{
if(tempValues.getIssueDetail(0)!=null)
{
String toAdd;
if(tempValues.getIssueDetail(0).serialNumber==-1)
toAdd="";
else
toAdd=" [ 巻"+tempValues.getIssueDetail(0).serialNumber+" ]";
holder.bookTitle.setText( tempValues.BookName+toAdd);
sp.execute(tempValues.getIssueDetail(0).smallImageUrl);
}
}
else{
if(tempValues.largetNumberIndex!=-1)
{
String toAdd;
if(tempValues.getIssueDetail(tempValues.largetNumberIndex).serialNumber==-1)
toAdd="";
else
toAdd=" ("+tempValues.getIssueCount()+"巻)";
holder.bookTitle.setText( tempValues.BookName+toAdd);
sp.execute(tempValues.getIssueDetail(tempValues.largetNumberIndex).smallImageUrl);
}
else{
holder.bookTitle.setText( tempValues.BookName);
sp.execute(tempValues.getIssueDetail(0).smallImageUrl);
}
}
vi.setOnClickListener(new OnItemClickListener( position ));
}
return vi;
}
if you have any further question, please let me know
The "problem" is the recycle behavior of ListView. You are not respecting it enough. When you scroll down and a View disappears on the top, it will be reused at the bottom (thats why you use the ViewHolder pattern, thats good). But you also start a asynchronous task and give it the ImageView and to hold onto it. Since the whole view of the row (and with that the imageview) gets recycled, it wont be eligible for garbage collection, thus the asynctask has a valid ImageView to display the image once its finished.
To correct your code, I suggest you simply adapt what is written on the android developer page, it nearly is copy-past-ready code for you to use:
Load Bitmaps into a GridView Implementation
You can also use 3rd party libraries, because other smart people have also faced this problem and came up with good solutions:
Glide
Picasso
They both have a very (very) simple Api to get things done and they are both highly efficient and tunable, with already good settings by default.
You can use Picasso for loading images into ListView
#Override
public void getView(int position, View convertView, ViewGroup parent) {
SquaredImageView view = (SquaredImageView) convertView;
if (view == null) {
view = new SquaredImageView(context);
}
String url = getItem(position);
Picasso.with(context).load(url).into(view);
}
OR
#Override
public void getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
ImageView imgView = v.findViewById(R.id.someImageView);
String url = getItem(position);
Picasso.with(context).load(url ).into(imgView);
}
Many common pitfalls of image loading on Android are handled automatically by Picasso:
Handling ImageView recycling and download cancelation in an adapter.
Complex image transformations with minimal memory use.
Automatic memory and disk caching.
I have a ListView which uses a custom adapter as shown:
private class CBAdapter extends BaseAdapter implements OnCheckedChangeListener{
Context context;
public String[] englishNames;
LayoutInflater inflater;
CheckBox[] checkBoxArray;
LinearLayout[] viewArray;
private boolean[] checked;
public CBAdapter(Context con, String[] engNames){
context=con;
englishNames=engNames;
inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
checked= new boolean[englishNames.length];
for(int i=0; i<checked.length; i++){
checked[i]=false;
//Toast.makeText(con, checked.toString(),Toast.LENGTH_SHORT).show();
}
checkBoxArray = new CheckBox[checked.length];
viewArray = new LinearLayout[checked.length];
}
public int getCount() {
return englishNames.length;
}
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
if(viewArray[position] == null){
viewArray[position]=(LinearLayout)inflater.inflate(R.layout.record_view_start,null);
TextView tv=(TextView)viewArray[position].findViewById(R.id.engName);
tv.setText(englishNames[position]);
checkBoxArray[position]=(CheckBox)viewArray[position].findViewById(R.id.checkBox1);
}
checkBoxArray[position].setChecked(checked[position]);
checkBoxArray[position].setOnCheckedChangeListener(this);
return viewArray[position];
}
public void checkAll(boolean areChecked){
for(int i=0; i<checked.length; i++){
checked[i]=areChecked;
if(checkBoxArray[i] != null)
checkBoxArray[i].setChecked(areChecked);
}
notifyDataSetChanged();
}
public void onCheckedChanged(CompoundButton cb, boolean isChecked) {
for(int i=0; i<checked.length; i++){
if(cb == checkBoxArray[i])
checked[i]=isChecked;
}
}
public boolean itemIsChecked(int i){
return checked[i];
}
}
The layouts are fairly simple so I won't post them unless anyone thinks they are relevant.
The problem is that some of the CheckBoxes are not responding. It seems to be the ones that are visible when the layout is first displayed. Any that you have to scroll down to work as expected.
Any pointers appreciated.
Your code from the answer works but is inefficient(you can actually see this, just scroll the ListView and check the Logcat to see the garbage collector doing it's work). An improved getView method which will recycle views is the one below:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout view = (LinearLayout) convertView;
if (view == null) {
view = (LinearLayout) inflater.inflate(R.layout.record_view_start, parent, false);
}
TextView tv = (TextView) view.findViewById(R.id.engName);
tv.setText(getItem(position));
CheckBox cBox = (CheckBox) view.findViewById(R.id.checkBox1);
cBox.setTag(Integer.valueOf(position)); // set the tag so we can identify the correct row in the listener
cBox.setChecked(mChecked[position]); // set the status as we stored it
cBox.setOnCheckedChangeListener(mListener); // set the listener
return view;
}
OnCheckedChangeListener mListener = new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mChecked[(Integer)buttonView.getTag()] = isChecked; // get the tag so we know the row and store the status
}
};
Regarding your code from your question, at first I thought it was wrong because of the way you setup the rows but I don't see why the adapter will have that behavior as you detached the row view from the list. Also, I even tested the code and it works quite well regarding CheckBoxes(but with very poor memory handling). Maybe you're doing something else that makes the adapter to not work?
Let me first say that you have thrown away one of the main benefits of using an adapter: Reusable views. Holding a hard reference to each created View holds a high risk of hitting the memory ceiling. You should be reusing convertView when it is non-null, and creating your view when convertView is null. There are many tutorials around which show you how to do this.
Views used in an adapter typically have an OnClickListener attached to them by the parent View so that you can set a OnItemClickListener on the ListView. This will supersede any touch listeners on the individual views. Try setting android:clickable="true" on the CheckBox in XML.
This may not be the most elegant or efficient solution but it works for my situation. For some reason attempting to reuse the views either from an array of views or using convertView makes every thing go wobbley and the CheckBoxes fail to respond.
The only thing that worked was creating a new View everytime getView() is called.
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
view=(LinearLayout)inflater.inflate(R.layout.record_view_start,null);
TextView tv=(TextView)view.findViewById(R.id.engName);
tv.setText(englishNames[position]);
CheckBox cBox=(CheckBox)view.findViewById(R.id.checkBox1);
cBox.setChecked(checked[position]);
cBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
checked[position]=isChecked;
}
});
return view;
}
Finding this solution was also hampered by the fact that I was calling a separately defined onCheckedChangedListener, that then identified which CheckBox by id, rather than having a new listener for each CheckBox.
As yet I haven't marked this as the correct answer as I'm hoping that others may have some input regarding the rather wasteful rebuilding the view every time.
Blatant n00b question: I have several directories of pictures and wish to display randomly pictures from only one, which I select by a set of radio buttons. How do I specify the directory when using :
//"ha" is ha.png, which I would like to be at drawable/1/ha.png
image.setImageResource(R.drawable.ha);
Can I use setImageResource for this? If so how? If not, what should I use and how?
The object of the exercise is a flashcard program with different lessons (hence the dividing up of images) selectable at the first activity.
You cannot have subfolders under res/drawable, if you are referring to the drawables folder in your apk.
If you are referring to a random folder on your sdcard, then it's fine to use subfolders, but then you cannot use R.drawable.* for that approach to refer to the image.
In that case you need to load the image using
Bitmap bmp = BitmapFactory.decodeFile("/sdcard/drawable/1/ha.png");
which returns a bitmap, which you can use like
image.setImageBitmap(bmp)
see http://developer.android.com/reference/android/widget/ImageView.html#setImageBitmap(android.graphics.Bitmap)
In order to react on changes made to the radion button, see
How to set On click listener on the Radio Button in android
You can use a GridView to show the images from a directory selected from a radio button (as your requirement says). After creating a GridView, associate a adapter to it. Please refer below for a n example adapter :
public class ImageAdapter extends BaseAdapter {
/** LayoutInflater. */
private LayoutInflater mInflater;
/** The i. */
private ImageView i;
/**
* Instantiates a new image adapter.
*
* #param c
* the c
*/
public ImageAdapter(Context c) {
mInflater = LayoutInflater.from(c);
}
public int getCount() {
// scaled pictures will have the list of
// which you have from the directory
return scaledPictures.size();
}
public Bitmap getItem(int position) {
return scaledPictures.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.image, parent, false);
} else {
i = (ImageView) convertView;
}
Bitmap bitmap = getItem(position);
i = (ImageView) convertView.findViewById(R.id.galleryimage);
i.setImageBitmap(bitmap);
bitmap = null;
return i;
}
}