I'm developing android App. My app is showing many of pictures from server by image_url with ImageLoader Library such as Picasso, Glide or AUIL.
There are five fragments in main activity. and each of fragments have many image list view.
My question is that after I click specific image view, new activity is created, and then fragment is created that have many images list view.
I clicked different images, and new activities are created. When this gesture occurred , heap memory is not fetched ...
Among of three libraries, AUIL is the best of them.
I tried recursing method at base adapter and drawable callback to null etc...
But This situation was not solved.
Here is my example Adapter sourse:
public class MagazineRelBrandItemAdapter extends BaseAdapter {
ArrayList<String> receivedList;
ArrayList<View> brandThumList = new ArrayList<>();
PrintLog printLog = new PrintLog(Application.isDEBUG(), "MagazineRelBrandItemAdap ", "created");
Activity mActivity;
public MagazineRelBrandItemAdapter(ArrayList<String> dataList, Activity activity) {
receivedList = dataList;
mActivity = activity;
}
#Override
public int getCount() {
return receivedList.size();
}
#Override
public String getItem(int position) {
return receivedList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
MagazineRelBrandView itemView;
if (convertView == null) {
itemView = new MagazineRelBrandView(Application.getContext(),mActivity);
} else {
itemView = (MagazineRelBrandView) convertView;
}
try {
itemView.setContent(receivedList.get(position));
brandThumList.add(itemView);
} catch (OutOfMemoryError e) {
if (mRecycleList.size() <= parent.getChildCount()) {
printLog.cutsomPrintLog(Application.isDEBUG(), "img recycle size comment>>", mRecycleList.size() + "");
throw e;
}
recycleHalf();
System.gc();
return getView(position, itemView, parent);
}
mRecycleList.add(new WeakReference<View>(itemView));
return itemView;
}
private List<WeakReference<View>> mRecycleList = new ArrayList<WeakReference<View>>();
public void recycleHalf() {
int halfSize = mRecycleList.size() / 2;
List<WeakReference<View>> recycleHalfList = mRecycleList.subList(0, halfSize);
RecycleUtils.recursiveRecycle(recycleHalfList);
for (int i = 0; i < halfSize; i++)
mRecycleList.remove(0);
}
public void recycle() {
RecycleUtils.recursiveRecycle(mRecycleList);
}
}
And Here is my widget :
public class MagazineRelBrandView extends LinearLayout {
Activity mActivity;
public MagazineRelBrandView(Context context,Activity activity) {
super(context);
mActivity = activity;
init();
}
CircleImageView brandLogo;
TextView brandName;
ImageView brandBg;
View clickV;
private void init() {
inflate(getContext(), R.layout.magazine_rel_brand_view, this);
brandBg = (ImageView) findViewById(R.id.magazine_rel_brand_bg);
brandName = (TextView) findViewById(R.id.magazine_rel_brand_name);
brandLogo = (CircleImageView) findViewById(R.id.magazine_rel_brand_icon);
clickV = findViewById(R.id.content_click_view);
brandName.setTypeface(Typeface.createFromAsset(getContext().getAssets(), CommonKeys.FuturaFont));
clickV.setOnClickListener(goBrandDetailPageListener);
}
public void setContent(String brandName) {
this.brandName.setText(brandName);
try {
String brandTitleUrl;
brandTitleUrl = URLEncoder.encode(brandName, "UTF-8");
totalThumbUrl = CommonKeys.brandThumbUrl + brandTitleUrl;
totalBackUrl = CommonKeys.brandBackgroundUrl + brandTitleUrl;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
showBG(brandName);
showLogo(brandName);
}
String totalThumbUrl;
String totalBackUrl;
public void showLogo(final String brandName){
Glide.with(mActivity).load(totalThumbUrl).override(DpToPxUtil.dp2px(38),DpToPxUtil.dp2px(38)).into(brandLogo);
}public void showBG(final String brandName){
Glide.with(mActivity).load(totalBackUrl).override(DpToPxUtil.dp2px(120),DpToPxUtil.dp2px(144)).into(brandBg);
}
public String getBrandName() {
return brandName.getText().toString();
}
Handler brandHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
MyToast.show("" + msg.obj);
break;
case 1:
BrandInfoZip brandInfoZip = (BrandInfoZip) ((BrandInfoParentZip) msg.obj).getData();
BrandListZip getBrandZip = new BrandListZip();
getBrandZip.setIs_following(brandInfoZip.is_following());
getBrandZip.setBrand_name(getBrandName());
Intent goBrandPage = new Intent(Application.getContext(), BrandDetailActivity.class);
goBrandPage.putExtra("brandZip", getBrandZip);
goBrandPage.putExtra("title", getBrandName());
goBrandPage.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Application.getContext().startActivity(goBrandPage);
break;
}
}
};
OnClickListener goBrandDetailPageListener = new OnClickListener() {
#Override
public void onClick(View v) {
BrandDescriptionZip brandDescriptionZip = new BrandDescriptionZip();
brandDescriptionZip.setBrand_name(getBrandName());
new GetBrandInfoFromServer(brandHandler).execute(brandDescriptionZip);
}
};
}
And fragment onDestroyView sourse :
#Override
public void onStop() {
super.onStop();
if (upperBgImgV.getDrawable() != null) {
upperBgImgV.getDrawable().setCallback(null);
}
if (writerThumb.getDrawable() != null) {
writerThumb.getDrawable().setCallback(null);
}
if (otherContentV.getDrawable() != null) {
otherContentV.getDrawable().setCallback(null);
}
}
#Override
public void onDestroyView() {
Glide.clear(writerThumb);
magazineRelBrandItemAdapter.recycle();
unbindDrawables(getView());
//unbindDrawables(writerThumb);
//unbindDrawables(otherContentV);
System.gc();
super.onDestroyView();
Log.i(TAG, "onDestroyView-");
}
private void unbindDrawables(View view) {
if (view == null)
return;
if (view instanceof ImageView) {
((ImageView) view).setImageDrawable(null);
}
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
view.setBackgroundResource(0);
view.setBackgroundDrawable(null);
}
}
and Here is activity onDestroy() course :
#Override
protected void onDestroy() {
RecycleUtils.recursiveRecycle(getWindow().getDecorView());
System.gc();
Glide.get(this).clearMemory();
FlurryAgent.onEndSession(Application.getContext());
printLog.cutsomPrintLog(Application.isDEBUG(),"Magazine Detail Activity onDestroy-","onDestroy-");
super.onDestroy();
finish();
}
These course is used to clear memory heap. But Memory leak is continue....
Please help me.
Related
I am using Epoxy Controller for Recycler View. I am having trouble changing the view after data changed by the user action.
Basically I have a switch button in a view which is used inside a recycler view and I am trying to update the view on switch button state change. I am calling requestModelBuild() in setProductList() function of the epoxy controller but change is not reflected in the view.
public class SellerInventoryListEpoxyController extends EpoxyController {
private List<Product> productList = Collections.emptyList();
private Context context;
private SellerInventoryListEpoxyController.Callbacks callbacks;
public void setProductList(List<Product> productList, Context context, SellerInventoryListEpoxyController.Callbacks callbacks) {
this.productList = productList;
this.context = context;
this.callbacks = callbacks;
requestModelBuild();
}
#Override
protected void buildModels() {
for (int i = 0; i < productList.size(); i++) {
new InventoryProductDetailModel_()
.id(productList.get(i).getId())
.product(productList.get(i))
.position(i)
.listSize(productList.size())
.callbacks(callbacks)
.context(context)
.addTo(this);
}
}
public interface Callbacks {
void onViewComboClick(Product productComboList);
void onProductListingStatusChanged(Boolean newStatus, int productSellerId);
void onRecyclerViewReachEnd();
}
}
public class InventoryProductDetailModel extends EpoxyModelWithHolder<InventoryProductDetailModel.ViewHolder> implements CompoundButton.OnCheckedChangeListener {
#EpoxyAttribute
Product product;
#EpoxyAttribute
int position;
#EpoxyAttribute
int listSize;
#EpoxyAttribute
Context context;
#EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
SellerInventoryListEpoxyController.Callbacks callbacks;
#Override
protected ViewHolder createNewHolder() {
return new ViewHolder();
}
#Override
protected int getDefaultLayout() {
return R.layout.inventroy_item_layout;
}
private DrawableCrossFadeFactory factory =
new DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build();
#Override
public void bind(#NonNull InventoryProductDetailModel.ViewHolder holder) {
super.bind(holder);
holder.quantity.setText(String.format("Available :%d", product.getTotalStock()));
holder.brand.setText(product.getProduct().getBrandName());
holder.title.setText(product.getProduct().getTitle());
holder.category.setText(product.getProduct().getCategoryName());
holder.sku.setText(String.format("Sku: %s", product.getSku()));
holder.inventoryItemConstrainLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(context, ProductDetailActivity.class);
intent.putExtra("product_id", product.getId());
context.startActivity(intent);
}
});
if (product.getProductCombos() != null && product.getProductCombos().size() > 0) {
holder.variationCount.setVisibility(View.GONE);
holder.comboBtn.setVisibility(View.VISIBLE);
holder.comboBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callbacks.onViewComboClick(product);
}
});
}
if (product.getSellerActive()) {
holder.productStatusSwitch.setText("Active");
holder.productStatusSwitch.setOnCheckedChangeListener(null);
holder.productStatusSwitch.setChecked(true);
holder.productStatusSwitch.setOnCheckedChangeListener(this);
holder.productStatusSwitch.setTextColor(context.getResources().getColor(R.color.colorAccent));
} else {
holder.productStatusSwitch.setText("Inactive");
holder.productStatusSwitch.setOnCheckedChangeListener(null);
holder.productStatusSwitch.setChecked(false);
holder.productStatusSwitch.setOnCheckedChangeListener(this);
holder.productStatusSwitch.setTextColor(Color.parseColor("#ff0000"));
}
holder.variationCount.setText(format("Variation(%d)", product.getVariantCount()));
holder.variationCount.setVisibility(View.VISIBLE);
holder.comboBtn.setVisibility(View.GONE);
loadImage(holder.productImage, Utils.getRequiredUrlForThisImage(holder.productImage, product.getProduct().getImage()));
if (position == listSize - 2) {
callbacks.onRecyclerViewReachEnd();
}
}
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
callbacks.onProductListingStatusChanged(isChecked, product.getId());
}
private void loadImage(ImageView imageView, String url) {
Glide.with(imageView.getContext()).asBitmap()
.load(Utils.getRequiredUrlForThisImage(imageView, url))
.apply(new RequestOptions().diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.fitCenter())
.transition(withCrossFade(factory))
.placeholder(R.mipmap.product)
.into(imageView);
}
#Override
public void unbind(#NonNull InventoryProductDetailModel.ViewHolder holder) {
super.unbind(holder);
}
public static class ViewHolder extends EpoxyHolder {
TextView quantity, brand, title, category, variationCount, comboBtn;
ImageView productImage, btn_product_detail;
ProgressBar progressBar;
ConstraintLayout inventoryItemConstrainLayout;
private TextView sku;
private Switch productStatusSwitch;
#Override
protected void bindView(#NonNull View itemView) {
productStatusSwitch = itemView.findViewById(R.id.productStatusSwitch);
quantity = itemView.findViewById(R.id.product_qty);
brand = itemView.findViewById(R.id.product_brand);
title = itemView.findViewById(R.id.product_title);
sku = itemView.findViewById(R.id.sku);
category = itemView.findViewById(R.id.product_category);
variationCount = itemView.findViewById(R.id.variantCount);
productImage = itemView.findViewById(R.id.product_image);
btn_product_detail = itemView.findViewById(R.id.btn_product_detail);
inventoryItemConstrainLayout = itemView.findViewById(R.id.inventory_item_constrain_layout);
comboBtn = itemView.findViewById(R.id.combo_btn);
progressBar = itemView.findViewById(R.id.progressbar);
progressBar.setVisibility(View.GONE);
}
}
#Override
public int hashCode() {
super.hashCode();
return product.hashCode();
}
#Override
public boolean equals(Object o) {
return super.equals(o);
}
}
private void addProductListingChangeObserver(final Boolean newStatus, final int productSellerId) {
ProductUpdate productUpdate = new ProductUpdate();
productUpdate.setSellerActive(newStatus);
mInventoryViewModel.updateProductSeller(productSellerId, productUpdate).observe(this, new Observer<Resource<ProductSeller>>() {
#Override
public void onChanged(Resource<ProductSeller> productSellerResource) {
if (productSellerResource.status == Status.ERROR) {
progressBar.setVisibility(View.GONE);
} else if (productSellerResource.status == Status.SUCCESS) {
progressBar.setVisibility(View.GONE);
if (productSellerResource.data != null && productSellerResource.data.isSellerActive() == newStatus) {
for (int i = 0; i < productList.size(); i++) {
if (productList.get(i).getId() == productSellerId) {
productList.get(i).setSellerActive(newStatus);
break;
}
}
sellerInventoryListEpoxyController.setProductList(productList, getContext(), InventoryFragment.this);
}
} else {
progressBar.setVisibility(View.VISIBLE);
}
}
});
}
In addProductListingChangeObserver() function one object of productList is modified and new productList is passed to the EpoxyController and requestModelbuild is called but the view is not modifying as expected.
In my project, there is need of searching data from server using keyword. After search, i am displaying results using RecyclerView . While searching fast, the data in RecyclerView is duplicating. If searching slowly, it's working fine. Any suggestions are appreciated. Thank you.
The below code for making server call:
private void callSearchUserApi(final String searchText, int currentPage, boolean clearData) {
isApiCallInProcess = true;
String URL = "userinfo/api/v1/user-search/" + "?page=" + currentPage;
if (!Connectivity.isConnected(activity)) {
Common.snackBarNoConnection(activity, activity.getString(R.string.no_conection));
//setOnProgressbarVisibility(View.GONE);
return;
}
if (clearData) {
globalSearchUsersModelList.clear();
//BS globalSearchUserResultsAdapter.notifyDataSetChanged();
}
ApiInterface apiCall = ApiClient.getApiService(activity);
final Call<SearchUsersModel> globalUserSearchApiCall = apiCall.searchUser(
URL,
searchText);
globalUserSearchApiCall.enqueue(new Callback<SearchUsersModel>() {
#Override
public void onResponse(Call<SearchUsersModel> call, Response<SearchUsersModel> response) {
if (response.isSuccessful() && response.body().getStatus().equalsIgnoreCase(Common.SUCCESS_RESPONSE)) {
//BS globalSearchUsersModelList.addAll(response.body().getData().getData());
for (int i = 0; i < response.body().getData().getData().size(); i++) {
SearchUsersModel.DataBeanX.DataBean dataBean = new SearchUsersModel.DataBeanX.DataBean();
dataBean.setDesignation(response.body().getData().getData().get(i).getDesignation());
dataBean.setFull_name(response.body().getData().getData().get(i).getFull_name());
dataBean.setGender(response.body().getData().getData().get(i).getGender());
dataBean.setId(response.body().getData().getData().get(i).getId());
dataBean.setPlace(response.body().getData().getData().get(i).getPlace());
dataBean.setProfile_pic(response.body().getData().getData().get(i).getProfile_pic());
globalSearchUsersModelList.add(dataBean);
/*BS if (!globalSearchUsersModelList.contains(response.body().getData().getData().get(i)))
globalSearchUsersModelList.add(response.body().getData().getData().get(i));*/
}
CURRENT_PAGE = response.body().getData().getPage();
isLoading = false;
if (response.body().getData().isNext() == false)
isLastPage = true;
else
isLastPage = false;
if (globalSearchUsersModelList.size() == 0) {
rv_GlobalsearchList.setVisibility(View.GONE);
rl_placeholderGSPeople.setVisibility(View.VISIBLE);
tv_placeholderGSPeople.setText(activity.getString(R.string.no_search_found) + " " + searchText);
} else {
rv_GlobalsearchList.setVisibility(View.VISIBLE);
rl_placeholderGSPeople.setVisibility(View.GONE);
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
globalSearchUserResultsAdapter.notifyDataSetChanged();
}
});
}
if (searchTextsList.size() > 0) {
String sText = searchTextsList.get(0);
searchTextsList.remove(0);
callSearchUserApi(sText, FIRST_PAGE, true);
} else
isApiCallInProcess = false;
}
#Override
public void onFailure(Call<SearchUsersModel> call, Throwable t) {
isApiCallInProcess = false;
}
});
}
This is my Adapter.
public class GlobalSearchUserResultsAdapter extends RecyclerView.Adapter<GlobalSearchUserResultsAdapter.SearchViewHolder> {
private Context context;
private List<SearchUsersModel.DataBeanX.DataBean> searchUserList;
public GlobalSearchUserResultsAdapter(Context context, List<SearchUsersModel.DataBeanX.DataBean> searchUserList){
this.context = context;
this.searchUserList = searchUserList;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.v("Search", "Adapter Activity : "+context);
View view = LayoutInflater.from(context).inflate(R.layout.global_search_row, parent, false);
return new GlobalSearchUserResultsAdapter.SearchViewHolder(view);
}
#Override
public void onBindViewHolder(GlobalSearchUserResultsAdapter.SearchViewHolder holder, int position) {
if ( searchUserList.get(position).getGender().equals("M")) {
holder.iv_userImage.setBackgroundResource(R.drawable.white_border_with_circle_appblue);
Common.setGlideImage((GlobalSearchActivity)context,
holder.iv_userImage,
/*searchUsersModel*/searchUserList.get(position).getProfile_pic(),
R.drawable.male,
true);
} else if (searchUserList.get(position).getGender().equals("F")) {
holder.iv_userImage.setBackgroundResource(R.drawable.white_border_with_circle_pink);
Common.setGlideImage((GlobalSearchActivity)context,
holder.iv_userImage,
searchUserList.get(position).getProfile_pic(),
R.drawable.female,
true);
} else {
Common.setGlideImage((GlobalSearchActivity)context,
holder.iv_userImage,
searchUserList.get(position).getProfile_pic(),
R.drawable.deafult_profilepic,
true);
}
holder.tv_userName.setText(searchUserList.get(position).getFull_name());
holder.tv_userName.setTypeface(Common
.getFontTypeface(context, GlobalConstants.FONT_AVENIR_MEDIUM));
holder.tv_place.setText(searchUserList.get(position).getPlace());
holder.tv_place.setTypeface(Common
.getFontTypeface(context, GlobalConstants.FONT_AVENIR_MEDIUM));
holder.designation.setText(searchUserList.get(position).getDesignation());
}
#Override
public int getItemCount() {
return searchUserList.size();
}
public class SearchViewHolder extends RecyclerView.ViewHolder{
private ImageView iv_userImage;
private TextView tv_userName;
private TextView tv_place;
private TextView designation;
public SearchViewHolder(View itemView) {
super(itemView);
this.iv_userImage = (ImageView) itemView.findViewById(R.id.imageSearch);
this.tv_userName = (TextView) itemView.findViewById(R.id.nameSearch);
tv_userName.setTypeface(Common.getFontTypeface(context,
GlobalConstants.FONT_AVENIR_MEDIUM));
this.designation = (TextView) itemView.findViewById(R.id.designation);
designation.setTypeface(Common.getFontTypeface(context,
GlobalConstants.FONT_AVENIR_MEDIUM));
this.tv_place = (TextView) itemView.findViewById(R.id.placeSearch);
tv_place.setTypeface(Common.getFontTypeface(context,
GlobalConstants.FONT_AVENIR_LIGHT));
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
context.startActivity(new Intent(context, ProfileActivity.class)//ThirdParty
.putExtra(GlobalConstants.KEY_THIRD_PARTY_ID, searchUserList.get(getAdapterPosition()).getId()));
}
});
}
}
}
You just had to clear the globalSearchUsersModelList list just before for loop, because API call is asynchronous.
globalSearchUsersModelList.clear();// Important one
for (int i = 0; i < response.body().getData().getData().size(); i++) {
// do your stuff
}
I think the issue come from your getItemId implementation. The way you implement it, the recycler view will identify an item according to its position in the list.
If you change this and use unique identification for instance searchUserList.get(position).id (if your User object has an unique ID) the problem should be fixed
You can also add in your activity adapter.setHasStableIds(true)
I'm trying to create a RecyclerView with ImageViews. Here is what I get:
As you can see all the artist covers are messed up and in the wrong order. If I scroll or reload, the respective images might change.
Here is my adapter onBindViewHolder:
public class ArtistsAdapter extends RecyclerView.Adapter<ArtistViewHolder> implements ListPreloader.PreloadModelProvider {
private static final String TAG = ArtistsAdapter.class.getSimpleName();
private final LayoutInflater mInflater;
private final GlideRequest<Bitmap> fullRequest;
ArtistsAdapter(Context context) {
this.mContext = context;
this.artists = new ArrayList<>();
this.mInflater = LayoutInflater.from(context);
this.fullRequest = GlideApp.with(context).asBitmap();
}
private final Context mContext;
public List<CompactdArtist> getArtists() {
return artists;
}
void swapArtists (List<CompactdArtist> artists) {
this.artists.clear();
this.artists.addAll(artists);
notifyDataSetChanged();
}
private final List<CompactdArtist> artists;
#Override
public ArtistViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_artist_item, parent, false);
return new ArtistViewHolder(view);
}
#Override
public void onViewRecycled(ArtistViewHolder holder) {
super.onViewRecycled(holder);
Log.d(TAG, "onViewRecycled: ");
GlideApp.with(mContext).clear(holder.artistImageView);
holder.artistImageView.setImageDrawable(null);
}
#Override
public void onBindViewHolder(final ArtistViewHolder holder, final int position) {
Log.d(TAG, "onBindViewHolder: " + position);
final CompactdArtist current = artists.get(position);
// holder.setIsRecyclable(false);
holder.artistImageView.setImageDrawable(null);
try {
fullRequest.load(new MediaCover(current))
.fallback(ImageUtils.getFallback(mContext))
.into(new ImageViewTarget<Bitmap>(holder.artistImageView) {
#Override
protected void setResource(#Nullable Bitmap resource) {
if (resource == null) {
// holder.setIsRecyclable(true);
return;
}
holder.artistImageView.setImageBitmap(resource);
int color = Palette.from(resource).generate().getMutedColor(0xFFFFFF);
try {
current.fetch();
} catch (CouchbaseLiteException e) {
e.printStackTrace();
holder.artistNameText.setText(R.string.unknown_artist_name);
}
holder.artistBackground.setBackgroundColor(color);
holder.bindArtist(current);
holder.artistSub.setText(current.getAlbumCount() + " albums");
holder.artistNameText.setText(current.getName());
// holder.setIsRecyclable(true);
}
#Override
public void onLoadCleared(#Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
// holder.setIsRecyclable(true);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public long getItemId(int position) {
return artists.get(position).getId().hashCode();
}
#Override
public int getItemCount() {
return this.artists.size();
}
#NonNull
#Override
public List getPreloadItems(int position) {
return this.artists.subList(position, position + 1);
}
#Nullable
#Override
public RequestBuilder<?> getPreloadRequestBuilder(Object item) {
MediaCover cover = (MediaCover) item;
return fullRequest.clone().thumbnail(0.2f).load(cover);
}
}
Here is code in my fragment container:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_artists, container, false);
mArtistRecyclerView = rootView.findViewById(R.id.artists_recyclerview);
LinearLayoutManager layoutManager = new GridLayoutManager(getContext(), 3);
mArtistRecyclerView.setLayoutManager(layoutManager);
mArtistsAdapter = new ArtistsAdapter(getActivity());
mArtistRecyclerView.setAdapter(mArtistsAdapter);
mArtistRecyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() {
#Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
ArtistViewHolder artistViewHolder = (ArtistViewHolder) holder;
GlideApp.with(holder.itemView).clear(artistViewHolder.getArtistImage());
}
});
FixedPreloadSizeProvider<MediaCover> coverSizeProvider = new FixedPreloadSizeProvider<>(64, 64);
RecyclerViewPreloader<MediaCover> preloader = new RecyclerViewPreloader<MediaCover>(GlideApp.with(this), mArtistsAdapter, coverSizeProvider, 2);
return rootView;
}
As a bonus, I add the fetcher for my MediaCover:
/**
* Created by vinz243 on 13/12/2017.
*/
public class MediaCoverFetcher implements DataFetcher<InputStream> {
private final MediaCover mediaCover;
private boolean cancelled = false;
private InputStream stream;
public MediaCoverFetcher(MediaCover mediaCover) {
this.mediaCover = mediaCover;
}
#Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
CompactdArtwork artwork = mediaCover.getArtwork();
try {
stream = artwork.getImage(ArtworkSize.LARGE);
if (cancelled) {
callback.onLoadFailed(new RuntimeException("Cancelled"));
} else {
callback.onDataReady(stream);
}
} catch (NullPointerException e) {
callback.onLoadFailed(e);
}
}
#Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void cancel() {
cancelled = true;
}
#NonNull
#Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
#NonNull
#Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}
I guess it comes from the recycling part, but I don't know how to fix the behavior. As you may see in the code, I tried setIsRecyclable but I would get errors saying unmatched calls and it wouldn't fix it
You should try to change a couple of things :
move your setters on your ArtistViewHolder object in the setResource function
define your CompactdArtist variable as a final
#Override
public void onBindViewHolder(final ArtistViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder: " + position);
final CompactdArtist current = artists.get(position);
// holder.setIsRecyclable(false);
holder.bindArtist(artists.get(position));
holder.artistImageView.setImageDrawable(null);
try {
current.fetch();
fullRequest.load(new MediaCover(current))
.fallback(ImageUtils.getFallback(mContext))
.into(new ImageViewTarget<Bitmap>(holder.artistImageView) {
#Override
protected void setResource(#Nullable Bitmap resource) {
if (resource == null) {
// holder.setIsRecyclable(true);
return;
}
holder.artistImageView.setImageBitmap(resource);
int color = Palette.from(resource).generate().getMutedColor(0xFFFFFF);
holder.artistBackground.setBackgroundColor(color);
// holder.setIsRecyclable(true);
holder.artistNameText.setText(current.getName());
holder.artistSub.setText(current.getAlbumCount() + " albums");
}
#Override
public void onLoadCleared(#Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
// holder.setIsRecyclable(true);
}
});
} catch (CouchbaseLiteException e) {
e.printStackTrace();
holder.artistNameText.setText(R.string.unknown_artist_name);
} catch (IOException e) {
e.printStackTrace();
}
}
I assume that the way you load your images is asynchronous, so in this case your problems resides in the fact that you set your variables as if they were in a synchronous process, that's why you get your information displayed on the wrong blocks.
I have a button in every cell of a RecyclerView that launches a download network call. The cell displays differently according to whether it's downloading, downloaded or finished.
my simplified code :
#Override public void onBindViewHolder(final CatalogViewHolder holder, int position) {
final DownloadStatusCallback statusCallback = new DownloadStatusCallback() {
#Override public void started() {
mainThreadHandler.post(new Runnable() {
#Override public void run() {
holder.itemView.setBackground(//color1
}
});
}
#Override public void finished() {
mainThreadHandler.post(new Runnable() {
#Override public void run() {
holder.itemView.setBackground(//color 2
}
});
}
#Override public void error(Exception e) {
mainThreadHandler.post(new Runnable() {
#Override public void run() {
holder.itemView.setBackground(//color 3
}
});
}
};
holder.button1.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View view) {
assyncCall(statusCallback);
}
});
}
The first time i clic on a cell, everything works fine. If I clic on the download button of another cell, both of them will update.
I understand that's due to recyclerview recycling cells, but I can't figure out how to do better.
Thanks !
my full adapter :
public class CatalogRecyclerAdapter extends RecyclerView.Adapter<CatalogViewHolder> {
public static final String TAG = "CatalogRecyclerAdapter";
private final LayoutInflater inflater;
private final DownloadCenter downloadCenter;
private final ListInterface.FlowController flowController;
private final ResourcesStringRepository resourcesStringRepository;
private final ImageManagerFactory imageManagerFactory;
private final Handler mainThreadHandler;
public CatalogRecyclerAdapter(LayoutInflater inflater, ListInterface.FlowController flowController,
DownloadCenter downloadCenter, ResourcesStringRepository resourcesStringRepository,
ImageManagerFactory imageManagerFactory, Handler mainThreadHandler) {
this.inflater = inflater;
this.flowController = flowController;
this.downloadCenter = downloadCenter;
this.resourcesStringRepository = resourcesStringRepository;
this.imageManagerFactory = imageManagerFactory;
this.mainThreadHandler = mainThreadHandler;
}
private static final int TITLE = 0;
private static final int USER = 2;
private static final int PROGRAM = 3;
private static final int COURSE = 4;
private static final int GROUP = 5;
private static final int MEDIA = 6;
private static final int ERROR = 7;
private static final int DEMO = 8;
//The list of all elements
private List<FilterableUser> users = new ArrayList<>();
private List<CatalogProgram> programs = new ArrayList<>();
private List<CatalogProgram> demos = new ArrayList<>();
private List<CatalogCourse> courses = new ArrayList<>();
private List<FilterableGroup> groups = new ArrayList<>();
private List<CatalogMedia> medias = new ArrayList<>();
//The list that will be displayed after filtering and research.
List<Object> displayedList = new ArrayList<>();
static final String TITLES[] = new String[10];
static {
Context ctx = M360Application.getContext();
TITLES[USER] = ctx.getString(R.string.users);
TITLES[PROGRAM] = ctx.getString(R.string.programs);
TITLES[COURSE] = ctx.getString(R.string.courses);
TITLES[GROUP] = ctx.getString(R.string.groups);
TITLES[MEDIA] = ctx.getString(R.string.documents);
TITLES[DEMO] = ctx.getString(R.string.programs_demo);
}
private String searchString;
#Override public int getItemViewType(int position) {
if (displayedList.get(position) instanceof String) {
return TITLE;
} else if (displayedList.get(position) instanceof FilterableUser) {
return USER;
} else if (displayedList.get(position) instanceof CatalogProgramDemo) {
return DEMO;
} else if (displayedList.get(position) instanceof CatalogProgram) {
return PROGRAM;
} else if (displayedList.get(position) instanceof CatalogCourse) {
return COURSE;
} else if (displayedList.get(position) instanceof FilterableGroup) {
return GROUP;
} else if (displayedList.get(position) instanceof CatalogMedia) {
return MEDIA;
} else if (displayedList.get(position) instanceof CatalogError) {
return ERROR;
} else {
throw new ClassCastException(
"this adapter's displayedList is corrupted" + displayedList.get(position).toString());
}
}
public void setData(List<Filterable> data, String searchedString) {
searchString = searchedString;
setData(data);
}
private void setData(List<Filterable> data) {
LogDev.i(TAG, "setting data size: " + data.size());
groups.clear();
users.clear();
programs.clear();
demos.clear();
courses.clear();
medias.clear();
for (Filterable element : data) {
if (element instanceof CatalogCourse) {
courses.add((CatalogCourse) element);
} else if (element instanceof FilterableUser) {
users.add((FilterableUser) element);
} else if (element instanceof CatalogProgramDemo) {
demos.add((CatalogProgramDemo) element);
} else if (element instanceof CatalogProgram) {
programs.add((CatalogProgram) element);
} else if (element instanceof FilterableGroup) {
groups.add((FilterableGroup) element);
} else if (element instanceof CatalogMedia) {
medias.add((CatalogMedia) element);
}
}
constructDataSet();
}
private void constructDataSet() {
displayedList.clear();
if (!demos.isEmpty()) {
displayedList.add(TITLES[DEMO]);
displayedList.addAll(demos);
}
if (!programs.isEmpty()) {
displayedList.add(TITLES[PROGRAM]);
displayedList.addAll(programs);
}
if (!courses.isEmpty()) {
displayedList.add(TITLES[COURSE]);
displayedList.addAll(courses);
}
if (!users.isEmpty()) {
displayedList.add(TITLES[USER]);
displayedList.addAll(users);
}
if (!groups.isEmpty()) {
displayedList.add(TITLES[GROUP]);
displayedList.addAll(groups);
}
if (!medias.isEmpty()) {
displayedList.add(TITLES[MEDIA]);
displayedList.addAll(medias);
}
if (displayedList.isEmpty()) {
displayedList.add(new CatalogError());
}
LogDev.w(TAG, "displayedList.size() : " + displayedList.size());
notifyDataSetChanged();
}
#Override public CatalogViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TITLE:
return new TitleViewHolder(inflater.inflate(R.layout.item_list_title_catalog, parent, false));
case USER:
return new UserViewHolder(inflater.inflate(R.layout.widget_user_small, parent, false));
case PROGRAM:
case DEMO:
return new ProgramViewHolder(inflater.inflate(R.layout.widget_program_small, parent, false));
case COURSE:
return new CourseViewHolder(inflater.inflate(R.layout.widget_course_small, parent, false));
case GROUP:
return new GroupViewHolder(inflater.inflate(R.layout.widget_group_small, parent, false));
case MEDIA:
return new MediaViewHolder(inflater.inflate(R.layout.widget_media_small, parent, false));
case ERROR:
return new CatalogErrorViewHolder(inflater.inflate(R.layout.widget_noresult_small, parent, false));
default:
LogDev.e(TAG, "view type not supported");
return null;
}
}
#Override public void onBindViewHolder(CatalogViewHolder holder, int position) {
Object displayedObject = displayedList.get(position);
//holder.bind(displayedObject, errorDisplayInterface);
if (holder instanceof TitleViewHolder && displayedObject instanceof String) {
((TitleViewHolder) holder).tv.setText((String) displayedObject);
} else if (holder instanceof ProgramViewHolder && displayedObject instanceof CatalogProgram) {
bindProgramViewHolder((ProgramViewHolder) holder, (CatalogProgram) displayedObject);
} else if (holder instanceof CourseViewHolder && displayedObject instanceof CatalogCourse) {
bindCourseViewHolder((CourseViewHolder) holder, (CatalogCourse) displayedObject);
} else if (holder instanceof GroupViewHolder && displayedObject instanceof FilterableGroup) {
bindGroupViewHolder((GroupViewHolder) holder, (FilterableGroup) displayedObject);
} else if (holder instanceof UserViewHolder && displayedObject instanceof FilterableUser) {
bindUserViewHolder((UserViewHolder) holder, (FilterableUser) displayedObject);
} else if (holder instanceof MediaViewHolder && displayedObject instanceof CatalogMedia) {
bindMediaViewHolder((MediaViewHolder) holder, (CatalogMedia) displayedObject);
} else if (holder instanceof CatalogErrorViewHolder) {
//No binding with any data
} else {
throw new ClassCastException(displayedObject.toString());
}
//Highlight
if (searchString != null && !searchString.isEmpty())
{
TextViewHighlighter.highlight(holder, searchString);
}
}
private void bindCourseViewHolder(final CourseViewHolder courseViewHolder, final CatalogCourse course) {
courseViewHolder.name_textView.setText(course.name);
courseViewHolder.viewNb_textView.setText(course.views != null ? course.views.toString() : "0");
if (course.elementCount == null) {
courseViewHolder.counterLinear.setVisibility(View.GONE);
} else {
courseViewHolder.counterLinear.setVisibility(View.VISIBLE);
courseViewHolder.questionNb_textView.setText(
course.elementCount.questions != null ? course.elementCount.questions.toString() : "0");
courseViewHolder.mediaNb_textView.setText(
course.elementCount.medias != null ? course.elementCount.medias.toString() : "0");
courseViewHolder.sheetNb_textView.setText(
course.elementCount.sheets != null ? course.elementCount.sheets.toString() : "0");
}
imageManagerFactory.course(course.id).thumbnail(courseViewHolder.pic_imageView);
//new CourseImageManager(course.id).load(courseViewHolder.pic_imageView);
View.OnClickListener clickListener = new View.OnClickListener() {
#Override public void onClick(View view) {
flowController.routeToCourse(course.id);
}
};
courseViewHolder.container.setOnClickListener(clickListener);
if (course.canBeOffline) {
courseViewHolder.downloadBlock.setVisibility(View.VISIBLE);
DownloadState state = downloadCenter.getCourseStatus(course.id);
LogDev.i(TAG, "can be offline " + state.name());
if (state == DownloadState.DOWNLOADING) {
updateDownloadBlock(courseViewHolder, DownloadableStatus.DOWNLOADING);
}
if (state == DownloadState.TO_DOWNLOAD) {
updateDownloadBlock(courseViewHolder, DownloadableStatus.DOWNLOADABLE);
}
if (state == DownloadState.DOWNLOADED || state == DownloadState.DOWNLOADED_WITH_SHARED_MODE) {
updateDownloadBlock(courseViewHolder, DownloadableStatus.DOWNLOADED);
} else {
DownloadStatusCallback statusCallback = new DownloadStatusCallback() {
#Override public void started() {
LogDev.i(TAG, "started");
mainThreadHandler.post(new Runnable() {
#Override public void run() {
updateDownloadBlock(courseViewHolder, DownloadableStatus.DOWNLOADING);
}
});
}
#Override public void finished() {
mainThreadHandler.post(new Runnable() {
#Override public void run() {
updateDownloadBlock(courseViewHolder, DownloadableStatus.DOWNLOADED);
}
});
}
#Override public void error(Exception e) {
mainThreadHandler.post(new Runnable() {
#Override public void run() {
updateDownloadBlock(courseViewHolder, DownloadableStatus.ERROR);
}
});
}
};
downloadCenter.subscribe(course.id, statusCallback);
courseViewHolder.downloadBlock.setOnClickListener(new View.OnClickListener()
{
#Override public void onClick(View v) {
new Thread() {
#Override public void run() {
super.run();
try {
downloadCenter.downloadCourse(course.id, null);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
});
}
} else {
LogDev.i(TAG, "can't be offline");
courseViewHolder.downloadBlock.setVisibility(View.INVISIBLE);
}
}
private void updateDownloadBlock(CourseViewHolder courseViewHolder, DownloadableStatus status) {
if (status == null) return;
courseViewHolder.downloadBlock.setVisibility(
status.equals(DownloadableStatus.NOT_DOWNLOADABLE) ? View.GONE : View.VISIBLE);
courseViewHolder.downloadImage.setVisibility(
status.equals(DownloadableStatus.DOWNLOADABLE) ? View.VISIBLE : View.GONE);
courseViewHolder.downloadProgress.setVisibility(
status.equals(DownloadableStatus.DOWNLOADING) ? View.VISIBLE : View.GONE);
courseViewHolder.downloadedImage.setVisibility(
status.equals(DownloadableStatus.DOWNLOADED) ? View.VISIBLE : View.GONE);
courseViewHolder.downloadErrImage.setVisibility(
status.equals(DownloadableStatus.ERROR) ? View.VISIBLE : View.GONE);
}
private enum DownloadableStatus {
NOT_DOWNLOADABLE, DOWNLOADABLE, DOWNLOADING, DOWNLOADED, ERROR
}
private void bindProgramViewHolder(ProgramViewHolder programViewHolder, final CatalogProgram program) {
imageManagerFactory.program(program.id).thumbnail(programViewHolder.pic_imageView);
//new ProgramImageManager(program.id).load(programViewHolder.pic_imageView);
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override public void onClick(View view) {
flowController.routeToProgram(program.id);
}
};
programViewHolder.container.setOnClickListener(onClickListener);
programViewHolder.pic_imageView.setOnClickListener(onClickListener);
programViewHolder.title_textView.setText(program.name);
programViewHolder.viewCount_textView.setText(program.views != null ? program.views.toString() : "0");
}
private void bindUserViewHolder(UserViewHolder userViewHolder, final FilterableUser user) {
userViewHolder.name_textView.setText(user.name);
userViewHolder.job_textView.setText(user.description);
imageManagerFactory.user(user.id).thumbnail(userViewHolder.pic_imageView);
//new UserImageManager(user.id).loadProfilePic(userViewHolder.pic_imageView, NetworkUtils.isNetworkAvailable(),
// true);
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override public void onClick(View view) {
flowController.routeToUser(user.id);
}
};
userViewHolder.pic_imageView.setOnClickListener(onClickListener);
userViewHolder.container.setOnClickListener(onClickListener);
}
private void bindMediaViewHolder(MediaViewHolder mediaViewHolder, final CatalogMedia media) {
imageManagerFactory.media(media.id, media.type, media.extention).symbolOnThumbnail(mediaViewHolder.complex);
//new MediaImageManager(media).load(mediaViewHolder.pic_imageView, NetworkUtils.isNetworkAvailable(), false);
mediaViewHolder.title_textView.setText(media.title);
mediaViewHolder.authorName_textView.setText(media.authorName);
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override public void onClick(final View view) {
flowController.routeToDocument(media.id);
}
};
mediaViewHolder.complex.setOnClickListener(onClickListener);
mediaViewHolder.container.setOnClickListener(onClickListener);
}
private void bindGroupViewHolder(GroupViewHolder groupViewHolder, final FilterableGroup group) {
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override public void onClick(View view) {
flowController.routeToGrouop(group.id);
}
};
groupViewHolder.pic_imageView.setOnClickListener(onClickListener);
groupViewHolder.container.setOnClickListener(onClickListener);
groupViewHolder.name_textView.setText(group.name);
String str = resourcesStringRepository.getQuantityString(R.plurals.catalog_group_stat_program,
group.nbProgramsRunning, group.nbProgramsRunning);
str += " - " + resourcesStringRepository.getQuantityString(R.plurals.catalog_group_stat_user, group.nbUser,
group.nbUser);
groupViewHolder.stats_textView.setText(str);
imageManagerFactory.group(group.id).thumbnail(groupViewHolder.pic_imageView);
//new GroupImageManager(group.id).load(groupViewHolder.pic_imageView, NetworkUtils.isNetworkAvailable(), true);
}
#Override public int getItemCount() {
return displayedList.size();
}
}
It is recycling the views.So while clicking the button you have to store its position and change views accordingly.
Maintain a position storing variable globally like this
private int itemClicked=-1;
While clicking the view store the position into itemclicked
holder.button1.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View view) {
itemclicked=position;
assyncCall(statusCallback);
}
});
Then while updating views check if the position is same like this
if(position==itemclicked){
//show download for clicked view
}else{
//show download stopped for other views
}
Solution
As Surender and Trickcy Solution suggested, I updated the presented data and then tell the adapter to update the cell accordingly :
DownloadStatusCallback statusCallback = new DownloadStatusCallback() {
#Override public void started() {
LogDev.i(TAG, "started");
course.downloadState = DownloadState.DOWNLOADING;
final int position = courseViewHolder.getAdapterPosition();
mainThreadHandler.post(new Runnable() {
#Override public void run() {
notifyItemChanged(position);
}
});
}
#Override public void finished() {
course.downloadState = DownloadState.DOWNLOADED;
final int position = courseViewHolder.getAdapterPosition();
mainThreadHandler.post(new Runnable() {
#Override public void run() {
notifyItemChanged(position);
}
});
}
#Override public void error(Exception e) {
course.downloadState = DownloadState.ERROR_WHILE_DOWNLOADING;
final int position = courseViewHolder.getAdapterPosition();
mainThreadHandler.post(new Runnable() {
#Override public void run() {
notifyItemChanged(position);
}
});
}
};
I have a FragmentActivity with a FragmentMediaOverview containing a list of MediaItemViews (each with a imageview and some text) and a click on one of the items opening a detail-Fragment.
Now when I go back (via back button) and forth (click on listitem) several times from list to detail fragment I eventually run into OOM-Errors. I use SoftReferences for the bitmaps in the listitems as well as in the detail fragment.
According to MAT there is an incresing number of MediaItemViews as well as FragmentMediaOverview instances, but I just cannot figure out why.
I read this Android: AlertDialog causes a memory leak , but couldn't solve it nulling out listeners.
Here is my code:
FragmentMediaOverview.java
(This is not a ListFragment because for a tablet-layout the MediaAdapter needs to connect to a gridview)
public class FragmentMediaOverview extends Fragment {
private static String TAG = FragmentMediaOverview.class.getSimpleName();
private MediaAdapter adapter;
private OnMediaSelectedListener selListener;
private ArrayList<BOObject> mediaItems;
private ViewGroup layoutContainer;
private AdapterView itemContainer; // list or gridview
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
layoutContainer = (ViewGroup) inflater.inflate(R.layout.fragment_media_overview, null);
return layoutContainer;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
selListener = (OnMediaSelectedListener) activity;
}
#Override
public void onDestroy() {
super.onDestroy();
itemContainer.setOnItemClickListener(null);
selListener = null;
adapter = null;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initUi(layoutContainer);
displayMedia();
}
private void initUi(ViewGroup layoutContainer) {
itemContainer = (AdapterView) layoutContainer.findViewById(android.R.id.list);
itemContainer.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BOMedia mediaItem = ((BOMedia) mediaItems.get(position));
//the FragmentActivity is coordinating the FragmentTransactions
selListener.onMediaSelected(mediaItem);
}
});
}
private void displayMedia() {
Log.d(TAG, "Displaying List");
if (mediaItems == null) {
loadMedia();
return;
}
Log.d(TAG, "List: " + mediaItems.size() + ", adapter: " + itemContainer.getAdapter());
if (adapter == null) {
Log.d(TAG, "Create Adapter with " + mediaItems.size());
adapter = new MediaAdapter(getActivity(), mediaItems);
}
if (itemContainer.getAdapter() == null) {
itemContainer.setAdapter(adapter);
} else {
adapter.setItems(mediaItems);
adapter.notifyDataSetChanged();
}
}
private void loadMedia() {
FragmentHelper.showProgressSpinner(layoutContainer, android.R.id.list);
DbHelper.getInstance().getMedia(mediaType, new DbQueryFinishListener() {
#Override
public void onDbCallFinish(ArrayList<BOObject> objects) {
if (!getActivity().isFinishing()) {
mediaItems = objects;
Collections.sort(mediaItems, new Comparator<BOObject>() {
final Collator c = Collator.getInstance(Locale.GERMAN);
#Override
public int compare(BOObject s1, BOObject s2) {
if (s2 != null && ((BOMedia) s2).getTitle() != null && s1 != null
&& ((BOMedia) s1).getTitle() != null) {
return c.compare(((BOMedia) s1).getTitle(),((BOMedia) s2).getTitle());
} else {
return 0;
}
}
});
displayMedia();
FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list);
}
}
#Override
public void onDbCallException(Exception exception) {
if (!getActivity().isFinishing()) {
FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list);
}
}
});
}
}
MediaAdapter.java
public class MediaAdapter extends BaseAdapter {
private static final String TAG = MediaAdapter.class.getSimpleName();
private Context context;
private ArrayList<BOObject> mediaItems;
public MediaAdapter(Context c, ArrayList<BOObject> mediaItems) {
super();
context = c;
this.mediaItems = mediaItems;
}
#Override
public int getCount() {
return mediaItems.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = new MediaItemView(context);
}
((MediaItemView)convertView).initialize((BOMedia) mediaItems.get(position));
return convertView;
}
public void setItems(ArrayList<BOObject> mediaItems) {
this.mediaItems = mediaItems;
}
}
MediaItemView.java
public class MediaItemView extends LinearLayout {
private static final String TAG = MediaItemView.class.getSimpleName();
private BOMedia item;
private SoftReference<Bitmap> bm;
private ImageView iv;
private Context ctx;
public MediaItemView(Context context) {
super(context);
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.view_media_item, this);
this.ctx = context;
}
/** Init the view with a new BOMedia object
* #param mediaItem
*/
public void initialize(BOMedia mediaItem) {
this.item = mediaItem;
initUI();
}
private void initUI() {
TextView title = (TextView) findViewById(R.id.itemText);
iv = (ImageView) findViewById(R.id.itemImage);
title.setText(Html.fromHtml(item.getTitle()));
iv.setImageBitmap(null);
bm = null;
System.gc();
iv.invalidate();
if (item.getFilepathThumb() != null && !item.getFilepathThumb().equals("")) {
ExpansionPackManager.getInstance().getBitmapResource(item.getFilepathThumb(), false,
new BitmapReadListener() {
#Override
public void onFileRead(BitmapResponseMessage message) {
Log.d(TAG, "Bitmap read: " + message.getFilepath());
Bitmap image = message.getBitmap();
if (image != null && message.getFilepath().equals(item.getFilepathThumb())) {
bm = new SoftReference<Bitmap>(image);
iv.setImageBitmap(bm.get());
Log.d(TAG, "image set");
} else {
Log.d(TAG, "image too late: " + image);
}
}
#Override
public void onFileException(Throwable exception) {
Log.d(TAG, "image exception");
}
});
}
}
}
In MediaItemView the size of your bitmap must be too big. If the bitmap is 600x600 and you want to display a image with a size of 50x50 you can use Bitmap.createScaledBitmap. You should also use bitmap cache while loading your bitmap.
This is because the View for rach child in the ListView is recreated as you scroll through. This is very heavy on resources. To avoid this use a holder class in adapters getView() to hold and reuse the views. This is called an Efficient Adapter. For example see Efficient List Adapter in API demos. http://developer.android.com/tools/samples/index.html
You can also use:
android:hardwareAccelerated = true
Beginning in Android 3.0 (API level 11), the Android 2D rendering pipeline is designed to better support hardware acceleration. Hardware acceleration carries out all drawing operations that are performed on a View's canvas using the GPU.
For more info http://developer.android.com/guide/topics/graphics/hardware-accel.html