3
\$\begingroup\$

I'm having an issue where when the recyclerview has a big amount of items (say 2000) the scrolling is really laggy.

Here's the Fragment code:

package jahirfiquitiva.apps.iconshowcase.fragments;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;

import java.util.ArrayList;
import java.util.Locale;

import jahirfiquitiva.apps.iconshowcase.R;
import jahirfiquitiva.apps.iconshowcase.adapters.IconsAdapter;
import jahirfiquitiva.apps.iconshowcase.utilities.Preferences;
import jp.wasabeef.recyclerview.adapters.AlphaInAnimationAdapter;
import jp.wasabeef.recyclerview.adapters.ScaleInAnimationAdapter;

public class IconsFragment extends Fragment {

    private IconsAdapter mAdapter;
    private Preferences mPrefs;
    private ArrayList<String> iconsNames, filteredIconsList;
    private ArrayList<Integer> iconsInts, filteredIconsInts;
    private ViewGroup layout;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        mPrefs = new Preferences(getActivity());

        if (layout != null) {
            ViewGroup parent = (ViewGroup) layout.getParent();
            if (parent != null) {
                parent.removeView(layout);
            }
        }
        try {
            layout = (ViewGroup) inflater.inflate(R.layout.icons_grid, container, false);
        } catch (InflateException e) {

        }

        RecyclerFastScroller fastScroller =
                (RecyclerFastScroller) layout.findViewById(R.id.rvFastScroller);
        fastScroller.setVisibility(View.GONE);

        RecyclerView iconsGrid = (RecyclerView) layout.findViewById(R.id.iconsGrid);
        iconsGrid.setHasFixedSize(true);
        iconsGrid.setLayoutManager(new GridLayoutManager(getActivity(),
                getResources().getInteger(R.integer.icon_grid_width)));

        mAdapter = new IconsAdapter(getActivity(), new ArrayList<String>(), new ArrayList<Integer>());

        if (getArguments() != null) {
            iconsNames = getArguments().getStringArrayList("iconsNamesList");
            iconsInts = getArguments().getIntegerArrayList("iconsArray");
            mAdapter.setIcons(iconsNames, iconsInts);
        }

        iconsGrid.setAdapter(mPrefs.getAnimationsEnabled() ? animAdapter(mAdapter) : mAdapter);
        fastScroller.setRecyclerView(iconsGrid);
        fastScroller.setHideDelay(500);
        fastScroller.setVisibility(View.VISIBLE);

        return layout;
    }

    public static IconsFragment newInstance(ArrayList<String> iconsNames, ArrayList<Integer> iconsArray) {
        IconsFragment fragment = new IconsFragment();
        Bundle args = new Bundle();
        args.putStringArrayList("iconsNamesList", iconsNames);
        args.putIntegerArrayList("iconsArray", iconsArray);
        fragment.setArguments(args);
        return fragment;
    }

    public void performSearch(String query) {
        filter(query, mAdapter);
    }

    private synchronized void filter(CharSequence s, IconsAdapter adapter) {
        if (s == null || s.toString().trim().isEmpty()) {
            if (filteredIconsList != null) {
                filteredIconsList = null;
            }
            if (filteredIconsInts != null) {
                filteredIconsList = null;
            }
            adapter.clearIconsList();
            adapter.setIcons(iconsNames, iconsInts);
            adapter.notifyDataSetChanged();
        } else {
            if (filteredIconsList != null) {
                filteredIconsList.clear();
            }
            if (filteredIconsInts != null) {
                filteredIconsList = null;
            }
            filteredIconsList = new ArrayList<String>();
            filteredIconsInts = new ArrayList<Integer>();
            for (int i = 0; i < iconsNames.size(); i++) {
                String name = iconsNames.get(i);
                if (name.toLowerCase(Locale.getDefault())
                        .startsWith(s.toString().toLowerCase(Locale.getDefault()))) {
                    filteredIconsList.add(iconsNames.get(i));
                    filteredIconsInts.add(iconsInts.get(i));
                }
            }
            adapter.clearIconsList();
            adapter.setIcons(filteredIconsList, filteredIconsInts);
            adapter.notifyDataSetChanged();
        }
    }

    private ScaleInAnimationAdapter animAdapter(IconsAdapter iconsAdapter) {
        AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(iconsAdapter);
        ScaleInAnimationAdapter scaleAdapter = new ScaleInAnimationAdapter(alphaAdapter);
        scaleAdapter.setFirstOnly(true);
        return scaleAdapter;
    }

}

And RecyclerView adapter:

package jahirfiquitiva.apps.iconshowcase.adapters;

import android.content.Context;
import android.content.res.Resources;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.afollestad.materialdialogs.MaterialDialog;

import java.util.ArrayList;
import java.util.Locale;

import jahirfiquitiva.apps.iconshowcase.R;
import jahirfiquitiva.apps.iconshowcase.utilities.Util;

public class IconsAdapter extends RecyclerView.Adapter<IconsAdapter.IconsHolder> implements View.OnClickListener {

    private final Context context;
    final Resources r;
    final String p;
    private ArrayList<String> iconsList = new ArrayList<>();
    private ArrayList<Integer> iconsArray = new ArrayList<>();

    public IconsAdapter(Context context, ArrayList<String> iconsList, ArrayList<Integer> iconsArray) {
        this.context = context;
        this.iconsList = iconsList;
        this.iconsArray = iconsArray;
        r = context.getResources();
        p = context.getPackageName();
    }

    public void setIcons(ArrayList<String> iconsList, ArrayList<Integer> iconsArray) {
        this.iconsList.addAll(iconsList);
        this.iconsArray.addAll(iconsArray);
        this.notifyItemRangeInserted(0, iconsList.size() - 1);
    }

    public void clearIconsList() {
        this.iconsList.clear();
        this.iconsArray.clear();
    }

    @Override
    public IconsHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        return new IconsHolder(inflater.inflate(R.layout.item_icon, parent, false));
    }

    @Override
    public void onBindViewHolder(IconsHolder holder, int position) {
        if (iconsArray.size() > 0) {
            holder.icon.setImageResource(iconsArray.get(position));
        }
        holder.view.setTag(position);
        holder.view.setOnClickListener(this);
        setAnimation(holder.icon, position);
    }

    private int lastPosition = -1;

    private void setAnimation(View viewToAnimate, int position) {
        if (position > lastPosition) {
            viewToAnimate.setHasTransientState(true);
            lastPosition = position;
        }
    }

    @Override
    public int getItemCount() {
        return iconsList == null ? 0 : iconsList.size();
    }

    @Override
    public void onClick(View v) {
        int position = (Integer) v.getTag();
        int resId = iconsArray.get(position);
        String name = iconsList.get(position).toLowerCase(Locale.getDefault());

        MaterialDialog dialog = new MaterialDialog.Builder(context)
                .customView(R.layout.dialog_icon, false)
                .title(Util.makeTextReadable(name))
                .positiveText(R.string.close)
                .show();

        if (dialog.getCustomView() != null) {
            ImageView dialogIcon = (ImageView) dialog.getCustomView().findViewById(R.id.dialogicon);
            dialogIcon.setImageResource(resId);
        }
    }

    class IconsHolder extends RecyclerView.ViewHolder {

        final View view;
        final ImageView icon;

        IconsHolder(View v) {
            super(v);
            view = v;
            icon = (ImageView) v.findViewById(R.id.icon_img);
        }
    }

}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Welcome to Code Review! Please declare your cross-post. \$\endgroup\$ Dec 19, 2015 at 0:26

1 Answer 1

2
\$\begingroup\$
private synchronized void filter(CharSequence s, IconsAdapter adapter) {
    if (s == null || s.toString().trim().isEmpty()) {
        if (filteredIconsList != null) {
            filteredIconsList = null;
        }
        if (filteredIconsInts != null) {
            filteredIconsList = null;
        }
        ...

This section of code, you check if the filteredIconsList is not null. If so, set it to null. The only value which is going to equal to null is null, so skip the check and just set it to null.

private synchronized void filter(CharSequence s, IconsAdapter adapter) {
    if (s == null || s.toString().trim().isEmpty()) {
        filteredIconsList = null;
        if (filteredIconsInts != null) {
            filteredIconsList = null;
        }
        ...

Which makes the next if statement just redundant. filteredIconsList is already null. So remove that if statement.

        adapter.notifyDataSetChanged();
    }
}

If both cases end with adapter.notifyDataSetChanged(), then you can just move the line to outside of the if statement.

In the else case of this if statement, you have this:

        if (filteredIconsInts != null) {
            filteredIconsList = null;
        }
        filteredIconsList = new ArrayList<String>();

That's a redundant set, you don't need to set to null if you're going to overwrite it with a new ArrayList just afterwards.

So remove the useless if-statement.

In the for loop, you retrieve the name with iconsNames.get(i), but if the condition in the for loop is true, you iconsNames.get(i) to filteredIconsList. Just add name to the filteredIconsList, as you've already retrieved it.

The documentation for toLowerCase says this:

public String toLowerCase()
Converts all of the characters in this String to lower case using the rules of the default locale. This is equivalent to calling toLowerCase(Locale.getDefault()).

So there's no need to include the Locale. Remove the argument.

Doing all these toLowerCase in a loop is pretty silly, though. Move it to outside of the loop, so you only lowercase once.

Results in this:

private synchronized void filter(CharSequence s, IconsAdapter adapter) {
    if (s == null || s.toString().trim().isEmpty()) {
        filteredIconsList = null;
        adapter.clearIconsList();
        adapter.setIcons(iconsNames, iconsInts);
    } else {
        if (filteredIconsList != null) {
            filteredIconsList.clear();
        }
        filteredIconsList = new ArrayList<String>();
        filteredIconsInts = new ArrayList<Integer>();
        String lowerCaseS = s.toString.toLowerCase();
        for (int i = 0; i < iconsNames.size(); i++) {
            String name = iconsNames.get(i);
            if (name.toLowerCase()
                    .startsWith(lowerCaseS)) {
                filteredIconsList.add(name);
                filteredIconsInts.add(iconsInts.get(i));
            }
        }
        adapter.clearIconsList();
        adapter.setIcons(filteredIconsList, filteredIconsInts);
    }
    adapter.notifyDataSetChanged();
}

If we're allowed to change the order of operations, I'd even move the adapter.clearIconsList() all the way to the top of the function.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.