2
\$\begingroup\$

I'm a beginner in Android and I've been experiencing "Skipped xx frames" everytime I load up my activity which is a list of languages. I tried optimizing the listview following tutorials (applied recycling and viewHolder pattern) but still can't get rid of the issue.

ListLanguagesActivity.java

package com.experiment.woppi.demoapp;

import android.content.Intent;
import android.content.res.Resources;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SearchView;

import java.util.ArrayList;

public class ListLanguagesActivity extends AppCompatActivity {

    ListView lv;
    SearchView sv;

    static String[] languagesCodes = {
            "english",
            "japanese",
            "korean",
            "spanish",
            "french",
            "portuguese",
            "german",
            "italian",
            "russian",
            "arabic",
            "chinese mandarin"
    };

    static int[] languagesImg = {

            R.drawable.flag_country_usa,
            R.drawable.flag_country_jpn,
            R.drawable.flag_country_kor,
            R.drawable.flag_country_esp,
            R.drawable.flag_country_fra,
            R.drawable.flag_country_prt,
            R.drawable.flag_country_deu,
            R.drawable.flag_country_ita,
            R.drawable.flag_country_rus,
            R.drawable.flag_country_sau,
            R.drawable.flag_country_chn
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_languages);

        lv = (ListView) findViewById(R.id.languageListView);
        sv = (SearchView) findViewById(R.id.languageSearchView);

        final LanguageListAdapter adapter = new LanguageListAdapter(this, getLanguages());
        lv.setAdapter(adapter);

        // on click of list item
        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

                LanguageList selected = (LanguageList) adapter.getItem(i);

                Intent intent = new Intent();

                // the selected item
                String selectedItemCode = String.valueOf(view.getTag(R.id.item_id));

                // set the data to be passed
                intent.putExtra("languageCode", selectedItemCode);
                intent.putExtra("languageName", selected.getName());
                intent.putExtra("languageImg", selected.getImg());

                // set the data to be passed
                //intent.setData(Uri.parse(selectedItem));
                setResult(RESULT_OK, intent);

                // end this activity
                finish();
            }
        });

        // on query search, perform custom filtering
        sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String s) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String s) {

                // call the filter method, passing the query "s" arg
                adapter.getFilter().filter(s);
                return false;
            }
        });
    }

    private ArrayList<LanguageList> getLanguages() {
        ArrayList<LanguageList> languages = new ArrayList<LanguageList>();
        LanguageList lang;

        Resources res = getResources();
        String langName;
        String langNativeName;

        for(int i=0; i<languagesCodes.length; i++) {

            langName = getResources().getString(getResources().getIdentifier(languagesCodes[i], "string", getPackageName()));
            langNativeName = getResources().getString(getResources().getIdentifier(languagesCodes[i] + "_native", "string", getPackageName()));


            lang = new LanguageList(languagesCodes[i], langName, langNativeName, languagesImg[i]);
            languages.add(lang);
        }

        return languages;
    }
}

LanguageListAdapter.java

package com.experiment.woppi.demoapp;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;

import java.util.ArrayList;

public class LanguageListAdapter extends BaseAdapter implements Filterable {

    Context mContext;
    ArrayList<LanguageList> languages;

    CustomFilter filter;
    ArrayList<LanguageList> filterList;

    public LanguageListAdapter(Context mContext, ArrayList<LanguageList> languages) {
        this.mContext = mContext;
        this.languages = languages;
        this.filterList = languages;
    }

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

    @Override
    public Object getItem(int i) {
        return languages.get(i);
    }

    @Override
    public long getItemId(int i) {
        return languages.indexOf(getItem(i));
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        View row = view;
        ListViewHolder viewHolder = null;

        // use the inflater only when creating stuff for the first time and initialize view holder
        if(row == null) {
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflater.inflate(R.layout.custom_list_languages, viewGroup, false);
            viewHolder = new ListViewHolder(row, "LANGUAGES");

            row.setTag(R.id.item_view_holder, viewHolder);
        } else {
            viewHolder = (ListViewHolder) row.getTag(R.id.item_view_holder);
        }

        // set the values for the fields
        viewHolder.mainTextView.setText(languages.get(i).getName());
        viewHolder.subTextView.setText(languages.get(i).getNativeName());
        viewHolder.imgView.setImageResource(languages.get(i).getImg());

        // add language code in tag attribute for back-end usage purposes
        row.setTag(R.id.item_id, languages.get(i).getId());

        return row;
    }

    public Filter getFilter() {
        if(filter == null) {
            filter = new CustomFilter();
        }

        return filter;
    }

    class CustomFilter extends Filter {

        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {

            FilterResults results = new FilterResults();

            // check if the user has typed a query in search bar
            if(charSequence != null && charSequence.length() > 0) {

                charSequence = charSequence.toString().toUpperCase();

                // holds the new filter
                ArrayList<LanguageList> filters = new ArrayList<LanguageList>();

                // get specific items
                for(int i=0; i<filterList.size(); i++) {

                    // create new list of matched items
                    if(filterList.get(i).getName().toUpperCase().contains(charSequence) ||
                            filterList.get(i).getNativeName().toUpperCase().contains(charSequence)) {

                        LanguageList lang = new LanguageList(filterList.get(i).getId(), filterList.get(i).getName(), filterList.get(i).getNativeName(), filterList.get(i).getImg());

                        filters.add(lang);
                    }

                    // prepare list items - filtered items
                    results.count = filters.size();
                    results.values = filters;
                }


            } else {
                // if no query, leave everything as-is
                // prepare list items - original item list
                results.count = filterList.size();
                results.values = filterList;
            }

            return results;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            // update the list view items with the filtered results
            languages = (ArrayList<LanguageList>) filterResults.values;

            // refresh the list view with new changes
            notifyDataSetChanged();
        }
    }
}

LanguageList.java

package com.experiment.woppi.demoapp;

public class LanguageList {
    private String id;
    private String name;
    private String nativeName;
    private int img;

    public LanguageList(String id, String name, String nativeName, int img) {
        this.id = id;
        this.name = name;
        this.nativeName = nativeName;
        this.img = img;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNativeName() {
        return nativeName;
    }

    public void setNativeName(String nativeName) {
        this.nativeName = nativeName;
    }

    public int getImg() {
        return img;
    }

    public void setImg(int img) {
        this.img = img;
    }
}

activity_list_languages.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_another_sample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.experiment.woppi.demoapp.ListLanguagesActivity">

    <SearchView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/languageSearchView" />

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="70dp"
        android:id="@+id/languageListView" />
</RelativeLayout>
\$\endgroup\$
1
\$\begingroup\$

About the skip frame: if you run in an emulator, it is OK. Please check in the real device for better visualization.

About the code:

  1. In the LanguageList class, the img variable name should change to imgId.
  2. In the CustomFilter class, the charSequence in the function performFiltering should be final, and avoid changing the argument value. You also reuse the TextUtils to check the empty string.

    final upperCharSequence = charSequence.toString().toUpperCase();
    
  3. Duplicated filterList.get(i) should assign an LanguageList tmpLang = filterList.get(i), the same for Resource res.

\$\endgroup\$
-2
\$\begingroup\$

My guess is that getLanguages() is slow. Try to initialize with an empty list and then notify of data set changes when get full list is ready.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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