android listview scroll image-loading

android - ListView-ImageLoader mueve la lista/elementos en Scroll up



image-loading (3)

Probablemente resolví el problema ... Finalmente ... Mi idea de establecer la altura del ImageView en getView no fue tan mala ... el problema fue:

Mi ImageView estaba envuelto dentro de un CardView en XML ... así que necesitaba cambiar la altura del CardView lugar del ImageView :)

Estoy buscando una respuesta medio día, pero no puedo encontrar nada, aunque pensé que era un problema común. Mi problema: tengo un ListView que tiene elementos de diferentes tamaños (alturas). Cada elemento contiene un ImageView . La imagen para este ImageView se carga en segundo plano por una clase de ImageLoader:

imageLoader.DisplayImage(url, holder.image);

Si me desplazo hacia abajo en ListView todo funciona bien. las imágenes se cargan y muestran (por supuesto en la parte inferior de la pantalla / lista).

Pero si me desplazo hacia arriba y la imagen ya no está almacenada en la caché, entonces ImageLoader tiene que volver a cargar la imagen, los saltos ListView / los elementos se mueven. Creo que se debe a que se crea una nueva View en la parte superior de la lista, con un ImageView con una altura de 0dp. Si la imagen se carga y se establece en ImageView la altura de ImageView cambia automáticamente de 0dp al tamaño de la imagen. Esto empujaría al ListView hacia abajo, creo.

Traté de guardar la altura de las ImageViews si se configuró una Imagen y luego establecí el alto de ImageView que se creó en la parte superior de la altura guardada. Pero sin éxito ...

No sé si puedes entender mi problema: DI así lo espero :)

¡Gracias y que tenga un buen día!

EDITAR: Se ImageLoader clase de ImageLoader

public class ImageLoader { MemoryCache memoryCache=new MemoryCache(); FileCache fileCache; int size; Context context; private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>()); private HashMap<String, Integer> imagesSizes; ExecutorService executorService; Handler handler=new Handler();//handler to display images in UI thread public ImageLoader(Context context, int size){ fileCache=new FileCache(context); this.context = context; executorService=Executors.newFixedThreadPool(5); this.size = size; imagesSizes = new HashMap<String, Integer>(); } final int stub_id=R.color.transparent; public void DisplayImage(String url, ImageView imageView) { imageViews.put(imageView, url); Bitmap bitmap=memoryCache.get(url); if(bitmap!=null){ imageView.setImageBitmap(bitmap); saveImageSize(imageView, url); } else { queuePhoto(url, imageView); imageView.setImageResource(stub_id); setImageSize(imageView, url); } } public void DisplayImage(File file, ImageView imageView) { imageViews.put(imageView, file.getAbsolutePath()); Bitmap bitmap=memoryCache.get(file.getAbsolutePath()); if(bitmap!=null){ imageView.setImageBitmap(bitmap); saveImageSize(imageView, file.getAbsolutePath()); } else { queuePhoto(file, imageView); imageView.setImageResource(stub_id); setImageSize(imageView, file.getAbsolutePath()); } } private void saveImageSize(ImageView imageView, String url){ int height = imageView.getHeight(); imagesSizes.put(url, height); System.out.println("IMAGE SIZE: Save: " + url + " " + height ); } private void setImageSize(ImageView imageView, String url){ if(imageView != null && imagesSizes!=null && imagesSizes.containsKey(url)){ imageView.getLayoutParams().height = imagesSizes.get(url); imageView.requestLayout(); System.out.println("IMAGE SIZE: Set: " + url + " " + imagesSizes.get(url) ); } } private void queuePhoto(String url, ImageView imageView) { PhotoToLoad p=new PhotoToLoad(url, imageView); executorService.submit(new PhotosLoader(p)); } private void queuePhoto(File file, ImageView imageView) { PhotoToLoad p=new PhotoToLoad(file, imageView); executorService.submit(new PhotosLoader(p)); } public Bitmap getImage(String url){ return getBitmap(url); } private Bitmap getBitmap(String url) { File f=fileCache.getFile(url); //from SD cache Bitmap b = decodeFile(f); if(b!=null) return b; //from web try { Bitmap bitmap=null; URL imageUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection(); conn.setConnectTimeout(30000); conn.setReadTimeout(30000); conn.setInstanceFollowRedirects(true); InputStream is=conn.getInputStream(); OutputStream os = new FileOutputStream(f); Utils.CopyStream(is, os); os.close(); conn.disconnect(); bitmap = decodeFile(f); return bitmap; } catch (Throwable ex){ ex.printStackTrace(); if(ex instanceof OutOfMemoryError) memoryCache.clear(); return null; } } //decodes image and scales it to reduce memory consumption private Bitmap decodeFile(File f){ try { //decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream stream1=new FileInputStream(f); BitmapFactory.decodeStream(stream1,null,o); stream1.close(); //Find the correct scale value. It should be the power of 2. final int REQUIRED_SIZE=size; int width_tmp=o.outWidth, height_tmp=o.outHeight; int scale=1; while(true){ if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE) break; width_tmp/=2; height_tmp/=2; scale*=2; } //decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize=scale; FileInputStream stream2=new FileInputStream(f); Bitmap bitmap=BitmapFactory.decodeStream(stream2, null, o2); stream2.close(); return bitmap; } catch (FileNotFoundException e) { } catch (IOException e) { e.printStackTrace(); } return null; } //Task for the queue private class PhotoToLoad { public File file; public String url; public ImageView imageView; public PhotoToLoad(String u, ImageView i){ url=u; imageView=i; file = null; } public PhotoToLoad(File file, ImageView i){ url=file.getAbsolutePath(); imageView=i; this.file = file; } } class PhotosLoader implements Runnable { PhotoToLoad photoToLoad; PhotosLoader(PhotoToLoad photoToLoad){ this.photoToLoad=photoToLoad; } @Override public void run() { try{ if(imageViewReused(photoToLoad)) return; Bitmap bmp; if(photoToLoad.file== null){ bmp=getBitmap(photoToLoad.url); } else { bmp=decodeFile(photoToLoad.file); } memoryCache.put(photoToLoad.url, bmp); if(imageViewReused(photoToLoad)) return; BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad); handler.post(bd); }catch(Throwable th){ th.printStackTrace(); } } } boolean imageViewReused(PhotoToLoad photoToLoad){ String tag=imageViews.get(photoToLoad.imageView); if(tag==null || !tag.equals(photoToLoad.url)) return true; return false; } //Used to display bitmap in the UI thread class BitmapDisplayer implements Runnable { Bitmap bitmap; PhotoToLoad photoToLoad; public BitmapDisplayer(Bitmap b, PhotoToLoad p){bitmap=b;photoToLoad=p;} public void run() { if(imageViewReused(photoToLoad)) return; if(bitmap!=null) photoToLoad.imageView.setImageBitmap(bitmap); else photoToLoad.imageView.setImageResource(stub_id); if(photoToLoad.file== null){ setImageSize(photoToLoad.imageView, photoToLoad.url); } else { setImageSize(photoToLoad.imageView, photoToLoad.file.getAbsolutePath()); } } } public void clearCache() { memoryCache.clear(); fileCache.clear(); }

EDITAR: Adaptador agregado:

public class LazyNewPostsAdapter extends BaseAdapter implements Constants{ private Activity activity; private ArrayList<Image> data; private static LayoutInflater inflater=null; private ImageLoader imageLoader; private AsyncHelper helper; public static final int VIEW_TYPE_LOADING = 0; public static final int VIEW_TYPE_ACTIVITY = 1; private int imgposition; Handler fmHandler = null; Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { switch(msg.what){ case HANDLER_NEW_POST_VOTE_IMAGE: int position = msg.arg1; JSONObject json; try { json = new JSONObject((String) msg.obj); int success = json.getInt("success"); if(success == 1){ int i_id = json.getInt("i_id"); int votesUp = json.getInt("votes_up"); int votesDown = json.getInt("votes_down"); data.get(position).setVotesUp(votesUp); data.get(position).setVotesDown(votesDown); notifyDataSetChanged(); } } catch (JSONException e) { e.printStackTrace(); } break; case HANDLER_USER_REPORTED_IMAGE: JSONObject json2 = Utils.createJSON((String)msg.obj); System.out.println("REPORT IMAGE " + json2); if(json2 != null){ try { int success = json2.getInt("success"); if(success==1){ Toast.makeText(activity, "Image reported!", Toast.LENGTH_LONG).show(); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } break; case HANDLER_IMAGE_SIZE_AVAILABLE: String url = (String) msg.obj; int height = msg.arg1; int width = msg.arg2; int imgPosition = findImageOfCertainURL(url); System.out.println("GETVIEW HANDLER 1: IMAGE POSITION" + imgPosition); if(imgPosition != -1){ data.get(imgPosition).setHeight(height); data.get(imgPosition).setWidth(width); } Message copyMsg = new Message(); copyMsg.what = HANDLER_IMAGE_SIZE_AVAILABLE; copyMsg.arg1 = height; copyMsg.arg2 = width; copyMsg.obj = url; fmHandler.sendMessage(copyMsg); notifyDataSetChanged(); break; } }; }; private int findImageOfCertainURL(String url){ for(int i = 0; i <data.size();i++){ if((URL_FOLDER_IMAGES + data.get(i).getUrl()).equalsIgnoreCase(url)){ return i; } } return -1; } public LazyNewPostsAdapter(Activity a, ArrayList<Image> d, Handler fragmentHandler) { activity = a; data=d; inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); helper = new AsyncHelper(activity, handler); imageLoader=new ImageLoader(activity.getApplicationContext(), 600,handler) ; fmHandler = fragmentHandler; } public void updateData(ArrayList<Image> d){ data = d; notifyDataSetChanged(); } public int getCount() { return data.size()+1; } @Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int position) { // TODO Auto-generated method stub return (position >= data.size()) ? VIEW_TYPE_LOADING : VIEW_TYPE_ACTIVITY; } @Override public boolean isEnabled(int position) { return getItemViewType(position) == VIEW_TYPE_ACTIVITY; } public Object getItem(int position) { return (getItemViewType(position) == VIEW_TYPE_ACTIVITY) ? data.get(position) : null; } public long getItemId(int position) { return (getItemViewType(position) == VIEW_TYPE_ACTIVITY) ? position : -1; } public View getFooterView(int position, View convertView, ViewGroup parent) { // the ListView has reached the last row TextView tvLastRow = new TextView(activity); if(AsyncHelper.isDownloadingImages){ tvLastRow.setHint("Requesting new Images.."); } else { tvLastRow.setHint("Reached the last row."); } tvLastRow.setGravity(Gravity.CENTER); return tvLastRow; } private OnClickListener myHotButtonClickListener = new OnClickListener() { @Override public void onClick(View v) { int position = (Integer) v.getTag(); if(ActivityMain.user.isLoggedIn()){ data.get(position).setThisUserVote(1); helper.vote_image(position, data.get(position).getId(), HOT); } else { Toast.makeText(activity, "Login to vote" , Toast.LENGTH_SHORT).show(); } } }; private OnClickListener myColdButtonClickListener = new OnClickListener() { @Override public void onClick(View v) { int position = (Integer) v.getTag(); if(ActivityMain.user.isLoggedIn()){ data.get(position).setThisUserVote(2); helper.vote_image(position, data.get(position).getId(), COLD); }else { Toast.makeText(activity, "Login to vote" , Toast.LENGTH_SHORT).show(); } } }; private OnClickListener myOptionsButtonClickListener = new OnClickListener() { @Override public void onClick(View v) { final int position = (Integer) v.getTag(); Runnable optionsRunnable = new Runnable() { @Override public void run() { openOptionsDialog(position); } }; handler.postDelayed(optionsRunnable, 500); } }; private void openOptionsDialog(final int imgposition){ final CharSequence[] items = {"Share", "Save", "Report"}; AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle("Options"); builder.setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { if(item==0){ Utils.shareImage(activity, imageLoader.getImage(URL_FOLDER_IMAGES + data.get(imgposition).getUrl()) , data.get(imgposition).getTitle()); } else if(item==1) { Utils.saveImage(imageLoader.getImage(URL_FOLDER_IMAGES + data.get(imgposition).getUrl()),activity); } else if(item==2) { openReportDialog(imgposition); } } }); AlertDialog alert = builder.create(); alert.show(); } private void openReportDialog(final int imgposition){ AlertDialog.Builder builder = new AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle); builder.setTitle("Report This Image?"); builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { helper.reportImage(data.get(imgposition).getId()); } }); builder.setNegativeButton("No", null); AlertDialog alert = builder.create(); alert.show(); } public View getView(final int position, View convertView, ViewGroup parent) { if (getItemViewType(position) == VIEW_TYPE_LOADING) { // display the last row return getFooterView(position, convertView, parent); } View vi=convertView; final ViewHolder holder; final Image img = data.get(position); boolean isViNull = false; if(convertView==null){ vi = inflater.inflate(R.layout.item_new_posts, null); holder = new ViewHolder(); isViNull = true; holder.title=(BrushTextView)vi.findViewById(R.id.tv_newposts_title); holder.image=(RatioImageView)vi.findViewById(R.id.iv_newposts_image); holder.uploader = (BrushTextView) vi.findViewById(R.id.tv_newposts_uploader); holder.upvotes = (BrushTextView) vi.findViewById(R.id.tv_newposts_upvotes); holder.downvotes= (BrushTextView) vi.findViewById(R.id.tv_newposts_downvotes); holder.options=(LinearLayout)vi.findViewById(R.id.ll_newposts_button_options); holder.iv_hot=(ImageView)vi.findViewById(R.id.iv_newposts_button_upvote); holder.iv_cold=(ImageView)vi.findViewById(R.id.iv_newposts_button_downvote); holder.ll_hot=(LinearLayout)vi.findViewById(R.id.ll_newposts_button_upvote); holder.ll_cold=(LinearLayout)vi.findViewById(R.id.ll_newposts_button_downvote); vi.setTag(holder); } else { holder = (ViewHolder) vi.getTag(); } if(img.getHeight() != 0 && img.getWidth() != 0){ holder.image.getLayoutParams().height = img.getHeight(); holder.image.getLayoutParams().width = img.getWidth(); holder.image.requestLayout(); } holder.ll_hot.setTag(position); holder.ll_hot.setOnClickListener(myHotButtonClickListener); holder.ll_cold.setTag(position); holder.ll_cold.setOnClickListener(myColdButtonClickListener); holder.options.setTag(position); holder.options.setOnClickListener(myOptionsButtonClickListener); changeVoteButtonImages(img, holder.iv_hot, holder.iv_cold); if(img.getTitle()!=null){ holder.title.setVisibility(View.VISIBLE); holder.title.setText(img.getTitle()); } else { holder.title.setVisibility(View.GONE); } holder.uploader.setText(img.getUploader()); holder.upvotes.setText(img.getVotesUp()+""); holder.downvotes.setText(img.getVotesDown()+""); String url = URL_FOLDER_IMAGES + img.getUrl(); System.out.println("GETVIEW NEW POST ADAPTER: height = " + img.getHeight() + " width = " + img.getWidth()); if(isViNull) System.out.println("GETVIEW CONVERTVIEW: VI: " +vi.getHeight()); imageLoader.DisplayImage(url, holder.image); return vi; } private void changeVoteButtonImages(Image image, ImageView upvote,ImageView downvote){ switch(image.getThisUserVote()){ case 0: upvote.setImageDrawable(activity.getResources().getDrawable(R.drawable.ic_post_hot_0)); downvote.setImageDrawable(activity.getResources().getDrawable(R.drawable.ic_post_cold_0)); break; case 1: downvote.setImageDrawable(activity.getResources().getDrawable(R.drawable.ic_post_cold_0)); upvote.setImageDrawable(activity.getResources().getDrawable(R.drawable.ic_post_hot_1)); break; case 2: upvote.setImageDrawable(activity.getResources().getDrawable(R.drawable.ic_post_hot_0)); downvote.setImageDrawable(activity.getResources().getDrawable(R.drawable.ic_post_cold_1)); break; } } static class ViewHolder { public BrushTextView title, uploader, upvotes, downvotes; public ImageView iv_hot,iv_cold; public LinearLayout options, ll_hot,ll_cold; public RatioImageView image; } }

Y aquí está el Fragment , que contiene el ListView

public class Fragment_New_Posts extends Fragment implements Constants, OnRefreshListener{ private static final String ARG_SECTION_NUMBER = "section_number"; private ListView list; private LazyNewPostsAdapter adapter; private Images images; private ArrayList<Image> imagesList; private SharedPreferences prefs; private Editor editor; private int visible_i_id; private SwipeRefreshLayout swipeLayout; private int option_image_filter; private AsyncHelper helper; private boolean onRefreshFired = false; private Parcelable state; public static Fragment_New_Posts newInstance(int sectionNumber) { Fragment_New_Posts fragment = new Fragment_New_Posts(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); fragment.setArguments(args); return fragment; } public static Fragment_New_Posts newInstance(int sectionNumber, Images imgs) { Fragment_New_Posts fragment = new Fragment_New_Posts(imgs); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); fragment.setArguments(args); return fragment; } public void updateImages(Images images, boolean createNew, boolean loadOldImages){ int i_id_position = 0; this.images = images; if(!images.hasErrorOccured){ if(this.imagesList == null || createNew || onRefreshFired){ this.imagesList = images.getListOfImages(); } else { this.imagesList.addAll(images.getListOfImages()); } } if(loadOldImages){ i_id_position = this.images.getIDPosition(visible_i_id); } if(onRefreshFired){ swipeLayout.setRefreshing(false); onRefreshFired = false; } synchronized (adapter) { adapter.updateData(this.imagesList); if(list!=null && createNew){ if(loadOldImages){ list.setSelection(i_id_position); } else { list.setSelection(0); } } } } private Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { switch(msg.what){ case HANDLER_MAIN_IMAGE_UPDATE: String imagesString = (String) msg.obj; Images imgs = new Images(imagesString); updateImages(imgs, false, false); if(imgs.hasErrorOccured){ Toast.makeText(getActivity(), "Can/'t get new Images", Toast.LENGTH_SHORT).show(); } break; case HANDLER_IMAGE_SIZE_AVAILABLE: String url = (String) msg.obj; int height = msg.arg1; int width = msg.arg2; int imgPosition = findImageOfCertainURL(url); System.out.println("GETVIEW HANDLER 2: IMAGE POSITION" + imgPosition); if(imgPosition != -1){ imagesList.get(imgPosition).setHeight(height); imagesList.get(imgPosition).setWidth(width); } break; } }; }; private int findImageOfCertainURL(String url){ for(int i = 0; i <imagesList.size();i++){ if((URL_FOLDER_IMAGES + imagesList.get(i).getUrl()).equalsIgnoreCase(url)){ return i; } } return -1; } public Fragment_New_Posts() { } public Fragment_New_Posts(Images imgs) { this.images = imgs; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { System.out.println("NEW POST FRAGMENT ONCREATE"); View rootView = inflater.inflate(R.layout.fragment_new_posts, container, false); prefs = getActivity().getSharedPreferences(PREF_WOODU, getActivity().MODE_PRIVATE); editor = prefs.edit(); if(prefs!=null){ visible_i_id = prefs.getInt(PREF_NEW_POSTS_VISIBLE_I_ID, 0); option_image_filter = prefs.getInt(PHP_TAG_IMAGE_FILTER, 0); } swipeLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_container); swipeLayout.setColorSchemeColors(R.color.red_hot); swipeLayout.setOnRefreshListener(this); helper = new AsyncHelper(getActivity(), handler); imagesList = new ArrayList<Image>(); if(images != null){ imagesList = images.getListOfImages(); System.out.println("SAVE IMAGES: load: imgsList " + imagesList); } list = (ListView) rootView.findViewById(R.id.lv_new_posts); adapter = new LazyNewPostsAdapter(getActivity(), imagesList, handler); list.setAdapter(adapter); list.setOnScrollListener(new EndlessScrollListener() { @Override public void onLoadMore(int page, int totalItemsCount) { if(imagesList!=null && imagesList.size()!=0){ helper.getImageUpdate(imagesList.get(imagesList.size()-1).getId(), option_image_filter); } } }); if(images != null){ updateImages(images, true, true); } return rootView; } @Override public void onPause() { int lastVisposition = list.getLastVisiblePosition(); if(lastVisposition>=0 && lastVisposition < this.imagesList.size()){ visible_i_id = this.imagesList.get(list.getFirstVisiblePosition()).getId(); editor.putInt(PREF_NEW_POSTS_VISIBLE_I_ID, visible_i_id); editor.commit(); } super.onPause(); } @Override public void onResume() { super.onResume(); } @Override public void onDestroy() { if(images != null){ editor.putString(PREF_NEW_POSTS_CURRENT_IMAGES, Utils.createJSONStringFromImageArrayList(imagesList, editor)); editor.commit(); } super.onDestroy(); } @Override public void onRefresh() { onRefreshFired = true; if(prefs!=null){ option_image_filter = prefs.getInt(PHP_TAG_IMAGE_FILTER, 0); } helper.getImageUpdate(0, option_image_filter); } }

Ahora aquí hay mucha información y código. Espero que eso no asuste a nadie para ayudar: D

¡¡Gracias!!


WeakHashMap ... estás usando referencias débiles. Lo cual hace bien eso. Elimina objetos cuando no se usan más. Entonces, simplemente cámbialo a HashMap y deberías estar bien.


Por favor, por favor, no "reinvente la rueda" y use cualquier lib para carga de imágenes, almacenamiento en caché, etc. como Picasso, Fresco, Glide, etc.