11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/ListViewVariants.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ListViewVariants
2 | ================
3 |
4 | Provides special ways to handle ListViews, including PinnedHeaderListView in Lollipop's Contacts-app style
5 |
6 | Screenshot
7 | ----------
8 | Here's a screenshot of how it shows the contacts of the device, very similar to how Lollipop's Contacts-app shows it , except for the blur, which I've added myself ... :)
9 |
10 | 
11 |
12 | 
13 |
14 | Requirements
15 | ------------
16 | The min API level is 8.
17 |
18 | Also, in case you wish to use any class that causes the headers to be ordered, you must sort the items accordingly.
19 |
20 | In case the headers should all be in uppercase, the sorting should make sure that items that start with each letter (uppercase or not) will be together (for example "Dan" and "duke" should be together on the same chunk that belongs to "D").
21 |
22 | Known Issues
23 | ------------
24 |
25 | 1. Missing some documentations and also some samples. Hope to work on this when I get the time. :)
26 | 2. RTL alignment on the headers (of the sample) isn't supported yet. Maybe it's a simple matter to fix.
27 | However, this doesn't mean that it won't work on RTL locale of the device. Just not that the UI components will get mirrored (meaning it will look exactly like for English locale, for example) ...
28 |
29 | Note
30 | ----
31 | There is another nice alternative library for RecyclerView, here:
32 | https://github.com/TonicArtos/SuperSLiM
33 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/Sample.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | applicationId "lb.listviewvariants"
9 | minSdkVersion 8
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:23.2.1'
25 | compile project(':library')
26 | }
27 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\android/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/MainActivity.java:
--------------------------------------------------------------------------------
1 | package lb.listviewvariants;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.database.Cursor;
7 | import android.graphics.Bitmap;
8 | import android.media.ThumbnailUtils;
9 | import android.net.Uri;
10 | import android.os.Bundle;
11 | import android.provider.ContactsContract;
12 | import android.support.v4.view.MenuItemCompat;
13 | import android.support.v7.app.ActionBarActivity;
14 | import android.support.v7.widget.SearchView;
15 | import android.support.v7.widget.SearchView.OnQueryTextListener;
16 | import android.text.TextUtils;
17 | import android.util.TypedValue;
18 | import android.view.LayoutInflater;
19 | import android.view.Menu;
20 | import android.view.MenuItem;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.widget.TextView;
24 |
25 | import java.util.ArrayList;
26 | import java.util.Collections;
27 | import java.util.Comparator;
28 | import java.util.List;
29 | import java.util.Locale;
30 | import java.util.Random;
31 |
32 | import lb.library.PinnedHeaderListView;
33 | import lb.library.SearchablePinnedHeaderListViewAdapter;
34 | import lb.library.StringArrayAlphabetIndexer;
35 | import lb.listviewvariants.utils.CircularContactView;
36 | import lb.listviewvariants.utils.ContactImageUtil;
37 | import lb.listviewvariants.utils.ContactsQuery;
38 | import lb.listviewvariants.utils.ImageCache;
39 | import lb.listviewvariants.utils.async_task_thread_pool.AsyncTaskEx;
40 | import lb.listviewvariants.utils.async_task_thread_pool.AsyncTaskThreadPool;
41 |
42 |
43 | public class MainActivity extends ActionBarActivity {
44 | private LayoutInflater mInflater;
45 | private PinnedHeaderListView mListView;
46 | private ContactsAdapter mAdapter;
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | mInflater = LayoutInflater.from(MainActivity.this);
52 | setContentView(R.layout.activity_main);
53 | final ArrayList contacts = getContacts();
54 | Collections.sort(contacts, new Comparator() {
55 | @Override
56 | public int compare(Contact lhs, Contact rhs) {
57 | char lhsFirstLetter = TextUtils.isEmpty(lhs.displayName) ? ' ' : lhs.displayName.charAt(0);
58 | char rhsFirstLetter = TextUtils.isEmpty(rhs.displayName) ? ' ' : rhs.displayName.charAt(0);
59 | int firstLetterComparison = Character.toUpperCase(lhsFirstLetter) - Character.toUpperCase(rhsFirstLetter);
60 | if (firstLetterComparison == 0)
61 | return lhs.displayName.compareTo(rhs.displayName);
62 | return firstLetterComparison;
63 | }
64 | });
65 | mListView = (PinnedHeaderListView) findViewById(android.R.id.list);
66 | mAdapter = new ContactsAdapter(contacts);
67 |
68 | int pinnedHeaderBackgroundColor = getResources().getColor(getResIdFromAttribute(this, android.R.attr.colorBackground));
69 | mAdapter.setPinnedHeaderBackgroundColor(pinnedHeaderBackgroundColor);
70 | mAdapter.setPinnedHeaderTextColor(getResources().getColor(R.color.pinned_header_text));
71 | mListView.setPinnedHeaderView(mInflater.inflate(R.layout.pinned_header_listview_side_header, mListView, false));
72 | mListView.setAdapter(mAdapter);
73 | mListView.setOnScrollListener(mAdapter);
74 | mListView.setEnableHeaderTransparencyChanges(false);
75 | // mAdapter.getFilter().filter(mQueryText,new FilterListener() ...
76 | //You can also perform operations on selected item by using :
77 | // mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() ...
78 | }
79 |
80 |
81 | public static int getResIdFromAttribute(final Activity activity, final int attr) {
82 | if (attr == 0)
83 | return 0;
84 | final TypedValue typedValue = new TypedValue();
85 | activity.getTheme().resolveAttribute(attr, typedValue, true);
86 | return typedValue.resourceId;
87 | }
88 |
89 | private ArrayList getContacts() {
90 | if (checkContactsReadPermission()) {
91 | Uri uri = ContactsQuery.CONTENT_URI;
92 | final Cursor cursor = managedQuery(uri, ContactsQuery.PROJECTION, ContactsQuery.SELECTION, null, ContactsQuery.SORT_ORDER);
93 | if (cursor == null)
94 | return null;
95 | ArrayList result = new ArrayList<>();
96 | while (cursor.moveToNext()) {
97 | Contact contact = new Contact();
98 | contact.contactUri = ContactsContract.Contacts.getLookupUri(
99 | cursor.getLong(ContactsQuery.ID),
100 | cursor.getString(ContactsQuery.LOOKUP_KEY));
101 | contact.displayName = cursor.getString(ContactsQuery.DISPLAY_NAME);
102 | contact.photoId = cursor.getString(ContactsQuery.PHOTO_THUMBNAIL_DATA);
103 | result.add(contact);
104 | }
105 |
106 | return result;
107 | }
108 | ArrayList result = new ArrayList<>();
109 | Random r = new Random();
110 | StringBuilder sb = new StringBuilder();
111 | for (int i = 0; i < 1000; ++i) {
112 | Contact contact = new Contact();
113 | sb.delete(0, sb.length());
114 | int strLength = r.nextInt(10) + 1;
115 | for (int j = 0; j < strLength; ++j)
116 | switch (r.nextInt(3)) {
117 | case 0:
118 | sb.append((char) ('a' + r.nextInt('z' - 'a')));
119 | break;
120 | case 1:
121 | sb.append((char) ('A' + r.nextInt('Z' - 'A')));
122 | break;
123 | case 2:
124 | sb.append((char) ('0' + r.nextInt('9' - '0')));
125 | break;
126 | }
127 |
128 | contact.displayName = sb.toString();
129 | result.add(contact);
130 | }
131 | return result;
132 | }
133 |
134 | private boolean checkContactsReadPermission() {
135 | String permission = "android.permission.READ_CONTACTS";
136 | int res = checkCallingOrSelfPermission(permission);
137 | return (res == PackageManager.PERMISSION_GRANTED);
138 | }
139 |
140 | @Override
141 | protected void onDestroy() {
142 | super.onDestroy();
143 | mAdapter.mAsyncTaskThreadPool.cancelAllTasks(true);
144 | }
145 |
146 | private static class Contact {
147 | long contactId;
148 | Uri contactUri;
149 | String displayName;
150 | String photoId;
151 | }
152 |
153 | @Override
154 | public boolean onCreateOptionsMenu(final Menu menu) {
155 | getMenuInflater().inflate(R.menu.activity_main, menu);
156 | final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menuItem_search));
157 | searchView.setOnQueryTextListener(new OnQueryTextListener() {
158 | @Override
159 | public boolean onQueryTextSubmit(final String query) {
160 | return false;
161 | }
162 |
163 | @Override
164 | public boolean onQueryTextChange(final String newText) {
165 | performSearch(newText);
166 | return true;
167 | }
168 | });
169 |
170 | return super.onCreateOptionsMenu(menu);
171 | }
172 |
173 | public void performSearch(final String queryText) {
174 | mAdapter.getFilter().filter(queryText);
175 | mAdapter.setHeaderViewVisible(TextUtils.isEmpty(queryText));
176 | }
177 |
178 | @SuppressWarnings("deprecation")
179 | @Override
180 | public boolean onOptionsItemSelected(final MenuItem item) {
181 | String url = null;
182 | switch (item.getItemId()) {
183 | case R.id.menuItem_all_my_apps:
184 | url = "https://play.google.com/store/apps/developer?id=AndroidDeveloperLB";
185 | break;
186 | case R.id.menuItem_all_my_repositories:
187 | url = "https://github.com/AndroidDeveloperLB";
188 | break;
189 | case R.id.menuItem_current_repository_website:
190 | url = "https://github.com/AndroidDeveloperLB/ListViewVariants";
191 | break;
192 | }
193 | if (url == null)
194 | return true;
195 | final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
196 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
197 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
198 | startActivity(intent);
199 | return true;
200 | }
201 |
202 | // ////////////////////////////////////////////////////////////
203 | // ContactsAdapter //
204 | // //////////////////
205 | private class ContactsAdapter extends SearchablePinnedHeaderListViewAdapter {
206 | private ArrayList mContacts;
207 | private final int CONTACT_PHOTO_IMAGE_SIZE;
208 | private final int[] PHOTO_TEXT_BACKGROUND_COLORS;
209 | private final AsyncTaskThreadPool mAsyncTaskThreadPool = new AsyncTaskThreadPool(1, 2, 10);
210 |
211 | @Override
212 | public CharSequence getSectionTitle(int sectionIndex) {
213 | return ((StringArrayAlphabetIndexer.AlphaBetSection) getSections()[sectionIndex]).getName();
214 | }
215 |
216 | public ContactsAdapter(final ArrayList contacts) {
217 | setData(contacts);
218 | PHOTO_TEXT_BACKGROUND_COLORS = getResources().getIntArray(R.array.contacts_text_background_colors);
219 | CONTACT_PHOTO_IMAGE_SIZE = getResources().getDimensionPixelSize(
220 | R.dimen.list_item__contact_imageview_size);
221 | }
222 |
223 | public void setData(final ArrayList contacts) {
224 | this.mContacts = contacts;
225 | final String[] generatedContactNames = generateContactNames(contacts);
226 | setSectionIndexer(new StringArrayAlphabetIndexer(generatedContactNames, true));
227 | }
228 |
229 | private String[] generateContactNames(final List contacts) {
230 | final ArrayList contactNames = new ArrayList();
231 | if (contacts != null)
232 | for (final Contact contactEntity : contacts)
233 | contactNames.add(contactEntity.displayName);
234 | return contactNames.toArray(new String[contactNames.size()]);
235 | }
236 |
237 | @Override
238 | public View getView(final int position, final View convertView, final ViewGroup parent) {
239 | final ViewHolder holder;
240 | final View rootView;
241 | if (convertView == null) {
242 | holder = new ViewHolder();
243 | rootView = mInflater.inflate(R.layout.listview_item, parent, false);
244 | holder.friendProfileCircularContactView = (CircularContactView) rootView
245 | .findViewById(R.id.listview_item__friendPhotoImageView);
246 | holder.friendProfileCircularContactView.getTextView().setTextColor(0xFFffffff);
247 | holder.friendName = (TextView) rootView
248 | .findViewById(R.id.listview_item__friendNameTextView);
249 | holder.headerView = (TextView) rootView.findViewById(R.id.header_text);
250 | rootView.setTag(holder);
251 | } else {
252 | rootView = convertView;
253 | holder = (ViewHolder) rootView.getTag();
254 | }
255 | final Contact contact = getItem(position);
256 | final String displayName = contact.displayName;
257 | holder.friendName.setText(displayName);
258 | boolean hasPhoto = !TextUtils.isEmpty(contact.photoId);
259 | if (holder.updateTask != null && !holder.updateTask.isCancelled())
260 | holder.updateTask.cancel(true);
261 | final Bitmap cachedBitmap = hasPhoto ? ImageCache.INSTANCE.getBitmapFromMemCache(contact.photoId) : null;
262 | if (cachedBitmap != null)
263 | holder.friendProfileCircularContactView.setImageBitmap(cachedBitmap);
264 | else {
265 | final int backgroundColorToUse = PHOTO_TEXT_BACKGROUND_COLORS[position
266 | % PHOTO_TEXT_BACKGROUND_COLORS.length];
267 | if (TextUtils.isEmpty(displayName))
268 | holder.friendProfileCircularContactView.setImageResource(R.drawable.ic_person_white_120dp,
269 | backgroundColorToUse);
270 | else {
271 | final String characterToShow = TextUtils.isEmpty(displayName) ? "" : displayName.substring(0, 1).toUpperCase(Locale.getDefault());
272 | holder.friendProfileCircularContactView.setTextAndBackgroundColor(characterToShow, backgroundColorToUse);
273 | }
274 | if (hasPhoto) {
275 | holder.updateTask = new AsyncTaskEx() {
276 |
277 | @Override
278 | public Bitmap doInBackground(final Void... params) {
279 | if (isCancelled())
280 | return null;
281 | final Bitmap b = ContactImageUtil.loadContactPhotoThumbnail(MainActivity.this, contact.photoId, CONTACT_PHOTO_IMAGE_SIZE);
282 | if (b != null)
283 | return ThumbnailUtils.extractThumbnail(b, CONTACT_PHOTO_IMAGE_SIZE,
284 | CONTACT_PHOTO_IMAGE_SIZE);
285 | return null;
286 | }
287 |
288 | @Override
289 | public void onPostExecute(final Bitmap result) {
290 | super.onPostExecute(result);
291 | if (result == null)
292 | return;
293 | ImageCache.INSTANCE.addBitmapToCache(contact.photoId, result);
294 | holder.friendProfileCircularContactView.setImageBitmap(result);
295 | }
296 | };
297 | mAsyncTaskThreadPool.executeAsyncTask(holder.updateTask);
298 | }
299 | }
300 | bindSectionHeader(holder.headerView, null, position);
301 | return rootView;
302 | }
303 |
304 | @Override
305 | public boolean doFilter(final Contact item, final CharSequence constraint) {
306 | if (TextUtils.isEmpty(constraint))
307 | return true;
308 | final String displayName = item.displayName;
309 | return !TextUtils.isEmpty(displayName) && displayName.toLowerCase(Locale.getDefault())
310 | .contains(constraint.toString().toLowerCase(Locale.getDefault()));
311 | }
312 |
313 | @Override
314 | public ArrayList getOriginalList() {
315 | return mContacts;
316 | }
317 |
318 |
319 | }
320 |
321 | // /////////////////////////////////////////////////////////////////////////////////////
322 | // ViewHolder //
323 | // /////////////
324 | private static class ViewHolder {
325 | public CircularContactView friendProfileCircularContactView;
326 | TextView friendName, headerView;
327 | public AsyncTaskEx updateTask;
328 | }
329 | }
330 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/CircularContactView.java:
--------------------------------------------------------------------------------
1 | package lb.listviewvariants.utils;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Rect;
7 | import android.graphics.drawable.ShapeDrawable;
8 | import android.graphics.drawable.shapes.OvalShape;
9 | import android.media.ThumbnailUtils;
10 | import android.os.Build;
11 | import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
12 | import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
13 | import android.util.AttributeSet;
14 | import android.util.TypedValue;
15 | import android.view.Gravity;
16 | import android.widget.FrameLayout;
17 | import android.widget.ImageView;
18 | import android.widget.ImageView.ScaleType;
19 | import android.widget.TextView;
20 | import android.widget.ViewSwitcher;
21 |
22 | import lb.listviewvariants.R;
23 |
24 | public class CircularContactView extends ViewSwitcher
25 | {
26 | // private static final int DEFAULT_CONTENT_SIZE_IN_DP=20;
27 | private ImageView mImageView;
28 | private TextView mTextView;
29 | private Bitmap mBitmap;
30 | private CharSequence mText;
31 | private int mBackgroundColor=0, mImageResId=0;
32 | private int mContentSize;
33 |
34 | public CircularContactView(final Context context)
35 | {
36 | this(context,null);
37 | }
38 |
39 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
40 | public CircularContactView(final Context context,final AttributeSet attrs)
41 | {
42 | super(context,attrs);
43 | addView(mImageView=new ImageView(context),new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
44 | LayoutParams.MATCH_PARENT,Gravity.CENTER));
45 | addView(mTextView=new TextView(context),new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
46 | LayoutParams.MATCH_PARENT,Gravity.CENTER));
47 | mTextView.setGravity(Gravity.CENTER);
48 | if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.ICE_CREAM_SANDWICH)
49 | mTextView.setAllCaps(true);
50 | mContentSize=getResources().getDimensionPixelSize(R.dimen.list_item__contact_imageview_size);
51 | if(isInEditMode())
52 | setTextAndBackgroundColor("",0xFFff0000);
53 | }
54 |
55 | public void setContentSize(final int contentSize)
56 | {
57 | this.mContentSize=contentSize;
58 | }
59 |
60 | @SuppressWarnings("deprecation")
61 | private void drawContent(final int viewWidth,final int viewHeight)
62 | {
63 | ShapeDrawable roundedBackgroundDrawable=null;
64 | if(mBackgroundColor!=0)
65 | {
66 | roundedBackgroundDrawable=new ShapeDrawable(new OvalShape());
67 | roundedBackgroundDrawable.getPaint().setColor(mBackgroundColor);
68 | roundedBackgroundDrawable.setIntrinsicHeight(viewHeight);
69 | roundedBackgroundDrawable.setIntrinsicWidth(viewWidth);
70 | roundedBackgroundDrawable.setBounds(new Rect(0,0,viewWidth,viewHeight));
71 | }
72 | if(mImageResId!=0)
73 | {
74 | mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
75 | mImageView.setImageResource(mImageResId);
76 | mImageView.setScaleType(ScaleType.CENTER_INSIDE);
77 | }
78 | else if(mText!=null)
79 | {
80 | mTextView.setText(mText);
81 | mTextView.setBackgroundDrawable(roundedBackgroundDrawable);
82 | mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, viewHeight / 2);
83 | }
84 | else if(mBitmap!=null)
85 | {
86 | mImageView.setScaleType(ScaleType.FIT_CENTER);
87 | mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
88 | if(mBitmap.getWidth()!=mBitmap.getHeight())
89 | mBitmap=ThumbnailUtils.extractThumbnail(mBitmap,viewWidth,viewHeight);
90 | final RoundedBitmapDrawable roundedBitmapDrawable=RoundedBitmapDrawableFactory.create(getResources(),
91 | mBitmap);
92 | roundedBitmapDrawable.setCornerRadius((mBitmap.getWidth()+mBitmap.getHeight())/4);
93 | mImageView.setImageDrawable(roundedBitmapDrawable);
94 | }
95 | resetValuesState(false);
96 | }
97 |
98 | public void setTextAndBackgroundColor(final CharSequence text,final int backgroundColor)
99 | {
100 | resetValuesState(true);
101 | while(getCurrentView()!=mTextView)
102 | showNext();
103 | this.mBackgroundColor=backgroundColor;
104 | mText=text;
105 | drawContent(mContentSize,mContentSize);
106 | }
107 |
108 | public void setImageResource(final int imageResId,final int backgroundColor)
109 | {
110 | resetValuesState(true);
111 | while(getCurrentView()!=mImageView)
112 | showNext();
113 | mImageResId=imageResId;
114 | this.mBackgroundColor=backgroundColor;
115 | drawContent(mContentSize,mContentSize);
116 | }
117 |
118 | public void setImageBitmap(final Bitmap bitmap)
119 | {
120 | setImageBitmapAndBackgroundColor(bitmap,0);
121 | }
122 |
123 | public void setImageBitmapAndBackgroundColor(final Bitmap bitmap,final int backgroundColor)
124 | {
125 | resetValuesState(true);
126 | while(getCurrentView()!=mImageView)
127 | showNext();
128 | this.mBackgroundColor=backgroundColor;
129 | mBitmap=bitmap;
130 | drawContent(mContentSize,mContentSize);
131 | }
132 |
133 | private void resetValuesState(final boolean alsoResetViews)
134 | {
135 | mBackgroundColor=mImageResId=0;
136 | mBitmap=null;
137 | mText=null;
138 | if(alsoResetViews)
139 | {
140 | mTextView.setText(null);
141 | mTextView.setBackgroundDrawable(null);
142 | mImageView.setImageBitmap(null);
143 | mImageView.setBackgroundDrawable(null);
144 | }
145 | }
146 |
147 | public ImageView getImageView()
148 | {
149 | return mImageView;
150 | }
151 |
152 | public TextView getTextView()
153 | {
154 | return mTextView;
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/ContactImageUtil.java:
--------------------------------------------------------------------------------
1 | package lb.listviewvariants.utils;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.ContentResolver;
5 | import android.content.Context;
6 | import android.content.res.AssetFileDescriptor;
7 | import android.graphics.Bitmap;
8 | import android.graphics.BitmapFactory;
9 | import android.net.Uri;
10 | import android.os.Build;
11 | import android.provider.ContactsContract;
12 |
13 | import java.io.FileDescriptor;
14 | import java.io.FileNotFoundException;
15 | import java.io.IOException;
16 |
17 | import lb.listviewvariants.BuildConfig;
18 |
19 | public class ContactImageUtil
20 | {
21 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
22 | public static Bitmap loadContactPhoto(Context context,Uri contactUri,int imageSize)
23 | {
24 | // Instantiates a ContentResolver for retrieving the Uri of the image
25 | final ContentResolver contentResolver=context.getContentResolver();
26 |
27 | // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the
28 | // ContentResolver can return an AssetFileDescriptor for the file.
29 | AssetFileDescriptor afd=null;
30 | if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.ICE_CREAM_SANDWICH)
31 | {
32 | // On platforms running Android 4.0 (API version 14) and later, a high resolution image
33 | // is available from Photo.DISPLAY_PHOTO.
34 | try
35 | {
36 | // Constructs the content Uri for the image
37 | Uri displayImageUri=Uri.withAppendedPath(contactUri,ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
38 |
39 | // Retrieves an AssetFileDescriptor from the Contacts Provider, using the
40 | // constructed Uri
41 | afd=contentResolver.openAssetFileDescriptor(displayImageUri,"r");
42 | // If the file exists
43 | if(afd!=null)
44 | {
45 | // Reads and decodes the file to a Bitmap and scales it to the desired size
46 | return decodeSampledBitmapFromDescriptor(
47 | afd.getFileDescriptor(),imageSize,imageSize);
48 | }
49 | }
50 | catch(FileNotFoundException e)
51 | {
52 | // Catches file not found exceptions
53 | if(BuildConfig.DEBUG)
54 | {
55 | // Log debug message, this is not an error message as this exception is thrown
56 | // when a contact is legitimately missing a contact photo (which will be quite
57 | // frequently in a long contacts list).
58 | // Log.d(TAG,"Contact photo not found for contact "+contactUri.toString()
59 | // +": "+e.toString());
60 | }
61 | }
62 | finally
63 | {
64 | // Once the decode is complete, this closes the file. You must do this each time
65 | // you access an AssetFileDescriptor; otherwise, every image load you do will open
66 | // a new descriptor.
67 | if(afd!=null)
68 | {
69 | try
70 | {
71 | afd.close();
72 | }
73 | catch(IOException e)
74 | {
75 | // Closing a file descriptor might cause an IOException if the file is
76 | // already closed. Nothing extra is needed to handle this.
77 | }
78 | }
79 | }
80 | }
81 |
82 | // If the platform version is less than Android 4.0 (API Level 14), use the only available
83 | // image URI, which points to a normal-sized image.
84 | try
85 | {
86 | // Constructs the image Uri from the contact Uri and the directory twig from the
87 | // Contacts.Photo table
88 | Uri imageUri=Uri.withAppendedPath(contactUri,ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
89 |
90 | // Retrieves an AssetFileDescriptor from the Contacts Provider, using the constructed
91 | // Uri
92 | afd=contentResolver.openAssetFileDescriptor(imageUri,"r");
93 |
94 | // If the file exists
95 | if(afd!=null)
96 | {
97 | // Reads the image from the file, decodes it, and scales it to the available screen
98 | // area
99 | return decodeSampledBitmapFromDescriptor(
100 | afd.getFileDescriptor(),imageSize,imageSize);
101 | }
102 | }
103 | catch(FileNotFoundException e)
104 | {
105 | // Catches file not found exceptions
106 | if(BuildConfig.DEBUG)
107 | {
108 | // Log.d(TAG,"Contact photo not found for contact "+contactUri.toString()
109 | // +": "+e.toString());
110 | }
111 | }
112 | finally
113 | {
114 | // Once the decode is complete, this closes the file. You must do this each time you
115 | // access an AssetFileDescriptor; otherwise, every image load you do will open a new
116 | // descriptor.
117 | if(afd!=null)
118 | {
119 | try
120 | {
121 | afd.close();
122 | }
123 | catch(IOException e)
124 | {
125 | // Closing a file descriptor might cause an IOException if the file is
126 | // already closed. Ignore this.
127 | }
128 | }
129 | }
130 |
131 | // If none of the case selectors match, returns null.
132 | return null;
133 | }
134 |
135 | /**
136 | * Decodes and scales a contact's image from a file pointed to by a Uri in the contact's data,
137 | * and returns the result as a Bitmap. The column that contains the Uri varies according to the
138 | * platform version.
139 | *
140 | * @param photoData For platforms prior to Android 3.0, provide the Contact._ID column value.
141 | * For Android 3.0 and later, provide the Contact.PHOTO_THUMBNAIL_URI value.
142 | * @param imageSize The desired target width and height of the output image in pixels.
143 | * @return A Bitmap containing the contact's image, resized to fit the provided image size. If
144 | * no thumbnail exists, returns null.
145 | */
146 | public static Bitmap loadContactPhotoThumbnail(Context context,String photoData,int imageSize)
147 | {
148 | // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the
149 | // ContentResolver can return an AssetFileDescriptor for the file.
150 | AssetFileDescriptor afd=null;
151 | // This "try" block catches an Exception if the file descriptor returned from the Contacts
152 | // Provider doesn't point to an existing file.
153 | try
154 | {
155 | Uri thumbUri;
156 | // If Android 3.0 or later, converts the Uri passed as a string to a Uri object.
157 | if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
158 | thumbUri=Uri.parse(photoData);
159 | else
160 | {
161 | // For versions prior to Android 3.0, appends the string argument to the content
162 | // Uri for the Contacts table.
163 | final Uri contactUri=Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI,photoData);
164 | // Appends the content Uri for the Contacts.Photo table to the previously
165 | // constructed contact Uri to yield a content URI for the thumbnail image
166 | thumbUri=Uri.withAppendedPath(contactUri,ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
167 | }
168 | // Retrieves a file descriptor from the Contacts Provider. To learn more about this
169 | // feature, read the reference documentation for
170 | // ContentResolver#openAssetFileDescriptor.
171 | afd=context.getContentResolver().openAssetFileDescriptor(thumbUri,"r");
172 |
173 | // Gets a FileDescriptor from the AssetFileDescriptor. A BitmapFactory object can
174 | // decode the contents of a file pointed to by a FileDescriptor into a Bitmap.
175 | FileDescriptor fileDescriptor=afd.getFileDescriptor();
176 |
177 | if(fileDescriptor!=null)
178 | {
179 | // Decodes a Bitmap from the image pointed to by the FileDescriptor, and scales it
180 | // to the specified width and height
181 | return decodeSampledBitmapFromDescriptor(
182 | fileDescriptor,imageSize,imageSize);
183 | }
184 | }
185 | catch(FileNotFoundException e)
186 | {
187 | // If the file pointed to by the thumbnail URI doesn't exist, or the file can't be
188 | // opened in "read" mode, ContentResolver.openAssetFileDescriptor throws a
189 | // FileNotFoundException.
190 | // if(BuildConfig.DEBUG)
191 | // {
192 | // Log.d(TAG,"Contact photo thumbnail not found for contact "+photoData
193 | // +": "+e.toString());
194 | // }
195 | }
196 | finally
197 | {
198 | // If an AssetFileDescriptor was returned, try to close it
199 | if(afd!=null)
200 | {
201 | try
202 | {
203 | afd.close();
204 | }
205 | catch(IOException e)
206 | {
207 | // Closing a file descriptor might cause an IOException if the file is
208 | // already closed. Nothing extra is needed to handle this.
209 | }
210 | }
211 | }
212 |
213 | // If the decoding failed, returns null
214 | return null;
215 | }
216 |
217 |
218 | public static Bitmap decodeSampledBitmapFromDescriptor(
219 | FileDescriptor fileDescriptor,int reqWidth,int reqHeight)
220 | {
221 |
222 | // First decode with inJustDecodeBounds=true to check dimensions
223 | final BitmapFactory.Options options=new BitmapFactory.Options();
224 | options.inJustDecodeBounds=true;
225 | BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options);
226 |
227 | // Calculate inSampleSize
228 | options.inSampleSize=calculateInSampleSize(options,reqWidth,reqHeight);
229 |
230 | // Decode bitmap with inSampleSize set
231 | options.inJustDecodeBounds=false;
232 | return BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options);
233 | }
234 |
235 | /**
236 | * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
237 | * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
238 | * the closest inSampleSize that will result in the final decoded bitmap having a width and
239 | * height equal to or larger than the requested width and height. This implementation does not
240 | * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
241 | * results in a larger bitmap which isn't as useful for caching purposes.
242 | *
243 | * @param options An options object with out* params already populated (run through a decode*
244 | * method with inJustDecodeBounds==true
245 | * @param reqWidth The requested width of the resulting bitmap
246 | * @param reqHeight The requested height of the resulting bitmap
247 | * @return The value to be used for inSampleSize
248 | */
249 | public static int calculateInSampleSize(BitmapFactory.Options options,
250 | int reqWidth,int reqHeight)
251 | {
252 | // Raw height and width of image
253 | final int height=options.outHeight;
254 | final int width=options.outWidth;
255 | int inSampleSize=1;
256 |
257 | if(height>reqHeight||width>reqWidth)
258 | {
259 |
260 | // Calculate ratios of height and width to requested height and width
261 | final int heightRatio=Math.round((float)height/(float)reqHeight);
262 | final int widthRatio=Math.round((float)width/(float)reqWidth);
263 |
264 | // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
265 | // with both dimensions larger than or equal to the requested height and width.
266 | inSampleSize=heightRatiototalReqPixelsCap)
280 | {
281 | inSampleSize++;
282 | }
283 | }
284 | return inSampleSize;
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/ContactsQuery.java:
--------------------------------------------------------------------------------
1 | package lb.listviewvariants.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.net.Uri;
5 | import android.os.Build;
6 | import android.provider.ContactsContract.Contacts;
7 |
8 | public interface ContactsQuery
9 | {
10 |
11 | // An identifier for the loader
12 | final static int QUERY_ID=1;
13 |
14 | // A content URI for the Contacts table
15 | final static Uri CONTENT_URI=Contacts.CONTENT_URI;
16 |
17 | // The search/filter query Uri
18 | final static Uri FILTER_URI=Contacts.CONTENT_FILTER_URI;
19 |
20 | // The selection clause for the CursorLoader query. The search criteria defined here
21 | // restrict results to contacts that have a display name and are linked to visible groups.
22 | // Notice that the search on the string provided by the user is implemented by appending
23 | // the search string to CONTENT_FILTER_URI.
24 | @SuppressLint("InlinedApi")
25 | final static String SELECTION=
26 | (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB?Contacts.DISPLAY_NAME_PRIMARY:Contacts.DISPLAY_NAME)+
27 | "<>''"+" AND "+Contacts.IN_VISIBLE_GROUP+"=1";
28 |
29 | // The desired sort order for the returned Cursor. In Android 3.0 and later, the primary
30 | // sort key allows for localization. In earlier versions. use the display name as the sort
31 | // key.
32 | @SuppressLint("InlinedApi")
33 | final static String SORT_ORDER=
34 | Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB?Contacts.SORT_KEY_PRIMARY:Contacts.DISPLAY_NAME;
35 |
36 | // The projection for the CursorLoader query. This is a list of columns that the Contacts
37 | // Provider should return in the Cursor.
38 | @SuppressLint("InlinedApi")
39 | final static String[] PROJECTION={
40 |
41 | // The contact's row id
42 | Contacts._ID,
43 |
44 | // A pointer to the contact that is guaranteed to be more permanent than _ID. Given
45 | // a contact's current _ID value and LOOKUP_KEY, the Contacts Provider can generate
46 | // a "permanent" contact URI.
47 | Contacts.LOOKUP_KEY,
48 |
49 | // In platform version 3.0 and later, the Contacts table contains
50 | // DISPLAY_NAME_PRIMARY, which either contains the contact's displayable name or
51 | // some other useful identifier such as an email address. This column isn't
52 | // available in earlier versions of Android, so you must use Contacts.DISPLAY_NAME
53 | // instead.
54 | Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB?Contacts.DISPLAY_NAME_PRIMARY:Contacts.DISPLAY_NAME,
55 |
56 | // In Android 3.0 and later, the thumbnail image is pointed to by
57 | // PHOTO_THUMBNAIL_URI. In earlier versions, there is no direct pointer; instead,
58 | // you generate the pointer from the contact's ID value and constants defined in
59 | // android.provider.ContactsContract.Contacts.
60 | Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB?Contacts.PHOTO_THUMBNAIL_URI:Contacts._ID,
61 |
62 | // The sort order column for the returned Cursor, used by the AlphabetIndexer
63 | SORT_ORDER,
64 | };
65 |
66 | // The query column numbers which map to each value in the projection
67 | final static int ID=0;
68 | final static int LOOKUP_KEY=1;
69 | final static int DISPLAY_NAME=2;
70 | final static int PHOTO_THUMBNAIL_DATA=3;
71 | final static int SORT_KEY=4;
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/ImageCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package lb.listviewvariants.utils;
18 |
19 | import android.graphics.Bitmap;
20 | import android.os.Build;
21 | import android.support.v4.util.LruCache;
22 | import android.util.Log;
23 |
24 | import lb.listviewvariants.BuildConfig;
25 |
26 | /**
27 | * This class holds our bitmap caches (memory and disk).
28 | */
29 | public class ImageCache
30 | {
31 | private static final float CACHE_PERCENTAGE=0.1f;
32 | private static final String TAG="ImageCache";
33 | private LruCache mMemoryCache;
34 | public static final ImageCache INSTANCE=new ImageCache(CACHE_PERCENTAGE);
35 |
36 | private ImageCache(float memCacheSizePercent)
37 | {
38 | init(memCacheSizePercent);
39 | }
40 |
41 | private void init(float memCacheSizePercent)
42 | {
43 | int memCacheSize=calculateMemCacheSize(memCacheSizePercent);
44 | // Set up memory cache
45 | if(BuildConfig.DEBUG)
46 | Log.d(TAG,"Memory cache created (size = "+memCacheSize+")");
47 | mMemoryCache=new LruCache(memCacheSize)
48 | {
49 | @Override
50 | protected int sizeOf(String key,Bitmap bitmap)
51 | {
52 | final int bitmapSize=getBitmapSize(bitmap)/1024;
53 | return bitmapSize==0?1:bitmapSize;
54 | }
55 | };
56 | }
57 |
58 | public void addBitmapToCache(String data,Bitmap bitmap)
59 | {
60 | if(data==null||bitmap==null)
61 | return;
62 | // Add to memory cache
63 | if(mMemoryCache!=null&&mMemoryCache.get(data)==null)
64 | mMemoryCache.put(data,bitmap);
65 | }
66 |
67 | public Bitmap getBitmapFromMemCache(String data)
68 | {
69 | if(mMemoryCache!=null)
70 | {
71 | final Bitmap memBitmap=mMemoryCache.get(data);
72 | if(memBitmap!=null)
73 | {
74 | if(BuildConfig.DEBUG)
75 | Log.d(TAG,"Memory cache hit");
76 | return memBitmap;
77 | }
78 | }
79 | return null;
80 | }
81 |
82 | public static int getBitmapSize(Bitmap bitmap)
83 | {
84 | if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB_MR1)
85 | return bitmap.getByteCount();
86 | // Pre HC-MR1
87 | return bitmap.getRowBytes()*bitmap.getHeight();
88 | }
89 |
90 | public static int calculateMemCacheSize(float percent)
91 | {
92 | if(percent<0.05f||percent>0.8f)
93 | {
94 | throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
95 | +"between 0.05 and 0.8 (inclusive)");
96 | }
97 | return Math.round(percent*Runtime.getRuntime().maxMemory()/1024);
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/async_task_thread_pool/AsyncTaskEx.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package lb.listviewvariants.utils.async_task_thread_pool;
17 | import java.io.File;
18 | import java.io.FileFilter;
19 | import java.util.HashSet;
20 | import java.util.Set;
21 | import java.util.concurrent.BlockingQueue;
22 | import java.util.concurrent.Callable;
23 | import java.util.concurrent.CancellationException;
24 | import java.util.concurrent.ExecutionException;
25 | import java.util.concurrent.Executor;
26 | import java.util.concurrent.FutureTask;
27 | import java.util.concurrent.LinkedBlockingQueue;
28 | import java.util.concurrent.ThreadFactory;
29 | import java.util.concurrent.ThreadPoolExecutor;
30 | import java.util.concurrent.TimeUnit;
31 | import java.util.concurrent.TimeoutException;
32 | import java.util.concurrent.atomic.AtomicBoolean;
33 | import java.util.concurrent.atomic.AtomicInteger;
34 | import java.util.regex.Pattern;
35 |
36 | import android.os.Handler;
37 | import android.os.Message;
38 |
39 | /**
40 | *
41 | * CustomAsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and
42 | * publish results on the UI thread without having to manipulate threads and/or handlers.
43 | *
44 | *
45 | * CustomAsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} and does not constitute a
46 | * generic threading framework. CustomAsyncTasks should ideally be used for short operations (a few seconds at the
47 | * most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various
48 | * APIs provided by the java.util.concurrent pacakge such as {@link Executor}, {@link ThreadPoolExecutor}
49 | * and {@link FutureTask}.
50 | *
51 | *
52 | * An asynchronous task is defined by a computation that runs on a background thread and whose result is published on
53 | * the UI thread. An asynchronous task is defined by 3 generic types, called Params, Progress
54 | * and Result, and 4 steps, called onPreExecute, doInBackground,
55 | * onProgressUpdate and onPostExecute.
56 | *
57 | *
Developer Guides
58 | *
59 | * For more information about using tasks and threads, read the Processes and Threads developer guide.
61 | *
62 | *
Usage
63 | *
64 | * CustomAsyncTask must be subclassed to be used. The subclass will override at least one method (
65 | * {@link #doInBackground}), and most often will override a second one ({@link #onPostExecute}.)
66 | *
122 | * When an asynchronous task is executed, the task goes through 4 steps:
123 | *
124 | *
125 | *
{@link #onPreExecute()}, invoked on the UI thread immediately after the task is executed. This step is normally
126 | * used to setup the task, for instance by showing a progress bar in the user interface.
127 | *
{@link #doInBackground}, invoked on the background thread immediately after {@link #onPreExecute()} finishes
128 | * executing. This step is used to perform background computation that can take a long time. The parameters of the
129 | * asynchronous task are passed to this step. The result of the computation must be returned by this step and will be
130 | * passed back to the last step. This step can also use {@link #publishProgress} to publish one or more units of
131 | * progress. These values are published on the UI thread, in the {@link #onProgressUpdate} step.
132 | *
{@link #onProgressUpdate}, invoked on the UI thread after a call to {@link #publishProgress}. The timing of the
133 | * execution is undefined. This method is used to display any form of progress in the user interface while the
134 | * background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a
135 | * text field.
136 | *
{@link #onPostExecute}, invoked on the UI thread after the background computation finishes. The result of the
137 | * background computation is passed to this step as a parameter.
138 | *
139 | *
Cancelling a task
140 | *
141 | * A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking this method will cause subsequent
142 | * calls to {@link #isCancelled()} to return true. After invoking this method, {@link #onCancelled(Object)}, instead of
143 | * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} returns. To ensure that a task
144 | * is cancelled as quickly as possible, you should always check the return value of {@link #isCancelled()} periodically
145 | * from {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)
146 | *
147 | *
Threading rules
148 | *
149 | * There are a few threading rules that must be followed for this class to work properly:
150 | *
151 | *
152 | *
The CustomAsyncTask class must be loaded on the UI thread. This is done automatically as of
153 | * {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
154 | *
The task instance must be created on the UI thread.
155 | *
{@link #execute} must be invoked on the UI thread.
156 | *
Do not call {@link #onPreExecute()}, {@link #onPostExecute}, {@link #doInBackground}, {@link #onProgressUpdate}
157 | * manually.
158 | *
The task can be executed only once (an exception will be thrown if a second execution is attempted.)
159 | *
160 | *
Memory observability
161 | *
162 | * CustomAsyncTask guarantees that all callback calls are synchronized in such a way that the following operations are
163 | * safe without explicit synchronizations.
164 | *
165 | *
166 | *
Set member fields in the constructor or {@link #onPreExecute}, and refer to them in {@link #doInBackground}.
167 | *
Set member fields in {@link #doInBackground}, and refer to them in {@link #onProgressUpdate} and
168 | * {@link #onPostExecute}.
169 | *
170 | *
Order of execution
171 | *
172 | * When first introduced, CustomAsyncTasks were executed serially on a single background thread. Starting with
173 | * {@link android.os.Build.VERSION_CODES#DONUT}, this was changed to a pool of threads allowing multiple tasks to
174 | * operate in parallel. Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
175 | * thread to avoid common application errors caused by parallel execution.
176 | *
177 | *
178 | * If you truly want parallel execution, you can invoke
179 | * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with {@link #THREAD_POOL_EXECUTOR}.
180 | *
181 | */
182 | /**
183 | * same as {@link android.os.AsyncTask} but doesn't have a limit on the number of tasks,plus it uses threads as many as the
184 | * number of cores , minus 1 (or a single thread if it's a single core device)
185 | */
186 | public abstract class AsyncTaskEx
187 | {
188 | public interface IOnFinishedListener
189 | {
190 | void onFinished();
191 | }
192 |
193 | private static final String LOG_TAG ="CustomAsyncTask";
194 | private static final int CORE_POOL_SIZE =2;
195 | private static final int MAXIMUM_POOL_SIZE =Math.max(CORE_POOL_SIZE,getCoresCount()-1);
196 | private static final int KEEP_ALIVE =1;
197 | private static final ThreadFactory sThreadFactory =new ThreadFactory()
198 | {
199 | private final AtomicInteger mCount =new AtomicInteger(1);
200 |
201 | @Override
202 | public Thread newThread(final Runnable r)
203 | {
204 | return new Thread(r,"CustomAsyncTask #"+mCount.getAndIncrement());
205 | }
206 | };
207 | private static final BlockingQueue sPoolWorkQueue =new LinkedBlockingQueue<>();
208 | /**
209 | * An {@link java.util.concurrent.Executor} that can be used to execute tasks in parallel.
210 | */
211 | public static final Executor THREAD_POOL_EXECUTOR =new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);
212 | /**
213 | * An {@link java.util.concurrent.Executor} that executes tasks one at a time in serial order. This serialization is global to a
214 | * particular process.
215 | */
216 | // public static final Executor SERIAL_EXECUTOR =new SerialExecutor();
217 | private static final int MESSAGE_POST_RESULT =0x1;
218 | private static final int MESSAGE_POST_PROGRESS =0x2;
219 | private static final InternalHandler sHandler =new InternalHandler();
220 | private static volatile Executor sDefaultExecutor =THREAD_POOL_EXECUTOR;
221 | private final WorkerRunnable mWorker;
222 | private final FutureTask mFuture;
223 | private volatile Status mStatus =Status.PENDING;
224 | private final AtomicBoolean mCancelled =new AtomicBoolean();
225 | private final AtomicBoolean mTaskInvoked =new AtomicBoolean();
226 | private final Set mOnFinishedListeners =new HashSet<>();
227 |
228 | /**
229 | * return the number of cores of the device.
230 | * based on : http://stackoverflow.com/a/10377934/878126
231 | */
232 | private static int getCoresCount()
233 | {
234 | class CpuFilter implements FileFilter
235 | {
236 | @Override
237 | public boolean accept(final File pathname)
238 | {
239 | return Pattern.matches("cpu[0-9]+",pathname.getName());
240 | }
241 | }
242 | try
243 | {
244 | final File dir=new File("/sys/devices/system/cpu/");
245 | final File[] files=dir.listFiles(new CpuFilter());
246 | return files.length;
247 | }
248 | catch(final Exception e)
249 | {
250 | return Math.max(1,Runtime.getRuntime().availableProcessors());
251 | }
252 | }
253 |
254 | /**
255 | * Indicates the current status of the task. Each status will be set only once during the lifetime of a task.
256 | */
257 | public enum Status
258 | {
259 | /**
260 | * Indicates that the task has not been executed yet.
261 | */
262 | PENDING,
263 | /**
264 | * Indicates that the task is running.
265 | */
266 | RUNNING,
267 | /**
268 | * Indicates that {@link CustomAsyncTask#onPostExecute} has finished.
269 | */
270 | FINISHED,
271 | }
272 |
273 | // /** @hide Used to force static handler to be created. */
274 | // public static void init() {
275 | // sHandler.getLooper();
276 | // }
277 | // /** @hide */
278 | // public static void setDefaultExecutor(final Executor exec) {
279 | // sDefaultExecutor = exec;
280 | // }
281 | /**
282 | * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
283 | */
284 | public AsyncTaskEx()
285 | {
286 | mWorker=new WorkerRunnable()
287 | {
288 | @Override
289 | public Result call() throws Exception
290 | {
291 | mTaskInvoked.set(true);
292 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
293 | // noinspection unchecked
294 | return postResult(doInBackground(mParams));
295 | }
296 | };
297 | mFuture=new FutureTask(mWorker)
298 | {
299 | @Override
300 | protected void done()
301 | {
302 | try
303 | {
304 | postResultIfNotInvoked(get());
305 | }
306 | catch(final InterruptedException e)
307 | {
308 | android.util.Log.w(LOG_TAG,e);
309 | }
310 | catch(final ExecutionException e)
311 | {
312 | throw new RuntimeException("An error occured while executing doInBackground()",e.getCause());
313 | }
314 | catch(final CancellationException e)
315 | {
316 | postResultIfNotInvoked(null);
317 | }
318 | }
319 | };
320 | }
321 |
322 | private void postResultIfNotInvoked(final Result result)
323 | {
324 | final boolean wasTaskInvoked=mTaskInvoked.get();
325 | if(!wasTaskInvoked)
326 | postResult(result);
327 | }
328 |
329 | private Result postResult(final Result result)
330 | {
331 | @SuppressWarnings("unchecked")
332 | final Message message=sHandler.obtainMessage(MESSAGE_POST_RESULT,new AsyncTaskExResult<>(this,result));
333 | message.sendToTarget();
334 | return result;
335 | }
336 |
337 | /**
338 | * Returns the current status of this task.
339 | *
340 | * @return The current status.
341 | */
342 | public final Status getStatus()
343 | {
344 | return mStatus;
345 | }
346 |
347 | /**
348 | * Override this method to perform a computation on a background thread. The specified parameters are the parameters
349 | * passed to {@link #execute} by the caller of this task. This method can call {@link #publishProgress} to publish
350 | * updates on the UI thread.
351 | *
352 | * @param params
353 | * The parameters of the task.
354 | * @return A result, defined by the subclass of this task.
355 | * @see #onPreExecute()
356 | * @see #onPostExecute
357 | * @see #publishProgress
358 | */
359 | public abstract Result doInBackground(@SuppressWarnings("unchecked") Params... params);
360 |
361 | /**
362 | * Runs on the UI thread before {@link #doInBackground}.
363 | *
364 | * @see #onPostExecute
365 | * @see #doInBackground
366 | */
367 | protected void onPreExecute()
368 | {}
369 |
370 | /**
371 | *
372 | * Runs on the UI thread after {@link #doInBackground}. The specified result is the value returned by {@link #doInBackground}.
373 | *
374 | *
375 | * This method won't be invoked if the task was cancelled.
376 | *
377 | *
378 | * @param result
379 | * The result of the operation computed by {@link #doInBackground}.
380 | * @see #onPreExecute
381 | * @see #doInBackground
382 | * @see #onCancelled(Object)
383 | */
384 | public void onPostExecute(final Result result)
385 | {}
386 |
387 | /**
388 | * Runs on the UI thread after {@link #publishProgress} is invoked. The specified values are the values passed to {@link #publishProgress}.
389 | *
390 | * @param values
391 | * The values indicating progress.
392 | * @see #publishProgress
393 | * @see #doInBackground
394 | */
395 | protected void onProgressUpdate(@SuppressWarnings("unchecked") final Progress... values)
396 | {}
397 |
398 | /**
399 | *
400 | * Runs on the UI thread after {@link #cancel(boolean)} is invoked and {@link #doInBackground(Object[])} has finished.
401 | *
402 | *
403 | * The default implementation simply invokes {@link #onCancelled()} and ignores the result. If you write your own implementation, do not call super.onCancelled(result).
404 | *
405 | *
406 | * @param result
407 | * The result, if any, computed in {@link #doInBackground(Object[])}, can be null
408 | * @see #cancel(boolean)
409 | * @see #isCancelled()
410 | */
411 | protected void onCancelled(final Result result)
412 | {
413 | onCancelled();
414 | }
415 |
416 | /**
417 | *
418 | * Applications should preferably override {@link #onCancelled(Object)}. This method is invoked by the default implementation of {@link #onCancelled(Object)}.
419 | *
420 | *
421 | * Runs on the UI thread after {@link #cancel(boolean)} is invoked and {@link #doInBackground(Object[])} has finished.
422 | *
423 | *
424 | * @see #onCancelled(Object)
425 | * @see #cancel(boolean)
426 | * @see #isCancelled()
427 | */
428 | protected void onCancelled()
429 | {}
430 |
431 | /**
432 | * Returns true if this task was cancelled before it completed normally. If you are calling {@link #cancel(boolean)} on the task, the value returned by this method should be checked periodically from {@link #doInBackground(Object[])} to end the task as soon as possible.
433 | *
434 | * @return true if task was cancelled before it completed
435 | * @see #cancel(boolean)
436 | */
437 | public final boolean isCancelled()
438 | {
439 | return mCancelled.get();
440 | }
441 |
442 | /**
443 | *
444 | * Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
445 | *
446 | *
447 | * Calling this method will result in {@link #onCancelled(Object)} being invoked on the UI thread after {@link #doInBackground(Object[])} returns. Calling this method guarantees that {@link #onPostExecute(Object)} is never invoked. After invoking this method, you should check the value returned by {@link #isCancelled()} periodically from {@link #doInBackground(Object[])} to finish the task as early as possible.
448 | *
449 | *
450 | * @param mayInterruptIfRunning
451 | * true if the thread executing this task should be interrupted; otherwise, in-progress tasks
452 | * are allowed to complete.
453 | * @return false if the task could not be cancelled, typically because it has already completed normally; true otherwise
454 | * @see #isCancelled()
455 | * @see #onCancelled(Object)
456 | */
457 | public final boolean cancel(final boolean mayInterruptIfRunning)
458 | {
459 | mCancelled.set(true);
460 | return mFuture.cancel(mayInterruptIfRunning);
461 | }
462 |
463 | /**
464 | * Waits if necessary for the computation to complete, and then retrieves its result.
465 | *
466 | * @return The computed result.
467 | * @throws java.util.concurrent.CancellationException
468 | * If the computation was cancelled.
469 | * @throws java.util.concurrent.ExecutionException
470 | * If the computation threw an exception.
471 | * @throws InterruptedException
472 | * If the current thread was interrupted while waiting.
473 | */
474 | public final Result get() throws InterruptedException,ExecutionException
475 | {
476 | return mFuture.get();
477 | }
478 |
479 | /**
480 | * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result.
481 | *
482 | * @param timeout
483 | * Time to wait before cancelling the operation.
484 | * @param unit
485 | * The time unit for the timeout.
486 | * @return The computed result.
487 | * @throws java.util.concurrent.CancellationException
488 | * If the computation was cancelled.
489 | * @throws java.util.concurrent.ExecutionException
490 | * If the computation threw an exception.
491 | * @throws InterruptedException
492 | * If the current thread was interrupted while waiting.
493 | * @throws java.util.concurrent.TimeoutException
494 | * If the wait timed out.
495 | */
496 | public final Result get(final long timeout,final TimeUnit unit) throws InterruptedException,ExecutionException,TimeoutException
497 | {
498 | return mFuture.get(timeout,unit);
499 | }
500 |
501 | /**
502 | * Executes the task with the specified parameters. The task returns itself (this) so that the caller can keep a
503 | * reference to it.
504 | *
505 | * Note: this function schedules the task on a queue for a single background thread or pool of threads depending on the platform version. When first introduced, CustomAsyncTasks were executed serially on a single background thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being executed on a single thread to avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the {@link #executeOnExecutor} version of this method with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings on its use.
506 | *
507 | * This method must be invoked on the UI thread.
508 | *
509 | * @param params
510 | * The parameters of the task.
511 | * @return This instance of CustomAsyncTask.
512 | * @throws IllegalStateException
513 | * If {@link #getStatus()} returns either {@link lb.listviewvariants.utils.async_task_thread_pool.AsyncTaskEx.Status#RUNNING} or {@link lb.listviewvariants.utils.async_task_thread_pool.AsyncTaskEx.Status#FINISHED}.
514 | * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
515 | * @see #execute(Runnable)
516 | */
517 | public final AsyncTaskEx execute(@SuppressWarnings("unchecked") final Params... params)
518 | {
519 | return executeOnExecutor(sDefaultExecutor,params);
520 | }
521 |
522 | /**
523 | * Executes the task with the specified parameters. The task returns itself (this) so that the caller can keep a
524 | * reference to it.
525 | *
526 | * This method is typically used with {@link #THREAD_POOL_EXECUTOR} to allow multiple tasks to run in parallel on a pool of threads managed by CustomAsyncTask, however you can also use your own {@link java.util.concurrent.Executor} for custom behavior.
527 | *
528 | * Warning: Allowing multiple tasks to run in parallel from a thread pool is generally not what one wants, because the order of their operation is not defined. For example, if these tasks are used to modify any state in common (such as writing a file due to a button click), there are no guarantees on the order of the modifications. Without careful work it is possible in rare cases for the newer version of the data to be over-written by an older one, leading to obscure data loss and stability issues. Such changes are best executed in serial; to guarantee such work is serialized regardless of platform version you can use this function with {@link #SERIAL_EXECUTOR}.
529 | *
530 | * This method must be invoked on the UI thread.
531 | *
532 | * @param exec
533 | * The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a convenient process-wide thread
534 | * pool for tasks that are loosely coupled.
535 | * @param params
536 | * The parameters of the task.
537 | * @return This instance of CustomAsyncTask.
538 | * @throws IllegalStateException
539 | * If {@link #getStatus()} returns either {@link lb.listviewvariants.utils.async_task_thread_pool.AsyncTaskEx.Status#RUNNING} or {@link lb.listviewvariants.utils.async_task_thread_pool.AsyncTaskEx.Status#FINISHED}.
540 | * @see #execute(Object[])
541 | */
542 | public final AsyncTaskEx executeOnExecutor(final Executor exec,@SuppressWarnings("unchecked") final Params... params)
543 | {
544 | if(mStatus!=Status.PENDING)
545 | switch(mStatus)
546 | {
547 | case RUNNING:
548 | throw new IllegalStateException("Cannot execute task:"+" the task is already running.");
549 | case FINISHED:
550 | throw new IllegalStateException("Cannot execute task:"+" the task has already been executed "+"(a task can be executed only once)");
551 | case PENDING:
552 | break;
553 | default:
554 | break;
555 | }
556 | mStatus=Status.RUNNING;
557 | onPreExecute();
558 | mWorker.mParams=params;
559 | exec.execute(mFuture);
560 | return this;
561 | }
562 |
563 | /**
564 | * Convenience version of {@link #execute(Object...)} for use with a simple Runnable object. See {@link #execute(Object[])} for more information on the order of execution.
565 | *
566 | * @see #execute(Object[])
567 | * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
568 | */
569 | public static void execute(final Runnable runnable)
570 | {
571 | sDefaultExecutor.execute(runnable);
572 | }
573 |
574 | /**
575 | * This method can be invoked from {@link #doInBackground} to publish updates on the UI thread while the background
576 | * computation is still running. Each call to this method will trigger the execution of {@link #onProgressUpdate} on
577 | * the UI thread. {@link #onProgressUpdate} will note be called if the task has been canceled.
578 | *
579 | * @param values
580 | * The progress values to update the UI with.
581 | * @see #onProgressUpdate
582 | * @see #doInBackground
583 | */
584 | public final void publishProgress(@SuppressWarnings("unchecked") final Progress... values)
585 | {
586 | if(!isCancelled())
587 | sHandler.obtainMessage(MESSAGE_POST_PROGRESS,new AsyncTaskExResult<>(this,values)).sendToTarget();
588 | }
589 |
590 | private void finish(final Result result)
591 | {
592 | if(isCancelled())
593 | onCancelled(result);
594 | else onPostExecute(result);
595 | for(final IOnFinishedListener listener : mOnFinishedListeners)
596 | listener.onFinished();
597 | mStatus=Status.FINISHED;
598 | }
599 |
600 | public void addOnFinishedListener(final IOnFinishedListener onFinishedListener)
601 | {
602 | this.mOnFinishedListeners.add(onFinishedListener);
603 | }
604 |
605 | public void removeOnFinishedListener(final IOnFinishedListener onFinishedListener)
606 | {
607 | this.mOnFinishedListeners.remove(onFinishedListener);
608 | }
609 |
610 | private static class InternalHandler extends Handler
611 | {
612 | @SuppressWarnings({"unchecked","rawtypes"})
613 | @Override
614 | public void handleMessage(final Message msg)
615 | {
616 | final AsyncTaskExResult result=(AsyncTaskExResult)msg.obj;
617 | switch(msg.what)
618 | {
619 | case MESSAGE_POST_RESULT:
620 | // There is only one result
621 | result.mTask.finish(result.mData[0]);
622 | break;
623 | case MESSAGE_POST_PROGRESS:
624 | result.mTask.onProgressUpdate(result.mData);
625 | break;
626 | }
627 | }
628 | }
629 |
630 | private static abstract class WorkerRunnable implements Callable
631 | {
632 | Params[] mParams;
633 | }
634 |
635 | @SuppressWarnings({})
636 | private static class AsyncTaskExResult
637 | {
638 | final AsyncTaskEx,?,?> mTask;
639 | final Data[] mData;
640 |
641 | AsyncTaskExResult(final AsyncTaskEx,?,?> task,@SuppressWarnings("unchecked") final Data... data)
642 | {
643 | mTask=task;
644 | mData=data;
645 | }
646 | }
647 | }
648 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/async_task_thread_pool/AsyncTaskThreadPool.java:
--------------------------------------------------------------------------------
1 | package lb.listviewvariants.utils.async_task_thread_pool;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 | import java.util.concurrent.ThreadFactory;
6 | import java.util.concurrent.ThreadPoolExecutor;
7 | import java.util.concurrent.TimeUnit;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 |
10 | /**
11 | * a thread pool that contains tasks to run like on asyncTask.
12 | * also has the ability to use a stack instead of a queue for the order of tasks
13 | */
14 | public class AsyncTaskThreadPool
15 | {
16 | // classes used for the threadPool were created because of an issue with customized parameters of
17 | // ThreadPoolExecutor:
18 | // https://github.com/kimchy/kimchy.github.com/blob/master/_posts/2008-11-23-juc-executorservice-gotcha.textile
19 | private static final int TIME_TO_KEEP_ALIVE_IN_SECONDS =10;
20 | private static final int CORE_POOL_SIZE =1;
21 | private static final int MAXIMUM_POOL_SIZE =Math.max(CORE_POOL_SIZE,Runtime.getRuntime().availableProcessors()-1);
22 | private final ThreadPoolExecutor mExecutor;
23 | // private final Executor mExecutor = Executors.newSingleThreadExecutor();
24 | // private final Executor mExecutor = Executors.newCachedThreadPool();
25 | // private final Executor mExecutor = Executors.newFixedThreadPool(MAXIMUM_POOL_SIZE);
26 | // private final Executor mExecutor = Executors.newScheduledThreadPool(corePoolSize, threadFactory)
27 | private final Set> mTasks =new HashSet<>();
28 |
29 | public AsyncTaskThreadPool()
30 | {
31 | this(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,TIME_TO_KEEP_ALIVE_IN_SECONDS);
32 | }
33 |
34 | public AsyncTaskThreadPool(final int minNumberOfThread,final int maxNumberOfThread,final int keepAliveTimeInSeconds)
35 | {
36 | final ScalingQueue poolWorkQueue=new ScalingQueue<>();
37 | final ThreadFactory threadFactory=new ThreadFactory()
38 | {
39 | private final AtomicInteger mCount =new AtomicInteger(1);
40 |
41 | @Override
42 | public Thread newThread(final Runnable r)
43 | {
44 | final Thread thread=new Thread(r,"thread #"+mCount.getAndIncrement());
45 | thread.setPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
46 | return thread;
47 | }
48 | };
49 | // needed because normal ThreadPoolExecutor always uses a single thread.
50 | mExecutor=new ScalingThreadPoolExecutor(minNumberOfThread,maxNumberOfThread,keepAliveTimeInSeconds,TimeUnit.SECONDS,poolWorkQueue,threadFactory);
51 | mExecutor.setRejectedExecutionHandler(new ForceQueuePolicy());
52 | poolWorkQueue.setThreadPoolExecutor(mExecutor);
53 | }
54 |
55 | /** runs the task and remembers it till it finishes. */
56 | public void executeAsyncTask(final AsyncTaskEx task,@SuppressWarnings("unchecked") final Params... params)
57 | {
58 | task.addOnFinishedListener(new AsyncTaskEx.IOnFinishedListener()
59 | {
60 | @Override
61 | public void onFinished()
62 | {
63 | mTasks.remove(task);
64 | task.removeOnFinishedListener(this);
65 | }
66 | });
67 | mTasks.add(task);
68 | task.executeOnExecutor(mExecutor,params);
69 | }
70 |
71 | public void executeAsyncTask(final AsyncTaskEx task)
72 | {
73 | //noinspection unchecked
74 | executeAsyncTask(task,(Params[])null);
75 | }
76 |
77 | public void cancelAllTasks(final boolean alsoInterrupt)
78 | {
79 | for(final AsyncTaskEx,?,?> task : mTasks)
80 | task.cancel(alsoInterrupt);
81 | mTasks.clear();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/async_task_thread_pool/ForceQueuePolicy.java:
--------------------------------------------------------------------------------
1 | package lb.listviewvariants.utils.async_task_thread_pool;
2 | import java.util.concurrent.RejectedExecutionException;
3 | import java.util.concurrent.RejectedExecutionHandler;
4 | import java.util.concurrent.ThreadPoolExecutor;
5 |
6 | class ForceQueuePolicy implements RejectedExecutionHandler
7 | {
8 | @Override
9 | public void rejectedExecution(final Runnable r,final ThreadPoolExecutor executor)
10 | {
11 | try
12 | {
13 | executor.getQueue().put(r);
14 | }
15 | catch(final InterruptedException e)
16 | {
17 | // should never happen since we never wait
18 | throw new RejectedExecutionException(e);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/lb/listviewvariants/utils/async_task_thread_pool/ScalingQueue.java:
--------------------------------------------------------------------------------
1 | package lb.listviewvariants.utils.async_task_thread_pool;
2 | import java.util.concurrent.LinkedBlockingQueue;
3 | import java.util.concurrent.ThreadPoolExecutor;
4 |
5 | public class ScalingQueue extends LinkedBlockingQueue
6 | {
7 | private static final long serialVersionUID =2868771663367097439L;
8 | /**
9 | * The executor this Queue belongs to
10 | */
11 | private ThreadPoolExecutor executor;
12 |
13 | /**
14 | * Creates a TaskQueue with a capacity of {@link Integer#MAX_VALUE}.
15 | */
16 | public ScalingQueue()
17 | {
18 | super();
19 | }
20 |
21 | /**
22 | * Creates a TaskQueue with the given (fixed) capacity.
23 | *
24 | * @param capacity
25 | * the capacity of this queue.
26 | */
27 | public ScalingQueue(final int capacity)
28 | {
29 | super(capacity);
30 | }
31 |
32 | /**
33 | * Sets the executor this queue belongs to.
34 | */
35 | public void setThreadPoolExecutor(final ThreadPoolExecutor executor)
36 | {
37 | this.executor=executor;
38 | }
39 |
40 | /**
41 | * Inserts the specified element at the tail of this queue if there is at least one available thread to run the
42 | * current task. If all pool threads are actively busy, it rejects the offer.
43 | *
44 | * @param o
45 | * the element to add.
46 | * @return true if it was possible to add the element to this queue, else false
47 | * @see java.util.concurrent.ThreadPoolExecutor#execute(Runnable)
48 | */
49 | @Override
50 | public boolean offer(final T o)
51 | {
52 | final int allWorkingThreads=executor.getActiveCount()+super.size();
53 | return allWorkingThreads
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/listview_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_action_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/app/src/main/res/drawable-xhdpi/ic_action_action_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_person_white_120dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/app/src/main/res/drawable-xhdpi/ic_person_white_120dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_action_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/app/src/main/res/drawable-xxhdpi/ic_action_action_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_action_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/app/src/main/res/drawable-xxxhdpi/ic_action_action_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/listview_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/listview_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
28 |
29 |
34 |
35 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/pinned_header_listview_side_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 0xFF9C27B0
5 | 0xFFFF5722
6 | 0xFF3F51B5
7 | 0xFFDB4437
8 | 0xFFEF6C00
9 | 0xFF689F38
10 | 0xFF757575
11 | 0xFF0F9D58
12 | 0xFF3F51B5
13 | 0xFF0097A7
14 |
15 |
16 |
17 |
18 | #FFdddddd
19 | #FF7dbcd3
20 | #FFededed
21 |
22 | #FF3c3c3c
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 55dp
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ListViewVariants
5 | Repository website
6 | All my repositories
7 | All my apps
8 | More info
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
17 |
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.0-alpha5'
9 | // NOTE: Do not place your application dependencies here; they belong
10 | // in the individual module build.gradle files
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/demo.gif
--------------------------------------------------------------------------------
/device-2014-12-28-230610.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/device-2014-12-28-230610.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidDeveloperLB/ListViewVariants/bca271a97bee485172cd094ed927a40ef8ae2644/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Apr 02 22:32:53 IDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 8
9 | targetSdkVersion 22
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | compile 'com.android.support:appcompat-v7:23.2.1'
24 | }
25 |
--------------------------------------------------------------------------------
/library/library.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:/android/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/library/src/main/java/lb/library/BasePinnedHeaderListViewAdapter.java:
--------------------------------------------------------------------------------
1 | package lb.library;
2 |
3 | import android.view.View;
4 | import android.view.ViewGroup;
5 | import android.widget.AbsListView;
6 | import android.widget.AbsListView.OnScrollListener;
7 | import android.widget.BaseAdapter;
8 | import android.widget.SectionIndexer;
9 | import android.widget.TextView;
10 |
11 |
12 | public abstract class BasePinnedHeaderListViewAdapter extends BaseAdapter implements SectionIndexer, OnScrollListener,
13 | PinnedHeaderListView.PinnedHeaderAdapter
14 | {
15 | private SectionIndexer _sectionIndexer;
16 | private boolean mHeaderViewVisible = true;
17 |
18 | public void setSectionIndexer(final SectionIndexer sectionIndexer) {
19 | _sectionIndexer = sectionIndexer;
20 | }
21 |
22 | /** remember to call bindSectionHeader(v,position); before calling return */
23 | @Override
24 | public abstract View getView(final int position, final View convertView, final ViewGroup parent);
25 |
26 | public abstract CharSequence getSectionTitle(int sectionIndex);
27 |
28 | protected void bindSectionHeader(final TextView headerView, final View dividerView, final int position) {
29 | final int sectionIndex = getSectionForPosition(position);
30 | if (getPositionForSection(sectionIndex) == position) {
31 | final CharSequence title = getSectionTitle(sectionIndex);
32 | headerView.setText(title);
33 | headerView.setVisibility(View.VISIBLE);
34 | if (dividerView != null)
35 | dividerView.setVisibility(View.GONE);
36 | } else {
37 | headerView.setVisibility(View.GONE);
38 | if (dividerView != null)
39 | dividerView.setVisibility(View.VISIBLE);
40 | }
41 | // move the divider for the last item in a section
42 | if (dividerView != null)
43 | if (getPositionForSection(sectionIndex + 1) - 1 == position)
44 | dividerView.setVisibility(View.GONE);
45 | else
46 | dividerView.setVisibility(View.VISIBLE);
47 | if (!mHeaderViewVisible)
48 | headerView.setVisibility(View.GONE);
49 | }
50 |
51 | @Override
52 | public int getPinnedHeaderState(final int position) {
53 | if (_sectionIndexer == null || getCount() == 0 || !mHeaderViewVisible)
54 | return PINNED_HEADER_GONE;
55 | if (position < 0)
56 | return PINNED_HEADER_GONE;
57 | // The header should get pushed up if the top item shown
58 | // is the last item in a section for a particular letter.
59 | final int section = getSectionForPosition(position);
60 | final int nextSectionPosition = getPositionForSection(section + 1);
61 | if (nextSectionPosition != -1 && position == nextSectionPosition - 1)
62 | return PINNED_HEADER_PUSHED_UP;
63 | return PINNED_HEADER_VISIBLE;
64 | }
65 |
66 | public void setHeaderViewVisible(final boolean isHeaderViewVisible) {
67 | mHeaderViewVisible = isHeaderViewVisible;
68 | }
69 |
70 | public boolean isHeaderViewVisible() {
71 | return this.mHeaderViewVisible;
72 | }
73 |
74 | @Override
75 | public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
76 | final int totalItemCount) {
77 | ((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem);
78 | }
79 |
80 | @Override
81 | public void onScrollStateChanged(final AbsListView arg0, final int arg1) {
82 | }
83 |
84 | @Override
85 | public int getPositionForSection(final int sectionIndex) {
86 | if (_sectionIndexer == null)
87 | return -1;
88 | return _sectionIndexer.getPositionForSection(sectionIndex);
89 | }
90 |
91 | @Override
92 | public int getSectionForPosition(final int position) {
93 | if (_sectionIndexer == null)
94 | return -1;
95 | return _sectionIndexer.getSectionForPosition(position);
96 | }
97 |
98 | @Override
99 | public Object[] getSections() {
100 | if (_sectionIndexer == null)
101 | return new String[] { " " };
102 | return _sectionIndexer.getSections();
103 | }
104 |
105 | @Override
106 | public long getItemId(final int position) {
107 | return position;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/library/src/main/java/lb/library/BaseSectionedAdapter.java:
--------------------------------------------------------------------------------
1 | package lb.library;
2 |
3 | import android.widget.BaseAdapter;
4 | import android.widget.SectionIndexer;
5 |
6 | /**
7 | * a base adapter that allows having multiple sections. each section has its own items. items don't have to be of the
8 | * same type
9 | */
10 | public abstract class BaseSectionedAdapter extends BaseAdapter implements SectionIndexer {
11 | private SectionedSectionIndexer mSectionIndexer;
12 |
13 | public void setSectionIndexer(final SectionedSectionIndexer sectionIndexer) {
14 | mSectionIndexer = sectionIndexer;
15 | }
16 |
17 | public SectionIndexer getSectionIndexer() {
18 | return this.mSectionIndexer;
19 | }
20 |
21 | @Override
22 | public int getPositionForSection(final int sectionIndex) {
23 | if (mSectionIndexer == null)
24 | return -1;
25 | return mSectionIndexer.getPositionForSection(sectionIndex);
26 | }
27 |
28 | @Override
29 | public int getSectionForPosition(final int position) {
30 | if (mSectionIndexer == null)
31 | return -1;
32 | return mSectionIndexer.getSectionForPosition(position);
33 | }
34 |
35 | @Override
36 | public Object[] getSections() {
37 | if (mSectionIndexer == null)
38 | return new String[] { " " };
39 | return mSectionIndexer.getSections();
40 | }
41 |
42 | @Override
43 | public long getItemId(final int position) {
44 | return position;
45 | }
46 |
47 | @Override
48 | public int getCount() {
49 | return mSectionIndexer.getItemsCount();
50 | }
51 |
52 | @Override
53 | public Object getItem(final int position) {
54 | return mSectionIndexer.getItem(position);
55 | }
56 |
57 | @Override
58 | public int getViewTypeCount() {
59 | return mSectionIndexer.getSections().length;
60 | }
61 |
62 | @Override
63 | public int getItemViewType(final int position) {
64 | return mSectionIndexer.getSectionForPosition(position);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/library/src/main/java/lb/library/IndexedPinnedHeaderListViewAdapter.java:
--------------------------------------------------------------------------------
1 | package lb.library;
2 |
3 | import android.graphics.Color;
4 | import android.os.Build.VERSION;
5 | import android.os.Build.VERSION_CODES;
6 | import android.view.View;
7 | import android.widget.TextView;
8 |
9 | public abstract class IndexedPinnedHeaderListViewAdapter extends BasePinnedHeaderListViewAdapter
10 | {
11 | private int _pinnedHeaderBackgroundColor;
12 | private int _pinnedHeaderTextColor;
13 |
14 | public void setPinnedHeaderBackgroundColor(final int pinnedHeaderBackgroundColor)
15 | {
16 | _pinnedHeaderBackgroundColor=pinnedHeaderBackgroundColor;
17 | }
18 |
19 | public void setPinnedHeaderTextColor(final int pinnedHeaderTextColor)
20 | {
21 | _pinnedHeaderTextColor=pinnedHeaderTextColor;
22 | }
23 |
24 | @Override
25 | public CharSequence getSectionTitle(final int sectionIndex)
26 | {
27 | return getSections()[sectionIndex].toString();
28 | }
29 |
30 | @Override
31 | public void configurePinnedHeader(final View v,final int position,final int alpha)
32 | {
33 | final TextView header=(TextView)v;
34 | final int sectionIndex=getSectionForPosition(position);
35 | final Object[] sections=getSections();
36 | if(sections!=null&§ions.length!=0)
37 | {
38 | final CharSequence title=getSectionTitle(sectionIndex);
39 | header.setText(title);
40 | }
41 | if(VERSION.SDK_INT extends IndexedPinnedHeaderListViewAdapter implements
14 | Filterable {
15 | private ArrayList mFilterListCopy;
16 | private final Filter mFilter;
17 |
18 | public SearchablePinnedHeaderListViewAdapter() {
19 | mFilter = new Filter() {
20 | CharSequence lastConstraint = null;
21 |
22 | @Override
23 | protected FilterResults performFiltering(final CharSequence constraint) {
24 | if (constraint == null || constraint.length() == 0)
25 | return null;
26 | final ArrayList newFilterArray = new ArrayList();
27 | final FilterResults results = new FilterResults();
28 | for (final T item : getOriginalList())
29 | if (doFilter(item, constraint))
30 | newFilterArray.add(item);
31 | results.values = newFilterArray;
32 | results.count = newFilterArray.size();
33 | return results;
34 | }
35 |
36 | @SuppressWarnings("unchecked")
37 | @Override
38 | protected void publishResults(final CharSequence constraint, final FilterResults results) {
39 | mFilterListCopy = results == null ? null : (ArrayList) results.values;
40 | final boolean needRefresh = !TextUtils.equals(constraint,lastConstraint);
41 | lastConstraint = constraint == null ? null : constraint;
42 | if (needRefresh)
43 | notifyDataSetChanged();
44 | }
45 | };
46 | }
47 |
48 | @Override
49 | public Filter getFilter() {
50 | return mFilter;
51 | }
52 |
53 | /** returns true iff the item can "pass" the filtering process and should be shown */
54 | public abstract boolean doFilter(T item, CharSequence constraint);
55 |
56 | public abstract ArrayList getOriginalList();
57 |
58 | @Override
59 | public T getItem(final int position) {
60 | if (position < 0)
61 | return null;
62 | final ArrayList listCopy = getFilterListCopy();
63 | if (listCopy != null) {
64 | if (position < listCopy.size())
65 | return listCopy.get(position);
66 | else
67 | return null;
68 | } else {
69 | final ArrayList originalList = getOriginalList();
70 | if (position < originalList.size())
71 | return originalList.get(position);
72 | else
73 | return null;
74 | }
75 | }
76 |
77 | @Override
78 | public int getCount() {
79 | final ArrayList listCopy = getFilterListCopy();
80 | if (listCopy != null)
81 | return listCopy.size();
82 | else
83 | return getOriginalList().size();
84 | }
85 |
86 | private ArrayList getFilterListCopy() {
87 | return mFilterListCopy;
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/library/src/main/java/lb/library/SectionedSectionIndexer.java:
--------------------------------------------------------------------------------
1 | package lb.library;
2 |
3 | import android.widget.SectionIndexer;
4 |
5 | public class SectionedSectionIndexer implements SectionIndexer {
6 | private final SimpleSection[] mSectionArray;
7 |
8 | public SectionedSectionIndexer(final SimpleSection[] sections) {
9 | mSectionArray = sections;
10 | //
11 | int previousIndex = 0;
12 | for (int i = 0; i < mSectionArray.length; ++i) {
13 | mSectionArray[i].startIndex = previousIndex;
14 | previousIndex += mSectionArray[i].getItemsCount();
15 | mSectionArray[i].endIndex = previousIndex - 1;
16 | }
17 | }
18 |
19 | @Override
20 | public int getPositionForSection(final int section) {
21 | final int result = section < 0 || section >= mSectionArray.length ? -1 : mSectionArray[section].startIndex;
22 | return result;
23 | }
24 |
25 | /** given a flat position, returns the position within the section */
26 | public int getPositionInSection(final int flatPos) {
27 | final int sectionForPosition = getSectionForPosition(flatPos);
28 | final SimpleSection simpleSection = mSectionArray[sectionForPosition];
29 | return flatPos - simpleSection.startIndex;
30 | }
31 |
32 | @Override
33 | public int getSectionForPosition(final int flatPos) {
34 | if (flatPos < 0)
35 | return -1;
36 | int start = 0, end = mSectionArray.length - 1;
37 | int piv = (start + end) / 2;
38 | while (true) {
39 | final SimpleSection section = mSectionArray[piv];
40 | if (flatPos >= section.startIndex && flatPos <= section.endIndex)
41 | return piv;
42 | if (piv == start && start == end)
43 | return -1;
44 | if (flatPos < section.startIndex)
45 | end = piv - 1;
46 | else
47 | start = piv + 1;
48 | piv = (start + end) / 2;
49 | }
50 | }
51 |
52 | @Override
53 | public SimpleSection[] getSections() {
54 | return mSectionArray;
55 | }
56 |
57 | public Object getItem(final int flatPos) {
58 | final int sectionIndex = getSectionForPosition(flatPos);
59 | final SimpleSection section = mSectionArray[sectionIndex];
60 | final Object result = section.getItem(flatPos - section.startIndex);
61 | return result;
62 | }
63 |
64 | public Object getItem(final int sectionIndex, final int positionInSection) {
65 | final SimpleSection section = mSectionArray[sectionIndex];
66 | final Object result = section.getItem(positionInSection);
67 | return result;
68 | }
69 |
70 | public int getRawPosition(final int sectionIndex, final int positionInSection) {
71 | final SimpleSection section = mSectionArray[sectionIndex];
72 | return section.startIndex + positionInSection;
73 | }
74 |
75 | public int getItemsCount() {
76 | if (mSectionArray.length == 0)
77 | return 0;
78 | return mSectionArray[mSectionArray.length - 1].endIndex + 1;
79 | }
80 |
81 | // /////////////////////////////////////////////
82 | // Section //
83 | // //////////
84 | public static abstract class SimpleSection {
85 | private String name;
86 | private int startIndex, endIndex;
87 |
88 | public SimpleSection() {
89 | }
90 |
91 | public SimpleSection(final String sectionName) {
92 | this.name = sectionName;
93 | }
94 |
95 | public String getName() {
96 | return name;
97 | }
98 |
99 | public void setName(final String name) {
100 | this.name = name;
101 | }
102 |
103 | public abstract int getItemsCount();
104 |
105 | public abstract Object getItem(int posInSection);
106 |
107 | @Override
108 | public String toString()
109 | {
110 | return name;
111 | }
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/library/src/main/java/lb/library/StringArrayAlphabetIndexer.java:
--------------------------------------------------------------------------------
1 | package lb.library;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.Locale;
8 | import java.util.Map;
9 | import java.util.Set;
10 | import java.util.TreeSet;
11 |
12 | public class StringArrayAlphabetIndexer extends SectionedSectionIndexer
13 | {
14 | /**
15 | * @param items each of the items. Note that they must be sorted in a way that each chunk will belong to
16 | * a specific header. For example, chunk with anything that starts with "A"/"a", then a chunk
17 | * that all of its items start with "B"/"b" , etc...
18 | * @param useOnlyUppercaseHeaders whether the header will be in uppercase or not.
19 | * if true, you must order the items so that each chunk will have its items start with either the lowercase or uppercase letter
20 | */
21 | public StringArrayAlphabetIndexer(String[] items,boolean useOnlyUppercaseHeaders)
22 | {
23 | super(createSectionsFromStrings(items,useOnlyUppercaseHeaders));
24 | }
25 |
26 | private static SimpleSection[] createSectionsFromStrings(String[] items,boolean useOnlyUppercaseHeaders)
27 | {
28 | //get all of the headers of the sections and their sections-items:
29 | Map> headerToSectionItemsMap=new HashMap>();
30 | Set alphabetSet=new TreeSet();
31 | for(String item : items)
32 | {
33 | String firstLetter=TextUtils.isEmpty(item)?" ":useOnlyUppercaseHeaders?item.substring(0,1).toUpperCase(Locale.getDefault()):
34 | item.substring(0,1);
35 | ArrayList sectionItems=headerToSectionItemsMap.get(firstLetter);
36 | if(sectionItems==null)
37 | headerToSectionItemsMap.put(firstLetter,sectionItems=new ArrayList());
38 | sectionItems.add(item);
39 | alphabetSet.add(firstLetter);
40 | }
41 | //prepare the sections, and also sort each section's items :
42 | SimpleSection[] sections=new SimpleSection[alphabetSet.size()];
43 | int i=0;
44 | for(String headerTitle : alphabetSet)
45 | {
46 | ArrayList sectionItems=headerToSectionItemsMap.get(headerTitle);
47 | SimpleSection simpleSection=new AlphaBetSection(sectionItems);
48 | simpleSection.setName(headerTitle);
49 | sections[i++]=simpleSection;
50 | }
51 | return sections;
52 | }
53 |
54 | public static class AlphaBetSection extends SimpleSection
55 | {
56 | private ArrayList items;
57 |
58 | private AlphaBetSection(ArrayList items)
59 | {
60 | this.items=items;
61 | }
62 |
63 | @Override
64 | public int getItemsCount()
65 | {
66 | return items.size();
67 | }
68 |
69 | @Override
70 | public String getItem(int posInSection)
71 | {
72 | return items.get(posInSection);
73 | }
74 |
75 | }
76 |
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------