Search code examples
androidandroid-recyclerviewimage-loading

Image loading using Picasso or other libraries are slow in my application


I'm using Picasso in my application inside an adapter and everything is fine except image loading performance while scrolling. If I scroll the list, on loading every images the scroll will be stopped a moment and that's a bad performance. I don't see this issue on other applications.

Note: I'm using endless RecyclerView and images path are URL.

public class UltimateViewAdapterr extends UltimateViewAdapter<UltimateViewAdapterr.CellFeedViewHolder>
        implements View.OnClickListener {

    private static final DecelerateInterpolator DECCELERATE_INTERPOLATOR = new DecelerateInterpolator();
    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
    private static final OvershootInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator(4);

    private static final int ANIMATED_ITEMS_COUNT = 2;

    private static final int VIEW_TYPE_EMPTY_LIST_PLACEHOLDER = 0;
    private static final int VIEW_TYPE_OBJECT_VIEW = 1;

    private Context context;
    private int lastAnimatedPosition = -1;
    private boolean animateItems = false;
    private List<Post> mPosts;

    private OnFeedItemClickListener onFeedItemClickListener;

    private final Map<RecyclerView.ViewHolder, AnimatorSet> likeAnimations = new HashMap<>();
    private final ArrayList<Integer> likedPositions = new ArrayList<>();

    public UltimateViewAdapterr(Context context, List<Post> postList) {
        this.context = context;
        mPosts = postList;
    }

    @Override
    public CellFeedViewHolder getViewHolder(View view) {
        return null;
    }

    @Override
    public CellFeedViewHolder onCreateViewHolder(ViewGroup viewGroup) {
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.fragment_person_profile_timeline, viewGroup,
                        false);
        CellFeedViewHolder cellFeedViewHolder = new CellFeedViewHolder(view);

        cellFeedViewHolder.btnComments.setOnClickListener(this);
        cellFeedViewHolder.btnMore.setOnClickListener(this);
        cellFeedViewHolder.postPicture.setOnClickListener(this);
        cellFeedViewHolder.likeImageButton.setOnClickListener(this);
        cellFeedViewHolder.pictureImageView.setOnClickListener(this);

        return cellFeedViewHolder;
    }

    private void runEnterAnimation(View view, int position) {
        if (!animateItems || position >= ANIMATED_ITEMS_COUNT - 1) {
            return;
        }

        if (position > lastAnimatedPosition) {
            lastAnimatedPosition = position;
            view.setTranslationY(Utils.getScreenHeight(context));
            view.animate()
                    .translationY(0)
                    .setInterpolator(new DecelerateInterpolator(3.f))
                    .setDuration(700)
                    .start();
        }
    }

    @Override
    public void onBindViewHolder(CellFeedViewHolder viewHolder, int
            position) {
        runEnterAnimation(viewHolder.itemView, position);
        bindFeedItem(position, viewHolder);
    }

    private void bindFeedItem(int position, CellFeedViewHolder holder) {
        Post item = mPosts.get(position);
        holder.nameTextView.setText(item.getSenderProfile().getName());

        int likesCount = item.getLikes().size();
        holder.likesTextView.setText(Integer.toString(likesCount));

        holder.pictureImageView.setTag(item.getSenderProfile());

        if (item.getSenderProfile().getImage() != null) {
            Picasso.with(context).load(Constants.General.PROTOCOL + item.getImage().getImageAddress
                    ()).into(holder.pictureImageView);
        }

        if (item.getImage() != null) {
            int width = Utils.dpToPx(context.getResources().getConfiguration().screenWidthDp);
            int height = Utils.dpToPx(context.getResources().getConfiguration().screenHeightDp);
            Picasso.with(context).load(Constants.General.PROTOCOL + item.getImage()
                    .getImageAddress
                    ()).priority(Picasso.Priority.HIGH).placeholder(R.drawable.ic_file_image_box).into
                    (holder.postPicture);
            holder.postPictureContainer.setVisibility(View.VISIBLE);
        }
        /*else if (item.getVideo() != null) {
            //TODO implement add video
        }*/

        if (!item.getText().isEmpty()) {
            holder.postTextTextView.setText(item.getText());
            holder.postTextTextView.setVisibility(View.VISIBLE);
        }

        if (item.getLikes() != null) {
            holder.likesTextView.setText(Integer.toString(item.getLikes().size()));
        }

        holder.footerContainer.setTag(item);
        holder.btnMore.setTag(item.getId().toString() + "," + position);
        Hashtable<CellFeedViewHolder, Post> hashtagPost = new Hashtable<>();
        hashtagPost.put(holder, item);
        holder.postPicture.setTag(hashtagPost);
        Hashtable<CellFeedViewHolder, String> hashtableTag = new Hashtable<>();
        hashtableTag.put(holder, "false");
        holder.likeImageButton.setTag(hashtableTag);
        for (Profile profile : item.getLikes()) {
            if (profile.getId() == Logged.Models.getUserProfile().getId()) {
                holder.likeImageButton.setTag(hashtableTag.put(holder, "true"));
                holder.likeImageButton.setImageResource(R.drawable.ic_heart);
                break;
            }
        }

        for (Profile profile : item.getLikes()) {
            if (profile.getId() == Logged.Models.getUserProfile().getId()) {
                holder.likeImageButton.setImageDrawable(context.getResources()
                        .getDrawable(R.drawable
                                .ic_heart));
                holder.likeImageButton.setTag(hashtableTag.put(holder, "true"));
                break;
            }
        }

        holder.likesContainer.setTag(item);

        holder.likesContainer.setOnClickListener(this);

        if (likeAnimations.containsKey(holder)) {
            likeAnimations.get(holder).cancel();
        }
        resetLikeAnimationState(holder);
    }

    @Override
    public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup viewGroup) {
        return null;
    }

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder viewHolder, int i) {

    }

    @Override
    public int getItemCount() {
        return mPosts.size();
    }

    @Override
    public int getAdapterItemCount() {
        return 0;
    }

    @Override
    public long generateHeaderId(int i) {
        return 0;
    }

    private void updateHeartButton(final CellFeedViewHolder holder, boolean animated) {
        if (animated) {
            if (!likeAnimations.containsKey(holder)) {
                AnimatorSet animatorSet = new AnimatorSet();
                likeAnimations.put(holder, animatorSet);

                ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(holder.likeImageButton, "rotation", 0f, 360f);
                rotationAnim.setDuration(300);
                rotationAnim.setInterpolator(ACCELERATE_INTERPOLATOR);

                ObjectAnimator bounceAnimX = ObjectAnimator.ofFloat(holder.likeImageButton, "scaleX", 0.2f, 1f);
                bounceAnimX.setDuration(300);
                bounceAnimX.setInterpolator(OVERSHOOT_INTERPOLATOR);

                ObjectAnimator bounceAnimY = ObjectAnimator.ofFloat(holder.likeImageButton, "scaleY", 0.2f, 1f);
                bounceAnimY.setDuration(300);
                bounceAnimY.setInterpolator(OVERSHOOT_INTERPOLATOR);
                bounceAnimY.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        holder.likeImageButton.setImageResource(R.drawable.ic_heart);
                    }
                });

                animatorSet.play(rotationAnim);
                animatorSet.play(bounceAnimX).with(bounceAnimY).after(rotationAnim);

                animatorSet.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        resetLikeAnimationState(holder);
                    }
                });

                animatorSet.start();
            }
        } else {
            if (likedPositions.contains(holder.getPosition())) {
                holder.likeImageButton.setImageResource(R.drawable.ic_heart);
            } else {
                holder.likeImageButton.setImageResource(R.drawable.ic_heart_outline);
            }
        }
    }

    public void setOnFeedItemClickListener(OnFeedItemClickListener onFeedItemClickListener) {
        this.onFeedItemClickListener = onFeedItemClickListener;
    }

    private void likePost(final CellFeedViewHolder holder, Post post) {

        updateLikes(holder);

        HttpClient.get(String.format(Constants.Server.GET_POST_LIKE, post.getId(),
                Logged.Models
                        .getUserProfile().getId()), new
                AsyncHttpResponseHandler() {
                    @Override
                    public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {

                    }

                    @Override
                    public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
                        Toast.makeText(context, context.getString(R.string.toast_error_like),
                                Toast.LENGTH_SHORT)
                                .show();
                        updateUnlikes(holder);
                    }
                });
    }

    private void updateLikes(final CellFeedViewHolder holder) {
        int likesCount = Integer.parseInt(holder.likesTextView
                .getText().toString()) + 1;
        holder.likesTextView.setText(Integer.toString(likesCount));
        updateHeartButton(holder, true);
        Hashtable<CellFeedViewHolder, String> hashTags = new Hashtable<>();
        hashTags.put(holder, "true");
        holder.likeImageButton.setTag(hashTags);
    }

    private void updateUnlikes(final CellFeedViewHolder holder) {
        int likesCount = Integer.parseInt(holder.likesTextView
                .getText().toString()) - 1;
        holder.likesTextView.setText(Integer.toString(likesCount));
        holder.likeImageButton.setImageResource(R.drawable.ic_heart_outline);
        Hashtable<CellFeedViewHolder, String> hashTags = new Hashtable<>();
        hashTags.put(holder, "false");
        holder.likeImageButton.setTag(hashTags);
    }

    private void unlikePost(final CellFeedViewHolder holder, Post post) {

        updateUnlikes(holder);

        HttpClient.get(String.format(Constants.Server.GET_POST_UNLIKE, post.getId()
                , Logged.Models
                .getUserProfile().getId()), new
                AsyncHttpResponseHandler() {
                    @Override
                    public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {

                    }

                    @Override
                    public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
                        Toast.makeText(context, context.getString(R.string.toast_error_like),
                                Toast.LENGTH_SHORT)
                                .show();
                        updateLikes(holder);
                    }
                });
    }

    @Override
    public void onClick(final View view) {
        switch (view.getId()) {
            case R.id.image_button_comments:
                LinearLayout footerContainer = (LinearLayout) view.getParent();
                context.startActivity(new Intent(context, PersonProfilePostCommentsActivity
                        .class).putExtra("post", (Post) footerContainer.getTag()));
                break;
            case R.id.image_button_more:
                if (onFeedItemClickListener != null) {
                    onFeedItemClickListener.onMoreClick(view, (String) view.getTag());
                }
                break;
            case R.id.image_button_like:
                Post post = (Post) ((LinearLayout) view.getParent()).getTag();
                final Hashtable hashTags = (Hashtable<CellFeedViewHolder, String>) view
                        .getTag();
                CellFeedViewHolder holder = (CellFeedViewHolder) hashTags.keys().nextElement();
                String isLiked = (String) hashTags.values().iterator().next();
                if (isLiked.equals("false")) {
                    likePost(holder, post);
                } else {
                    unlikePost(holder, post);
                }
                break;
            case R.id.image_view_post_picture:
                final Hashtable postPictureTags = (Hashtable<CellFeedViewHolder, Post>) view
                        .getTag();
                CellFeedViewHolder postPictureHolder = (CellFeedViewHolder) postPictureTags.keys()
                        .nextElement();
                Post postPicturePost = (Post) postPictureTags.values().iterator().next();
                Hashtable<CellFeedViewHolder, String> btnLikeTags = (Hashtable<CellFeedViewHolder, String>) postPictureHolder
                        .likeImageButton.getTag();

                if (btnLikeTags.values().iterator().next().equals("false")) {
                    likePost(postPictureHolder, postPicturePost);
                }
                break;
            case R.id.linear_layout_post_likes:
                context.startActivity(new Intent(context, PostLikesActivity.class).putExtra
                        ("post", (Post) view.getTag()));
                break;

            case R.id.image_view_picture:
                context.startActivity(new Intent(context, PersonProfileActivity
                        .class).putExtra("profile", (Profile) view.getTag()));
                break;
        }
    }

    private void animatePhotoLike(final CellFeedViewHolder holder) {
        if (!likeAnimations.containsKey(holder)) {
            holder.vBgLike.setVisibility(View.VISIBLE);
            holder.ivLike.setVisibility(View.VISIBLE);

            holder.vBgLike.setScaleY(0.1f);
            holder.vBgLike.setScaleX(0.1f);
            holder.vBgLike.setAlpha(1f);
            holder.ivLike.setScaleY(0.1f);
            holder.ivLike.setScaleX(0.1f);

            AnimatorSet animatorSet = new AnimatorSet();
            likeAnimations.put(holder, animatorSet);

            ObjectAnimator bgScaleYAnim = ObjectAnimator.ofFloat(holder.vBgLike, "scaleY", 0.1f, 1f);
            bgScaleYAnim.setDuration(200);
            bgScaleYAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
            ObjectAnimator bgScaleXAnim = ObjectAnimator.ofFloat(holder.vBgLike, "scaleX", 0.1f, 1f);
            bgScaleXAnim.setDuration(200);
            bgScaleXAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
            ObjectAnimator bgAlphaAnim = ObjectAnimator.ofFloat(holder.vBgLike, "alpha", 1f, 0f);
            bgAlphaAnim.setDuration(200);
            bgAlphaAnim.setStartDelay(150);
            bgAlphaAnim.setInterpolator(DECCELERATE_INTERPOLATOR);

            ObjectAnimator imgScaleUpYAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleY", 0.1f, 1f);
            imgScaleUpYAnim.setDuration(300);
            imgScaleUpYAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
            ObjectAnimator imgScaleUpXAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleX", 0.1f, 1f);
            imgScaleUpXAnim.setDuration(300);
            imgScaleUpXAnim.setInterpolator(DECCELERATE_INTERPOLATOR);

            ObjectAnimator imgScaleDownYAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleY", 1f, 0f);
            imgScaleDownYAnim.setDuration(300);
            imgScaleDownYAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
            ObjectAnimator imgScaleDownXAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleX", 1f, 0f);
            imgScaleDownXAnim.setDuration(300);
            imgScaleDownXAnim.setInterpolator(ACCELERATE_INTERPOLATOR);

            animatorSet.playTogether(bgScaleYAnim, bgScaleXAnim, bgAlphaAnim, imgScaleUpYAnim, imgScaleUpXAnim);
            animatorSet.play(imgScaleDownYAnim).with(imgScaleDownXAnim).after(imgScaleUpYAnim);

            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    resetLikeAnimationState(holder);
                }
            });
            animatorSet.start();
        }
    }

    private void resetLikeAnimationState(CellFeedViewHolder holder) {
        likeAnimations.remove(holder);
        holder.vBgLike.setVisibility(View.GONE);
        holder.ivLike.setVisibility(View.GONE);
    }

    public interface OnFeedItemClickListener {
        public void onCommentsClick(View v, int position);

        public void onMoreClick(View view, String tag);

        public void onProfileClick(View v);
    }

    public static class CellFeedViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.image_view_post_picture)
        ImageView postPicture;
        @Bind(R.id.image_button_comments)
        ImageButton btnComments;
        @Bind(R.id.image_button_like)
        ImageButton likeImageButton;
        @Bind(R.id.image_button_more)
        ImageButton btnMore;
        @Bind(R.id.vBgLike)
        View vBgLike;
        @Bind(R.id.ivLike)
        ImageView ivLike;
        @Bind(R.id.text_view_name)
        TextView nameTextView;
        @Bind(R.id.image_view_picture)
        ImageView pictureImageView;
        @Bind(R.id.text_view_likes)
        TextView likesTextView;
        @Bind(R.id.linear_layout_footer_container)
        LinearLayout footerContainer;
        @Bind(R.id.vImageRoot)
        SquaredFrameLayout postPictureContainer;
        @Bind(R.id.text_view_text)
        TextView postTextTextView;
        @Bind(R.id.linear_layout_post_likes)
        LinearLayout likesContainer;

        public CellFeedViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }
}

Solution

  • When scrollview or listview scrolls view clears its children and set new values. In your case picasso fetchs image after that set image into imageview. 1- Volley handles this problem. If you can switch picasso to volley, do it. 2- You can cache images on first time , after that whenever you scroll check the cache memory then set image. But it increase app memory. You have to clear images that has cached