1

I'm trying to get the fast scroll pop up containing the large letter to appear when I the fast scroll feature in my list (just like in the image below), but for some reason it won't appear. I've consulted various tutorials but it still doesn't seem to work + I'm not sure if code is missing or some code is in the wrong place. All help would be appreciated.

enter image description here

strings.xml

<resources>
    <string name="app_name">World</string>

    <string name="action_search">Search</string>

    <string name="search_hint">Continent name</string>

    <string name="item0">Azerbaijan</string>
    <string name="item1">Bosnia &amp; Herzegovina</string>
    <string name="item2">Brazil</string>
    <string name="item3">China</string>
    <string name="item4">Denmark</string>
    <string name="item5">France</string>
    <string name="item6">Hungary</string>
    <string name="item7">Italy</string>
    <string name="item8">Japan</string>
    <string name="item9">Lithuania</string>
    <string name="item10">Luxembourg</string>
    <string name="item11">Malta</string>
    <string name="item12">Monaco</string>
    <string name="item13">Norway</string>
    <string name="item14">Portugal</string>
    <string name="item15">Thailand</string>
    <string name="item16">Singapore</string>
    <string name="item17">South Korea</string>
    <string name="item18">Sweden</string>
    <string name="item19">United Kingdom</string>
    <string name="item20">United States</string>


    <string name="item0_description">Item 0 description</string>
    <string name="item1_description">Item 1 description</string>
    <string name="item2_description">Item 2 description</string>
    <string name="item3_description">Item 3 description</string>
    <string name="item4_description">Item 4 description</string>
    <string name="item5_description">Item 5 description</string>
    <string name="item6_description">Item 6 description</string>
    <string name="item7_description">Item 7 description</string>
    <string name="item8_description">Item 8 description</string>
    <string name="item9_description">Item 9 description</string>
    <string name="item10_description">Item 10 description</string>
    <string name="item11_description">Item 11 description</string>
    <string name="item12_description">Item 12 description</string>
    <string name="item13_description">Item 13 description</string>
    <string name="item14_description">Item 14 description</string>
    <string name="item15_description">Item 15 description</string>
    <string name="item16_description">Item 16 description</string>
    <string name="item17_description">Item 17 description</string>
    <string name="item18_description">Item 18 description</string>
    <string name="item19_description">Item 19 description</string>
    <string name="item20_description">Item 20 description</string>

    <string-array name="items">
        //item 0    <item>@string/item0</item>
        //item 1    <item>@string/item1</item>
        //item 2    <item>@string/item2</item>
        //item 3    <item>@string/item3</item>
        //item 4    <item>@string/item4</item>
        //item 5    <item>@string/item5</item>
        //item 6    <item>@string/item6</item>
        //item 7    <item>@string/item7</item>
        //item 8    <item>@string/item8</item>
        //item 9    <item>@string/item9</item>
        //item 10    <item>@string/item10</item>
        //item 11    <item>@string/item11</item>
        //item 12    <item>@string/item12</item>
        //item 13    <item>@string/item13</item>
        //item 14    <item>@string/item14</item>
        //item 15    <item>@string/item15</item>
        //item 16    <item>@string/item16</item>
        //item 17    <item>@string/item17</item>
        //item 18    <item>@string/item18</item>
        //item 19    <item>@string/item19</item>
        //item 20    <item>@string/item20</item>
    </string-array>

    <string-array name="item_descriptions">
        //item 0    <item>@string/item0_description</item>
        //item 1    <item>@string/item1_description</item>
        //item 2    <item>@string/item2_description</item>
        //item 3    <item>@string/item3_description</item>
        //item 4    <item>@string/item4_description</item>
        //item 5    <item>@string/item5_description</item>
        //item 6    <item>@string/item6_description</item>
        //item 7    <item>@string/item7_description</item>
        //item 8    <item>@string/item8_description</item>
        //item 9    <item>@string/item9_description</item>
        //item 10    <item>@string/item10_description</item>
        //item 11    <item>@string/item11_description</item>
        //item 12    <item>@string/item12_description</item>
        //item 13    <item>@string/item13_description</item>
        //item 14    <item>@string/item14_description</item>
        //item 15    <item>@string/item15_description</item>
        //item 16    <item>@string/item16_description</item>
        //item 17    <item>@string/item17_description</item>
        //item 18    <item>@string/item18_description</item>
        //item 19    <item>@string/item19_description</item>
        //item 20    <item>@string/item20_description</item>
    </string-array>

</resources>

FragmentCountries.java

    public class FragmentCountries extends ListFragment implements SearchView.OnQueryTextListener {

    private CountriesListAdapter mAdapter;

    public FragmentCountries() {
        // Required empty constructor
    }

    public static FragmentCountries newInstance() {
        return new FragmentCountries();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_countries, container, false);
        setHasOptionsMenu(true);
        initialize(view);
        return view;
    }

    List<Countries> list = new ArrayList<Countries>();
    private void initialize(View view) {
        String[] items = getActivity().getResources().getStringArray(R.array.country_names);
        String[] itemDescriptions = getActivity().getResources().getStringArray(R.array.country_descriptions);
        for (int n = 0; n < items.length; n++){
            Countries countries = new Countries();
            countries.setID();
            countries.setName(items[n]);
            countries.setDescription(itemDescriptions[n]);
            list.add(countries);
        }

        mAdapter = new CountriesListAdapter(list, getActivity());
        setListAdapter(mAdapter);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Set up search view
        inflater.inflate(R.menu.menu_countries, menu);
        MenuItem item = menu.findItem(R.id.action_search);
        SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
        searchView.setIconifiedByDefault(true);
        searchView.clearAnimation();
        searchView.setOnQueryTextListener(this);
        searchView.setQueryHint(getResources().getString(R.string.search_hint));

        View close = searchView.findViewById(R.id.search_close_btn);
        close.setBackgroundResource(R.drawable.ic_action_content_clear);
    }

    @Override
    public boolean onQueryTextSubmit(String newText) {
        return false;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        mAdapter.getFilter().filter(newText);
        return false;
    }


    @Override
    public int getPositionForSection(int section) {
        return alphaIndexer.get(sections[section]);
    }

    @Override
    public int getSectionForPosition(int position) {
        return 0;
    }

    @Override
    public Object[] getSections() {
        return sections;
    }
}

CountriesListAdapter.java

public class CountriesListAdapter extends BaseAdapter implements Filterable, SectionIndexer {

    private String mSections = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private List<Countries> mData;
    private List<Countries> mFilteredData;
    private LayoutInflater mInflater;
    private ItemFilter mFilter;

    public CountriesListAdapter (List<Countries> data, Context context) {
        mData = data;
        mFilteredData = new ArrayList(mData);
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

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

    @Override
    public String getItem(int position) {
        return mFilteredData.get(position).getName();
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item_dualline, parent, false);
            holder = new ViewHolder();

            holder.title = (TextView) convertView.findViewById(R.id.item_name);
            holder.description = (TextView) convertView.findViewById(R.id.item_description);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.title.setText(mFilteredData.get(position).getName());
        holder.description.setText(mFilteredData.get(position).getDescription());

        return convertView;
    }

    @Override
    public Filter getFilter() {
        if (mFilter == null) {
            mFilter = new ItemFilter();
        }
        return mFilter;
    }

    /**
     * View holder
     */
    static class ViewHolder {
        private TextView title;
        private TextView description;
    }

    private class ItemFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();

            if (TextUtils.isEmpty(constraint)) {
                results.count = mData.size();
                results.values = new ArrayList(mData);
            } else {
                //Create a new list to filter on
                List<Countries> resultList = new ArrayList<Countries>();
                for (Countries str : mData) {
                    if (str.getName().toLowerCase().contains(constraint.toString().toLowerCase())) {
                        resultList.add(str);
                    }
                }
                results.count = resultList.size();
                results.values = resultList;
            }
            return results;
        }


        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results.count == 0) {
                mFilteredData.clear();
                notifyDataSetInvalidated();
            } else {
                mFilteredData = (ArrayList<Countries>)results.values;
                notifyDataSetChanged();
            }
        }
    }


    @Override
    public int getPositionForSection(int section) {
        return alphaIndexer.get(sections[section]);
    }

    @Override
    public int getSectionForPosition(int position) {
        return 0;
    }

    @Override
    public Object[] getSections() {
        String[] sections = new String[mSections.length()];
        for (int i = 0; i < mSections.length(); i++)
            sections[i] = String.valueOf(mSections.charAt(i));
        return sections;
    }

}

Main.java

public class Main {
    public Main(){}

    private String continent;
    private String description;
    private boolean selected;

    public String getContinent(){
        return continent;
    }

    public void setContinent(String continent){
        this.continent = continent;
    }

    public String getDescription(){
        return description;
    }

    public void setDescription(String description){
        this.description = description;
    }

    private int _id;
    public void getID(int _id){
        this._id = _id;
    }

    public int setID(){
        return _id;
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}

drawable/orange_fastscroll_thumb.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <corners
        android:topLeftRadius="44dp"
        android:topRightRadius="44dp"
        android:bottomLeftRadius="44dp" />
    <padding
        android:paddingLeft="22dp"
        android:paddingRight="22dp" />

    <solid android:color="@color/orange" />
</shape>

manifest

<activity
    android:name="OrangeActivity"
    android:label="@string/orange_title"
    android:theme="@style/OrangeTheme" >
</activity>

<style name="OrangeTheme" parent="AppBaseTheme">
    <item name="android:fastScrollThumbDrawable">@drawable/orange_fastscroll_thumb</item>
    <item name="android:fastScrollOverlayPosition">atThumb</item>
    <item name="android:fastScrollTextColor">@color/white</item>
    <item name="android:fastScrollTrackDrawable">@drawable/fastscroll_thumb_pressed</item>
</style>

xml layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/fragmentorange">

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fastScrollEnabled="true"
        android:scrollbarStyle="outsideInset">
    </ListView>

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="@string/no_results"
        android:visibility="invisible"
        android:gravity="center_horizontal"
        android:id="@android:id/empty"
        android:layout_marginTop="100dp"
        android:textColor="@color/white"/>

</LinearLayout>

MainListAdapter

public class MainListAdapter extends BaseAdapter implements Filterable, SectionIndexer {

    private List<Main> mData;
    private List<Main> mFilteredData;
    private LayoutInflater mInflater;
    private ItemFilter mFilter;

    private Object[] mSections;
    private int[] mSectionsIndexedByPosition;
    private int[] mPositionsIndexedBySection;

    public MainListAdapter (List<Main> data, Context context) {
        mData = data;
        mFilteredData = new ArrayList(mData);
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        setupSections();
    }

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

    @Override
    public Main getItem(int position) {
        return mFilteredData.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item, parent, false);
            holder = new ViewHolder();

            holder.title = (TextView) convertView.findViewById(R.id.item);
            holder.description = (TextView) convertView.findViewById(R.id.item_description);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        Main main = getItem(position);
        holder.title.setText(main.getContinent());
        holder.description.setText(main.getDescription());
        if (main.isSelected()) {
            convertView.setBackgroundColor(Color.parseColor("#1C3F96"));
            holder.title.setTextColor(Color.parseColor("#FFFFFF"));
            holder.description.setTextColor(Color.parseColor("#FFFFFF"));
        } else {
            convertView.setBackgroundColor(Color.TRANSPARENT);
            holder.title.setTextColor(Color.parseColor("#FFFFFF"));
            holder.description.setTextColor(Color.parseColor("#B5B5B5"));
        }

        holder.title.setText(mFilteredData.get(position).getContinent());
        holder.description.setText(mFilteredData.get(position).getDescription());

        return convertView;
    }

    @Override
    public Filter getFilter() {
        if (mFilter == null) {
            mFilter = new ItemFilter();
        }
        return mFilter;
    }

    /**
     * View holder
     */
    static class ViewHolder {
        private TextView title;
        private TextView description;
    }

    /**
     * Filter for filtering list items
     */
    /**
     * <p>An array filter constrains the content of the array adapter with
     * a prefix. Each item that does not start with the supplied prefix
     * is removed from the list.</p>
     */
    private class ItemFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();

            if (TextUtils.isEmpty(constraint)) {
                results.count = mData.size();
                results.values = new ArrayList(mData);
            } else {
                //Create a new list to filter on
                List<Main> resultList = new ArrayList<Main>();
                for (Main str : mData) {
                    if (str.getContinent().toLowerCase().contains(constraint.toString().toLowerCase())) {
                        resultList.add(str);
                    }
                }
                results.count = resultList.size();
                results.values = resultList;
            }
            return results;
        }

        /**
         * Runs on ui thread
         * @param constraint the constraint used for the result
         * @param results the results to display
         */
        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results.count == 0) {
                mFilteredData.clear();
                notifyDataSetInvalidated();
            } else {
                mFilteredData = (ArrayList<Main>)results.values;
                notifyDataSetChanged();
            }
            setupSections();
        }
    }

    @Override
    public int getPositionForSection(int section) {
        return mPositionsIndexedBySection[section];
    }

    @Override
    public int getSectionForPosition(int position) {
        return mSectionsIndexedByPosition[position];
    }

    @Override
    public Object[] getSections() {

        return mSections;
    }

    private void setupSections() {

        String initial = "\0";
        List<String> sections = new ArrayList<String>();
        mSectionsIndexedByPosition = new int[mFilteredData.size()];
        mPositionsIndexedBySection = new int[mFilteredData.size()]; // yes it's bigger than necessary

        int section = 0;
        for (int pos = 0; pos < mFilteredData.size(); pos++) {
            Main country = mFilteredData.get(pos);
            if (initial.charAt(0) != country.getContinent().charAt(0)) {
                initial = country.getContinent().substring(0, 1);
                sections.add(initial);
                mPositionsIndexedBySection[section] = pos;
                mSectionsIndexedByPosition[pos] = section;
                section++;
            } else {
                mSectionsIndexedByPosition[pos] = section;
            }
        }
        mSections = sections.toArray();
        mPositionsIndexedBySection = Arrays.copyOf(mPositionsIndexedBySection, section);
    }
}

enter image description here

wbk727
  • 8,017
  • 12
  • 61
  • 125
  • you never asign `alphaIndexer` ... – Selvin Jul 09 '15 at 15:33
  • 1
    just forget about this ... let CountriesListAdapter implements SectionIndexer ... now `getSections()` should return String[] {"*", "A" ,... "Z"} ... `getSectionForPosition` should return index to the letter based on item data (index of mFilteredData.get(position).getName()[0] in array returned by getSections() ... and getPositionForSection ... you should return index in mFilteredData where first country with letter appear – Selvin Jul 09 '15 at 15:59
  • @Selvin I've already got `implements Filterable` in the adapter class so how am I supposed to include `implements SectionIndexer` as well? – wbk727 Jul 09 '15 at 16:10
  • 1
    `implements SectionIndexer` rather `, SectionIndexer` but yes ... and then there will be the hardes part implementation ... but i gave you few hints in the prev comment ... and you be able to do it (looks like a basic programming excercise) – Selvin Jul 09 '15 at 16:13
  • @Selvin I've updated my code – wbk727 Jul 11 '15 at 12:12

1 Answers1

2

Replace the setupSections() in your MainListAdapter with this code:

    private void setupSections() {

        String initial = "\0";
        List<String> sections = new ArrayList<String>();
        mSectionsIndexedByPosition = new int[mFilteredData.size()];
        mPositionsIndexedBySection = new int[mFilteredData.size()];

        int section = 0;
        for (int pos = 0; pos < mFilteredData.size(); pos++) {
            Main country = mFilteredData.get(pos);
            if (initial.charAt(0) != country.getContinent().charAt(0)) {
                initial = country.getContinent().substring(0, 1);
                section = sections.size();
                sections.add(initial);
                mPositionsIndexedBySection[section] = pos;
                mSectionsIndexedByPosition[pos] = section;
            } else {
                mSectionsIndexedByPosition[pos] = section;
            }
        }
        mSections = sections.toArray();
        mPositionsIndexedBySection = Arrays.copyOf(mPositionsIndexedBySection, mSections.length);

    }

Regarding the background to the index letter, you are confusing the preview background with the thumb. The thumb is the element that moves along the track. The file you call orange_fastscroll_thumb.xml is actually a preview background and not a thumb. If you change the name to orange_fastscroll_preview_bg you can set it like this:

<style name="OrangeTheme" parent="AppTheme">
    <item name="android:fastScrollPreviewBackgroundRight">@drawable/orange_fastscroll_preview_bg</item>
    <item name="android:fastScrollOverlayPosition">atThumb</item>
</style>

Apparently, the way Google coded the fast scroll code, you can't directly style the thumb and the track. You can try a couple of suggestions in this question.

Community
  • 1
  • 1
kris larson
  • 30,387
  • 5
  • 62
  • 74
  • Okay, now I see you inflated a view, and you're saying it has a ListView? ListFragment already has it's own ListView, which you set the adapter on. What you should do is extend Fragment instead of ListFragment and set the adapter on the correct ListView. – kris larson Jul 09 '15 at 20:49
  • Please change your answer as a better response to the question – wbk727 Aug 22 '15 at 18:51
  • After using your code `return mFilteredData.get(position).getContinent();` becomes underlined in red and this error is returned: `Required: 'com.apptacularapps.world.data.Main' Found: 'java.lang.String'` Main.java class has been added. Screenshot: http://www.tiikoni.com/tis/view/?id=f790a24 – wbk727 Aug 23 '15 at 22:12
  • That's because `getContinent()` returns a String, but you defined `getItem()` to return `Main`. Either change to `public Main getItem(int position)` to `public String getItem(int position)` --OR-- change `return mFilteredData.get(position).getContinent();` by taking off `getContinent()` so you have `return mFilteredData.get(position);` – kris larson Aug 24 '15 at 03:29
  • Your 2nd suggestion I did as it's the better option but as soon as I scroll the list during deployment I get the `java.lang.ArrayIndexOutOfBoundsException: length=1; index=1 at com.apptacularapps.world.adapters.MainListAdapter.getPositionForSection(MainListAdapter.java:160)` error for this line `return mPositionsIndexedBySection[section];` in `public int getPositionForSection(int section) {}` – wbk727 Aug 24 '15 at 10:32
  • Code has been posted. Btw some 'Countries' instances have been changed to 'Main' so bear this in mind when viewing my code – wbk727 Aug 25 '15 at 16:12
  • Error resolved but the preview thumb letter still doesn't appear – wbk727 Aug 27 '15 at 16:19
  • Apparently the `ListView` has to be much longer than the parent view in order for the fast scroll to show up, so I couldn't see it either until I set `android:fastScrollAlwaysVisible="true"` on the `ListView`. – kris larson Aug 27 '15 at 16:29
  • I'm not talking about the fast scroll itself, I'm referring to the big letter that appears when the users clicks & holds the fast scroll thumb. Please have a look at my latest screenshot within my post. When I click and hold the fast scroll thumb the big letter still doesn't appear. – wbk727 Aug 27 '15 at 18:27
  • Yes, I couldn't see the big letter either -- until I set `android:fastScrollAlwaysVisible="true"` on the `ListView`. – kris larson Aug 27 '15 at 18:53
  • That's exactly what I've set and yet it still doesn't appear – wbk727 Aug 27 '15 at 19:07
  • Okay, one thing I don't have is your data set since you moved to indexing by continent. Can you post that somehow? Later I am going to put my working test project up on GitHub so you can look at it. – kris larson Aug 27 '15 at 19:13
  • So that first section of strings with `country_names` is what comes out of `getContinent()`? Maybe you should go back and post all your current code. `FragmentCountries` is still referring to `Countries` so I am just guessing at half your code. – kris larson Aug 27 '15 at 20:41
  • Everything starts with "I" so there's only one index. You should go back to the country names so you have strings that start with different letters. – kris larson Aug 27 '15 at 20:44
  • Code has been updated + if this solution works, I will get rid of the search view facility – wbk727 Aug 30 '15 at 14:42
  • I have posted a working demo app with fast scroll using your code at https://github.com/klarson2/fast-scroll-demo – kris larson Aug 30 '15 at 17:13
  • Cool. How can I change the background and text colours of the big letter indicator programmatically? – wbk727 Aug 30 '15 at 22:16
  • Lollipop has `ListView.setFastScrollStyle(int styleResId)` but that's not supported in KitKat or earlier. The styles aren't working for you? Let me try to get them working in my GitHub project. – kris larson Aug 31 '15 at 14:11
  • After a couple of tweaks, I've managed to get it appearing in my project. However, I need the fast scroll style to work for prelollipop also & I want to be able to use my own colours (hex or colour resource) for both the letter and the background. – wbk727 Aug 31 '15 at 14:18
  • No. I spent some time on it last night, but couldn't get it working before I had to shut down. I will look at it some more tonight. – kris larson Sep 01 '15 at 20:43
  • Are you using Theme.AppCompat from support library? – kris larson Sep 03 '15 at 01:01
  • Yes I am. Defined it in styles.xml – wbk727 Sep 03 '15 at 01:08
  • I updated my answer and committed some changes. At least I got the background to the index letter to show up. Setting the thumb and track need some tricky code that I don't have time to figure out. I referenced another SO question about setting the thumb. – kris larson Sep 03 '15 at 23:54
  • OK, I had a look at that link and have successfully implemented those things so thanks for that :-) – wbk727 Sep 04 '15 at 17:35