HardCodeStuds
HardCodeStuds

Reputation: 539

Android ListView lags for every scroll, even with ViewHolder

I want to give a little context: The app I'm currently involved in helping develop is an app a company has already developed for IOS and Android but the Android developer didn't delivered the expected results (mostly performance wise) so the leader gave me the code to see if I could fix it. Given the code I tried to improve it, now, the app is a picture sharing app somewhat like Instagram and it uses and endless scrolling list, the problem with the app is that every time I scroll a little bit, the app lags or freezes for a second and then loads a new row.

The listview only has one row visible at any moment.

What have I tried?:

The adapter wasn't using the ViewHolder pattern so I tried to implement it.

The programmer was defining a lot of click listeners on the getView method so I removed them.

still, every time I scroll ( a new row appears as there's only one visible row at any time) it lags. Are there any other noticeable problems here that could be the cause?

On another note, on this view there's a lot of overdraw so, maybe it's affecting the performance on the ListView? if so, tell me and I'll post the XML part.

Here's the code I adapted:

public class SomeFragment extends Fragment  implements SwipeRefreshLayout.OnRefreshListener {

// declaration and constructors.....

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {

  //initialization......
  //setting variables.......
  //getting views.....
    listview = (ListView)rootView.findViewById(R.id.listview);

    listview.setOnScrollListener(new AbsListView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            int threshold = 1;
            int count = listview.getCount();

            if (scrollState == SCROLL_STATE_IDLE) {
                if (listview.getLastVisiblePosition() >= count - threshold) {
                    int position = listview.getLastVisiblePosition();
                    if (!loading) {
                        loading = true;
                        listview.addFooterView(footerView, null, false);
                        currentVal = position + 1;
                        LoadMoreStuffAsyncTask loadMoreStuffAsyncTask = new LoadMoreStuffAsyncTask();
                        loadMoreStuffAsyncTask.execute();
                    }

                }
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            int topRowVerticalPosition = (listview == null || listview.getChildCount() == 0) ? 0 : listview.getChildAt(0).getTop();
            swipeRefreshLayout.setEnabled(firstVisibleItem == 0 && topRowVerticalPosition >= 0);
        }
    });
    LoadStuffAsyncTask loadStuffAsyncTask=new LoadStuffAsyncTask();
    loadStuffAsyncTask.execute();

}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    rootView = inflater.inflate(R.layout.activity, container, false);
    swipeRefreshLayout = (SwipeRefreshLayout)rootView.findViewById(R.id.swipe);
    swipeRefreshLayout.setOnRefreshListener(SomeFragment.this);

    return  rootView;
}


private void  LoadStuff () {
    list=null;
    ParseQuery<ParseObject> query = null;
    query = ParseQuery.getQuery("Objects");
    query.orderByDescending("createdAt");
    query.include("User");
    query.setLimit(20);

    try {
        list = query.find();

    } catch (ParseException e) {
        e.printStackTrace();
    }
}

private void LoadMoreStuff () {

    List<ParseObject> moreList=null;
    ParseQuery<ParseObject> query = null;
    query = ParseQuery.getQuery("Objects");
    query.orderByDescending("createdAt");
    query.include("User");
    query.setLimit(20);
    query.setSkip(currentVal);

    try {
        moreList = query.find();
        if(moreList!=null|| moreList.size()!=0){
            for(int i =0;i<moreList.size();i++){
                list.add(moreList.get(i));
            }
        }else{
            loading=false;
        }

    } catch (ParseException e) {
        e.printStackTrace();
        loading=false;
    }
}

@Override
public void onRefresh() {
    swipeRefreshLayout.setRefreshing(true);
    listAdapter.notifyDataSetChanged();
    LoadStuffAsyncTask loadStuffAsyncTask=new LoadStuffAsyncTask();
    loadStuffAsyncTask.execute();
}

class LoadMoreStuffAsyncTask extends  AsyncTask<Void, Void, Void>{
    private ProgressDialog pDialog;

    @Override
    protected Void doInBackground(Void... params) {
        LoadMoreStuff();
        return null;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        listAdapter.updateList(list);
        listview.setAdapter(listAdapter);
        loading=false;
        listview.removeFooterView(footerView);
        listview.setSelection(currentVal);

    }
}

class LoadStuffAsyncTask extends  AsyncTask<Void, Void, Void>{
    private ProgressDialog pDialog;
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        pDialog = new ProgressDialog(activity);
        pDialog.setMessage(activity.getString(R.string.loading));
        pDialog.setCancelable(false);
        pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        pDialog.show();
    }


    @Override
    protected Void doInBackground(Void... params) {
        LoadStuff();
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        pDialog.dismiss();
        swipeRefreshLayout.setRefreshing(false);
        listAdapter = new CustomAdapter(activity,momentosGeneral);
        listview.setAdapter(listAdapter);

    }
}
}

This is the adapter:

public class CustomAdapter extends BaseAdapter {

//declaration of variables.....

public CustomAdapter(ActionBarActivity activity, List<ParseObject> list) {

    this.activity = activity;
    this.list = list;
}

@Override
public int getCount() {
    return list.size();
}

@Override
public Object getItem(int position) {
    return position;
}

@Override
public long getItemId(int position) {
    return position;
}

private static class ViewHolder{
  //holderfields......
}

@Override
public View getView(final int position, View v, ViewGroup parent) {
  ViewHolder holder;
  if (v == null) {
        v = View.inflate(activity.getApplicationContext(), R.layout.customview, null);
        holder = new ViewHolder();
        holder.profilepicture = (CircularImageView) v.findViewById(R.id.profilepic);
        holder.username = (TextView) v.findViewById(R.id.username);
        holder.picture = (ParseImageView) v.findViewById(R.id.picture);
        holder.container =(LinearLayout)v.findViewById(R.id.container);
        holder.share=(LinearLayout)v.findViewById(R.id.share);
        holder.comment= (TextView) v.findViewById(R.id.comment);
        holder.likes= (TextView) v.findViewById(R.id.likes);
        holder.publishDate =(TextView)v.findViewById(R.id.publisdate);
        holder.liked = (ImageView)v.findViewById(R.id.liked);
        v.setTag(holder);
    }

    holder = (ViewHolder)v.getTag();
    CustomTypography customTypo = new CustomTypography(activity.getApplicationContext());
    holder.username.setTypeface(customTypo.OpenSansSemibold());

    if(list.get(position).getParseUser("User")!=null){
        holder.username.setText(list.get(position).getParseUser("User").getString("name"));
        profilePic = list.get(position).getParseUser("User").getParseFile("profilePic");

        if (profilePic != null) {
            try {
                Drawable drawable = new BitmapDrawable(BitmapFactory.decodeByteArray(profilePic.getData(), 0, profilePic.getData().length));
                holder.profilepicture.setImageDrawable(drawable);
                holder.profilepicture.setDrawingCacheEnabled(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
    else{

        holder.username.setText("");
    }

    final ParseFile picture = list.get(position).getParseFile("picture");
    if (picture != null) {
        try {
            Drawable drawable = new BitmapDrawable(BitmapFactory.decodeByteArray(picture.getData(), 0, picture.getData().length));
            holder.picture.setImageDrawable(drawable);
            holder.picture.setDrawingCacheEnabled(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    else{

    }

    holder.container.setLayoutParams(customLayoutParams);

    holder.comment.setText(capitalizarPrimeraLetra(list.get(postiion).getString("Comment")));
    holder.comment.setTypeface(customTypo.OpenSansRegular());

    final int likes= list.get(position).getInt("Likes");
    if(likes==0|| likes<0){
        holder.likes.setText("");
    }else{
        holder.likes.setText(String.valueOf(likes));
    }

    holder.likes.setTypeface(customTypo.OpenSansLight());


    holder.publishDate.setText(timeBetween(list.get(position).getCreatedAt()));

    ParseQuery<ParseObject> query = ParseQuery.getQuery("LikedPictures");
    query.whereEqualTo("picture", list.get(position));
    query.whereEqualTo("user", currentUser);


    query.findInBackground(new FindCallback<ParseObject>() {
        public void done(List<ParseObject> likelist, ParseException e) {

            if (e == null) {
                if (likelist.size() != 0) {
                    hasLiked = true;
                    holder.liked.setBackground(activity.getApplicationContext().getResources().getDrawable(R.drawable.like));
                    holder.likes.setTextColor(activity.getApplicationContext().getResources().getColor(R.color.red));
                } else {
                    hasLiked = false;
                }
            } else {
                hasLiked = false;
            }

        }
    });
     return v;
}
private String timeBetween(Date date){
    String result="";
    Date parsedPictureDate = null;
    Date parsedCurrentDate=null;
    Date today = new Date();
    SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
    dateFormatGmt.setTimeZone(TimeZone.getTimeZone("GMT"));
    SimpleDateFormat dateFormatLocal = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
    try {
        parsedPictureDate= dateFormatLocal.parse(dateFormatGmt.format(date));
        parsedCurrentDate=dateFormatLocal.parse(dateFormatGmt.format(hoy));
    } catch (java.text.ParseException e) {
        result="";
    }

    long milis=parsedCurrentDate.getTime()-parsedPictureDate.getTime();
    final long MILISECS_PER_MINUTE=milis/(60 * 1000);
    final long MILISECS_PER_HOUR=milis/(60 * 60 * 1000);
    final long MILLSECS_PER_DAY=milis/(24 * 60 * 60 * 1000);


    if(milis<60000){
        result="Now";
    }
    if(milis>=60000&&milis<3600000){
        result=MILISECS_PER_MINUTE+" min";

    }
    if(milis>=3600000&&milis<86400000){
        result=MILISECS_PER_HOUR+" h";

    }
    if(milis>=86400000){
        result=MILLSECS_PER_DAY+" d";
    }
    return  result;
}

This is the item that always gets populated in the listview

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical" android:layout_width="match_parent"
    android:background="@android:color/white"
    android:paddingBottom="20dp"
    android:layout_height="match_parent">


    <LinearLayout

        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout

            android:id="@+id/somelayout"
            android:layout_weight="0.5"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="@dimen/margin_16dp_sw600"

            android:layout_marginLeft="5dp"
            android:paddingRight="@dimen/padding_16dp_sw600"
            android:gravity="left"
            android:layout_marginBottom="5dp">

            <customImageView

                android:layout_gravity="center"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:id="@+id/profilepicture"
                app:border_width="0dp"

                />

            <TextView
                android:layout_marginLeft="@dimen/margin_16dp_sw600"
                android:layout_gravity="center"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/username"
                android:textSize="14sp"
                android:gravity="left"
                android:singleLine="true"
                android:minLines="1"
                android:maxLines="1"
                android:lines="1"
                />



        </LinearLayout>

        <LinearLayout

            android:id="@+id/layoutTime"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="right"
            android:layout_weight="0.5"
            android:layout_gravity="center"  >
            <TextView
                android:gravity="right"
                android:id="@+id/publishdate"
                android:layout_marginRight="@dimen/margin_16dp_sw600"
                android:textSize="14sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"


                />
        </LinearLayout>
    </LinearLayout>

    <LinearLayout
        android:paddingLeft="5dp"

        android:layout_width="wrap_content"
        android:layout_height="wrap_content">


    <LinearLayout
        android:id="@+id/container"
        android:layout_gravity="center"
        android:background="@drawable/border"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"
        >
        <com.parse.ParseImageView

            android:layout_margin="1dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:id="@+id/picture" />
    </LinearLayout>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >

        <LinearLayout
            android:id="@+id/layoutlikes"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="left"
            android:layout_weight="0.5"

            android:layout_gravity="center"  >
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:id="@+id/likes"
                >
                <TextView
                    android:layout_gravity="center"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/numlikes"
                    android:text="0"
                    android:textSize="14sp"
                    android:layout_margin="@dimen/margin_16dp_sw600"
                    />
                <ImageView
                    android:layout_gravity="center"
                    android:layout_marginRight="@dimen/margin_16dp_sw600"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/likepic"
                    android:background="@drawable/like_off"
                    />
            </LinearLayout>
        </LinearLayout>
        <LinearLayout
            android:id="@+id/sharelayout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="right"
            android:padding="10dp"
            android:layout_weight="0.5"
            android:layout_marginTop="@dimen/margin_16dp_sw600"
            android:layout_gravity="center"  >
            <ImageView

                android:layout_gravity="center"
                android:layout_marginRight="@dimen/margin_16dp_sw600"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/sharepic"
                android:background="@drawable/ellipsis"
                />
        </LinearLayout>
    </LinearLayout>
    <View

        android:layout_width="match_parent"
        android:layout_height="@dimen/division_2dp_sw_600"
        android:layout_margin="@dimen/margin_16dp_sw600"
        android:background="@color/loading" />
    <TextView

        android:layout_gravity="left"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/comment"

        android:textSize= "14sp"

        android:layout_marginLeft="@dimen/margin_16dp_sw600"
        android:layout_marginRight="@dimen/margin_16dp_sw600"
        />


</LinearLayout>

Upvotes: 2

Views: 5328

Answers (2)

Shayan_Aryan
Shayan_Aryan

Reputation: 2042

Here are a couple of things:

1. in your adapter.getView() method, move the

 holder = (ViewHolder)v.getTag();

to if/else scope:

if(view ==null){
    //creation of viewHolder
}else{
    holder = (ViewHolder)v.getTag();
}

2. There is too much code in the adapter.getView(), specially the parse query. You better call it once per item, by caching the result of query for each item in adapter. It is currently being called on every item get visible on scroll.

3. Try to reduce the count of views in your item's xml. Too many views in ListView items cause in memory leak and low performance.

4. Adding the hardwareAccelerated="true" to your activity in manifest might help.

5. Try to comment out the scroll listener, to find out if it is reducing the performance.

Upvotes: 1

fractalwrench
fractalwrench

Reputation: 4076

Your biggest performance hit is probably the code below. You should use some sort of caching rather than decoding the Bitmap each time.

if (profilePic != null) {
            try {
                Drawable drawable = new BitmapDrawable(BitmapFactory.decodeByteArray(profilePic.getData(), 0, profilePic.getData().length));
                holder.profilepicture.setImageDrawable(drawable);
                holder.profilepicture.setDrawingCacheEnabled(true);
            } catch (Exception e) {
                e.printStackTrace();
            }

Also, you are creating a new CustomTypography instance everytime getView() is called. I assume this class extends Typeface, in which case you could just use a single instance that gets initialised in the adapter's constructor.

Upvotes: 2

Related Questions