├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ └── layout
│ │ │ │ ├── view_list_no_selection_item.xml
│ │ │ │ ├── view_list_item.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── content_main.xml
│ │ ├── java
│ │ │ └── gr
│ │ │ │ └── escsoft
│ │ │ │ └── michaelprimez
│ │ │ │ └── searchablespinnerexamples
│ │ │ │ ├── SearchableSpinnerApp.java
│ │ │ │ ├── SimpleListAdapter.java
│ │ │ │ ├── SimpleArrayListAdapter.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── gr
│ │ │ └── escsoft
│ │ │ └── michaelprimez
│ │ │ └── searchablespinnerexamples
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── gr
│ │ └── escsoft
│ │ └── michaelprimez
│ │ └── searchablespinnerexamples
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── searchablespinner
├── .gitignore
├── src
│ ├── main
│ │ ├── java
│ │ │ └── gr
│ │ │ │ └── escsoft
│ │ │ │ └── michaelprimez
│ │ │ │ └── searchablespinner
│ │ │ │ ├── interfaces
│ │ │ │ ├── IStatusListener.java
│ │ │ │ ├── ISpinnerSelectedView.java
│ │ │ │ └── OnItemSelectedListener.java
│ │ │ │ ├── tools
│ │ │ │ ├── UITools.java
│ │ │ │ └── EditCursorColor.java
│ │ │ │ ├── SelectedView.java
│ │ │ │ └── SearchableSpinner.java
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── attrs.xml
│ │ │ ├── drawable
│ │ │ │ └── spinner_drawable.xml
│ │ │ └── layout
│ │ │ │ ├── view_list.xml
│ │ │ │ └── view_searchable_spinner.xml
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── gr
│ │ │ └── escsoft
│ │ │ └── michaelprimez
│ │ │ └── searchablespinner
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── gr
│ │ └── escsoft
│ │ └── michaelprimez
│ │ └── searchablespinner
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── searchablespinner.gif
├── .gitignore
├── gradle.properties
├── README.md
├── .idea
└── markdown-navigator.xml
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/searchablespinner/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':searchablespinner'
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/searchablespinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/searchablespinner.gif
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelprimez/searchablespinner/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SearchableSpinnerExamples
3 | Reset
4 | No Selection
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #BDBDBD
7 |
8 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/java/gr/escsoft/michaelprimez/searchablespinner/interfaces/IStatusListener.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner.interfaces;
2 |
3 | /**
4 | * Created by michael on 3/19/17.
5 | */
6 |
7 | public interface IStatusListener {
8 | void spinnerIsOpening();
9 | void spinnerIsClosing();
10 | }
11 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SearchableSpinner
3 | Done Search
4 | Start search
5 | Search...
6 | No Items Found
7 |
8 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/java/gr/escsoft/michaelprimez/searchablespinner/interfaces/ISpinnerSelectedView.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner.interfaces;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Created by michael on 2/12/17.
7 | */
8 |
9 | public interface ISpinnerSelectedView {
10 | View getNoSelectionView();
11 | View getSelectedView(int position);
12 | }
13 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/java/gr/escsoft/michaelprimez/searchablespinner/interfaces/OnItemSelectedListener.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner.interfaces;
2 |
3 | import android.view.View;
4 | import android.widget.AdapterView;
5 |
6 | /**
7 | * Created by michael on 1/14/17.
8 | */
9 |
10 | public interface OnItemSelectedListener {
11 | void onItemSelected(View view, int position, long id);
12 | void onNothingSelected();
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/escsoft/michaelprimez/searchablespinnerexamples/SearchableSpinnerApp.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinnerexamples;
2 |
3 | import android.app.Application;
4 |
5 | import com.facebook.stetho.Stetho;
6 |
7 | /**
8 | * Created by michael on 1/8/17.
9 | */
10 |
11 | public class SearchableSpinnerApp extends Application {
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 | Stetho.initializeWithDefaults(this);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/java/gr/escsoft/michaelprimez/searchablespinner/tools/UITools.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner.tools;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Created by michael on 12/31/16.
7 | */
8 |
9 | public class UITools {
10 |
11 | private UITools() { }
12 |
13 | public static int dpToPx(Context context, float dp) {
14 | final float scale = context.getResources().getDisplayMetrics().density;
15 | return Math.round(dp * scale);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/test/java/gr/escsoft/michaelprimez/searchablespinnerexamples/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinnerexamples;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/searchablespinner/src/test/java/gr/escsoft/michaelprimez/searchablespinner/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/searchablespinner/src/main/res/drawable/spinner_drawable.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/searchablespinner/src/androidTest/java/gr/escsoft/michaelprimez/searchablespinner/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("gr.escsoft.michaelprimez.searchablespinner.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/gr/escsoft/michaelprimez/searchablespinnerexamples/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinnerexamples;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("gr.escsoft.michaelprimez.searchablespinnerexamples", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_list_no_selection_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
21 |
--------------------------------------------------------------------------------
/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 /Users/michael/Library/Android/sdk/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/searchablespinner/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 /Users/michael/Library/Android/sdk/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/res/layout/view_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
19 |
26 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion '26.0.2'
6 | defaultConfig {
7 | applicationId "gr.escsoft.michaelprimez.searchablespinnerexamples"
8 | minSdkVersion 21
9 | targetSdkVersion 26
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.facebook.stetho:stetho:1.4.1'
28 | compile 'com.android.support:appcompat-v7:26.1.0'
29 | compile 'com.android.support:design:26.1.0'
30 | compile project(path: ':searchablespinner')
31 | compile 'gr.escsoft.michaelprimez.revealedittext:RevealEditText:1.0.2'
32 | compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
33 | testCompile 'junit:junit:4.12'
34 | }
35 |
--------------------------------------------------------------------------------
/searchablespinner/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | ext {
4 | PUBLISH_GROUP_ID = 'gr.escsoft.michaelprimez.searchablespinner'
5 | PUBLISH_ARTIFACT_ID = 'SearchableSpinner'
6 | PUBLISH_VERSION = '1.0.9'
7 | }
8 |
9 | android {
10 | compileSdkVersion 26
11 | buildToolsVersion '26.0.2'
12 |
13 | defaultConfig {
14 | minSdkVersion 21
15 | targetSdkVersion 26
16 | versionCode 1
17 | versionName "1.0.9"
18 |
19 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
20 |
21 | }
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | }
29 |
30 | dependencies {
31 | compile fileTree(dir: 'libs', include: ['*.jar'])
32 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
33 | exclude group: 'com.android.support', module: 'support-annotations'
34 | })
35 | compile 'com.android.support:appcompat-v7:26.1.0'
36 | compile 'com.facebook.stetho:stetho:1.5.0'
37 | compile 'com.android.support:design:26.1.0'
38 | compile 'com.android.support:cardview-v7:26.1.0'
39 | compile 'com.joanzapata.iconify:android-iconify-material:2.2.2' // (v2.0.0)
40 | testCompile 'junit:junit:4.12'
41 | }
42 |
43 | apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle'
44 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/java/gr/escsoft/michaelprimez/searchablespinner/tools/EditCursorColor.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner.tools;
2 |
3 | import android.content.Context;
4 | import android.graphics.PorterDuff;
5 | import android.graphics.drawable.Drawable;
6 | import android.os.Build;
7 | import android.widget.EditText;
8 | import android.widget.TextView;
9 |
10 | import java.lang.reflect.Field;
11 |
12 | /**
13 | * Created by michael on 12/31/16.
14 | */
15 |
16 | public class EditCursorColor {
17 |
18 | private EditCursorColor() { }
19 |
20 | public static void setCursorColor(EditText editText, int color) {
21 | try {
22 | final Field drawableResField = TextView.class.getDeclaredField("mCursorDrawableRes");
23 | drawableResField.setAccessible(true);
24 | final Drawable drawable = getDrawable(editText.getContext(), drawableResField.getInt(editText));
25 | if (drawable == null) {
26 | return;
27 | }
28 | drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
29 | final Object drawableFieldOwner;
30 | final Class> drawableFieldClass;
31 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
32 | drawableFieldOwner = editText;
33 | drawableFieldClass = TextView.class;
34 | } else {
35 | final Field editorField = TextView.class.getDeclaredField("mEditor");
36 | editorField.setAccessible(true);
37 | drawableFieldOwner = editorField.get(editText);
38 | drawableFieldClass = drawableFieldOwner.getClass();
39 | }
40 | final Field drawableField = drawableFieldClass.getDeclaredField("mCursorDrawable");
41 | drawableField.setAccessible(true);
42 | drawableField.set(drawableFieldOwner, new Drawable[] {drawable, drawable});
43 | } catch (Exception ignored) {
44 | }
45 | }
46 |
47 | private static Drawable getDrawable(Context context, int id) {
48 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
49 | return context.getResources().getDrawable(id);
50 | } else {
51 | return context.getDrawable(id);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/java/gr/escsoft/michaelprimez/searchablespinner/SelectedView.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 | import android.support.annotation.IdRes;
6 | import android.view.View;
7 |
8 | /**
9 | * Created by michael on 1/14/17.
10 | */
11 |
12 | public class SelectedView implements Parcelable {
13 | private View mView;
14 | private int mPosition;
15 | private @IdRes long mId;
16 |
17 | public SelectedView(View view, int position, @IdRes long id) {
18 | mView = view;
19 | mPosition = position;
20 | mId = id;
21 | }
22 |
23 | public View getView() {
24 | return mView;
25 | }
26 |
27 | public void setView(View view) {
28 | mView = view;
29 | }
30 |
31 | public int getPosition() {
32 | return mPosition;
33 | }
34 |
35 | public void setPosition(int position) {
36 | mPosition = position;
37 | }
38 |
39 | public long getId() {
40 | return mId;
41 | }
42 |
43 | public void setId(@IdRes long id) {
44 | mId = id;
45 | }
46 |
47 | @Override
48 | public boolean equals(Object o) {
49 | if (this == o) return true;
50 | if (o == null || getClass() != o.getClass()) return false;
51 |
52 | SelectedView that = (SelectedView) o;
53 |
54 | if (mPosition != that.mPosition) return false;
55 | if (mId != that.mId) return false;
56 | return mView != null ? mView.equals(that.mView) : that.mView == null;
57 |
58 | }
59 |
60 | @Override
61 | public int hashCode() {
62 | int result = mView != null ? mView.hashCode() : 0;
63 | result = 31 * result + mPosition;
64 | result = 31 * result + (int) (mId ^ (mId >>> 32));
65 | return result;
66 | }
67 |
68 | @Override
69 | public int describeContents() {
70 | return 0;
71 | }
72 |
73 | public static final Creator CREATOR = new Creator() {
74 | @Override
75 | public SelectedView createFromParcel(Parcel in) {
76 | return new SelectedView(in);
77 | }
78 |
79 | @Override
80 | public SelectedView[] newArray(int size) {
81 | return new SelectedView[size];
82 | }
83 | };
84 |
85 | protected SelectedView(Parcel in) {
86 | mPosition = in.readInt();
87 | mId = in.readLong();
88 | }
89 |
90 | @Override
91 | public void writeToParcel(Parcel dest, int flags) {
92 | dest.writeInt(mPosition);
93 | dest.writeLong(mId);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [  ](https://bintray.com/michaelprimez/maven/SearchableSpinner/_latestVersion)
4 |
5 | [](https://android-arsenal.com/details/1/5120)
6 |
7 | # Searchable Spinner
8 |
9 | 
10 |
11 | ## Usage
12 |
13 | Add the dependency to your build.gradle.
14 | ```xml
15 | implementation 'gr.escsoft.michaelprimez.searchablespinner:SearchableSpinner:1.0.9'
16 | ```
17 |
18 | Usage on layout
19 | ```xml
20 |
37 | ```
38 | ## Contributing
39 | 1. Fork it!
40 | 2. Create your feature branch: `git checkout -b my-new-feature`
41 | 3. Commit your changes: `git commit -am 'Add some feature'`
42 | 4. Push to the branch: `git push origin my-new-feature`
43 | 5. Submit a pull request :D ## History
44 |
45 | ## History
46 | #### Version 1.0.3
47 | * added [get, set] selected item
48 |
49 | #### Version 1.0.6
50 | * added Touch outside to dismiss
51 |
52 | #### Version 1.0.7
53 | * added a status listener
54 |
55 | #### Version 1.0.8
56 | * added divider and divider height
57 |
58 | # LICENSE
59 |
60 | ```
61 | Copyright (C) 2017 Michael Keskinidis
62 |
63 | Licensed under the Apache License, Version 2.0 (the "License");
64 | you may not use this file except in compliance with the License.
65 | You may obtain a copy of the License at
66 |
67 | http://www.apache.org/licenses/LICENSE-2.0
68 |
69 | Unless required by applicable law or agreed to in writing, software
70 | distributed under the License is distributed on an "AS IS" BASIS,
71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72 | See the License for the specific language governing permissions and
73 | limitations under the License.
74 | ```
75 |
76 |
77 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.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 |
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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
30 |
31 |
49 |
50 |
69 |
70 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/escsoft/michaelprimez/searchablespinnerexamples/SimpleListAdapter.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinnerexamples;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.text.TextUtils;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.BaseAdapter;
9 | import android.widget.Filter;
10 | import android.widget.Filterable;
11 | import android.widget.ImageView;
12 | import android.widget.TextView;
13 |
14 | import com.amulyakhare.textdrawable.TextDrawable;
15 | import com.amulyakhare.textdrawable.util.ColorGenerator;
16 |
17 | import java.util.ArrayList;
18 |
19 | import gr.escsoft.michaelprimez.revealedittext.tools.UITools;
20 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.ISpinnerSelectedView;
21 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.OnItemSelectedListener;
22 |
23 | /**
24 | * Created by michael on 1/8/17.
25 | */
26 |
27 | public class SimpleListAdapter extends BaseAdapter implements Filterable, ISpinnerSelectedView {
28 |
29 | private Context mContext;
30 | private ArrayList mBackupStrings;
31 | private ArrayList mStrings;
32 | private StringFilter mStringFilter = new StringFilter();
33 |
34 | public SimpleListAdapter(Context context, ArrayList strings) {
35 | mContext = context;
36 | mStrings = strings;
37 | mBackupStrings = strings;
38 | }
39 |
40 | @Override
41 | public int getCount() {
42 | return mStrings == null ? 0 : mStrings.size() + 1;
43 | }
44 |
45 | @Override
46 | public Object getItem(int position) {
47 | if (mStrings != null && position > 0)
48 | return mStrings.get(position - 1);
49 | else
50 | return null;
51 | }
52 |
53 | @Override
54 | public long getItemId(int position) {
55 | if (mStrings == null && position > 0)
56 | return mStrings.get(position).hashCode();
57 | else
58 | return -1;
59 | }
60 |
61 | @Override
62 | public View getView(int position, View convertView, ViewGroup parent) {
63 | View view = null;
64 | if (position == 0) {
65 | view = getNoSelectionView();
66 | } else {
67 | view = View.inflate(mContext, R.layout.view_list_item, null);
68 | ImageView letters = (ImageView) view.findViewById(R.id.ImgVw_Letters);
69 | TextView dispalyName = (TextView) view.findViewById(R.id.TxtVw_DisplayName);
70 | letters.setImageDrawable(getTextDrawable(mStrings.get(position-1)));
71 | dispalyName.setText(mStrings.get(position-1));
72 | }
73 | return view;
74 | }
75 |
76 | @Override
77 | public View getSelectedView(int position) {
78 | View view = null;
79 | if (position == 0) {
80 | view = getNoSelectionView();
81 | } else {
82 | view = View.inflate(mContext, R.layout.view_list_item, null);
83 | ImageView letters = (ImageView) view.findViewById(R.id.ImgVw_Letters);
84 | TextView dispalyName = (TextView) view.findViewById(R.id.TxtVw_DisplayName);
85 | letters.setImageDrawable(getTextDrawable(mStrings.get(position-1)));
86 | dispalyName.setText(mStrings.get(position-1));
87 | }
88 | return view;
89 | }
90 |
91 | @Override
92 | public View getNoSelectionView() {
93 | View view = View.inflate(mContext, R.layout.view_list_no_selection_item, null);
94 | return view;
95 | }
96 |
97 | private TextDrawable getTextDrawable(String displayName) {
98 | TextDrawable drawable = null;
99 | if (!TextUtils.isEmpty(displayName)) {
100 | int color2 = ColorGenerator.MATERIAL.getColor(displayName);
101 | drawable = TextDrawable.builder()
102 | .beginConfig()
103 | .width(UITools.dpToPx(mContext, 32))
104 | .height(UITools.dpToPx(mContext, 32))
105 | .textColor(Color.WHITE)
106 | .toUpperCase()
107 | .endConfig()
108 | .round()
109 | .build(displayName.substring(0, 1), color2);
110 | } else {
111 | drawable = TextDrawable.builder()
112 | .beginConfig()
113 | .width(UITools.dpToPx(mContext, 32))
114 | .height(UITools.dpToPx(mContext, 32))
115 | .endConfig()
116 | .round()
117 | .build("?", Color.GRAY);
118 | }
119 | return drawable;
120 | }
121 |
122 | @Override
123 | public Filter getFilter() {
124 | return mStringFilter;
125 | }
126 |
127 | public class StringFilter extends Filter {
128 |
129 | @Override
130 | protected FilterResults performFiltering(CharSequence constraint) {
131 | final FilterResults filterResults = new FilterResults();
132 | if (TextUtils.isEmpty(constraint)) {
133 | filterResults.count = mBackupStrings.size();
134 | filterResults.values = mBackupStrings;
135 | return filterResults;
136 | }
137 | final ArrayList filterStrings = new ArrayList<>();
138 | for (String text : mBackupStrings) {
139 | if (text.toLowerCase().contains(constraint)) {
140 | filterStrings.add(text);
141 | }
142 | }
143 | filterResults.count = filterStrings.size();
144 | filterResults.values = filterStrings;
145 | return filterResults;
146 | }
147 |
148 | @Override
149 | protected void publishResults(CharSequence constraint, FilterResults results) {
150 | mStrings = (ArrayList) results.values;
151 | notifyDataSetChanged();
152 | }
153 | }
154 |
155 | private class ItemView {
156 | public ImageView mImageView;
157 | public TextView mTextView;
158 | }
159 |
160 | public enum ItemViewType {
161 | ITEM, NO_SELECTION_ITEM;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/escsoft/michaelprimez/searchablespinnerexamples/SimpleArrayListAdapter.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinnerexamples;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.text.TextUtils;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ArrayAdapter;
9 | import android.widget.BaseAdapter;
10 | import android.widget.Filter;
11 | import android.widget.Filterable;
12 | import android.widget.ImageView;
13 | import android.widget.TextView;
14 |
15 | import com.amulyakhare.textdrawable.TextDrawable;
16 | import com.amulyakhare.textdrawable.util.ColorGenerator;
17 |
18 | import java.util.ArrayList;
19 |
20 | import gr.escsoft.michaelprimez.revealedittext.tools.UITools;
21 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.ISpinnerSelectedView;
22 |
23 | /**
24 | * Created by michael on 1/8/17.
25 | */
26 |
27 | public class SimpleArrayListAdapter extends ArrayAdapter implements Filterable, ISpinnerSelectedView {
28 |
29 | private Context mContext;
30 | private ArrayList mBackupStrings;
31 | private ArrayList mStrings;
32 | private StringFilter mStringFilter = new StringFilter();
33 |
34 | public SimpleArrayListAdapter(Context context, ArrayList strings) {
35 | super(context, R.layout.view_list_item);
36 | mContext = context;
37 | mStrings = strings;
38 | mBackupStrings = strings;
39 | }
40 |
41 | @Override
42 | public int getCount() {
43 | return mStrings == null ? 0 : mStrings.size() + 1;
44 | }
45 |
46 | @Override
47 | public String getItem(int position) {
48 | if (mStrings != null && position > 0)
49 | return mStrings.get(position - 1);
50 | else
51 | return null;
52 | }
53 |
54 | @Override
55 | public long getItemId(int position) {
56 | if (mStrings == null && position > 0)
57 | return mStrings.get(position).hashCode();
58 | else
59 | return -1;
60 | }
61 |
62 | @Override
63 | public View getView(int position, View convertView, ViewGroup parent) {
64 | View view = null;
65 | if (position == 0) {
66 | view = getNoSelectionView();
67 | } else {
68 | view = View.inflate(mContext, R.layout.view_list_item, null);
69 | ImageView letters = (ImageView) view.findViewById(R.id.ImgVw_Letters);
70 | TextView dispalyName = (TextView) view.findViewById(R.id.TxtVw_DisplayName);
71 | letters.setImageDrawable(getTextDrawable(mStrings.get(position-1)));
72 | dispalyName.setText(mStrings.get(position-1));
73 | }
74 | return view;
75 | }
76 |
77 | @Override
78 | public View getSelectedView(int position) {
79 | View view = null;
80 | if (position == 0) {
81 | view = getNoSelectionView();
82 | } else {
83 | view = View.inflate(mContext, R.layout.view_list_item, null);
84 | ImageView letters = (ImageView) view.findViewById(R.id.ImgVw_Letters);
85 | TextView dispalyName = (TextView) view.findViewById(R.id.TxtVw_DisplayName);
86 | letters.setImageDrawable(getTextDrawable(mStrings.get(position-1)));
87 | dispalyName.setText(mStrings.get(position-1));
88 | }
89 | return view;
90 | }
91 |
92 | @Override
93 | public View getNoSelectionView() {
94 | View view = View.inflate(mContext, R.layout.view_list_no_selection_item, null);
95 | return view;
96 | }
97 |
98 | private TextDrawable getTextDrawable(String displayName) {
99 | TextDrawable drawable = null;
100 | if (!TextUtils.isEmpty(displayName)) {
101 | int color2 = ColorGenerator.MATERIAL.getColor(displayName);
102 | drawable = TextDrawable.builder()
103 | .beginConfig()
104 | .width(UITools.dpToPx(mContext, 32))
105 | .height(UITools.dpToPx(mContext, 32))
106 | .textColor(Color.WHITE)
107 | .toUpperCase()
108 | .endConfig()
109 | .round()
110 | .build(displayName.substring(0, 1), color2);
111 | } else {
112 | drawable = TextDrawable.builder()
113 | .beginConfig()
114 | .width(UITools.dpToPx(mContext, 32))
115 | .height(UITools.dpToPx(mContext, 32))
116 | .endConfig()
117 | .round()
118 | .build("?", Color.GRAY);
119 | }
120 | return drawable;
121 | }
122 |
123 | @Override
124 | public Filter getFilter() {
125 | return mStringFilter;
126 | }
127 |
128 | public class StringFilter extends Filter {
129 |
130 | @Override
131 | protected FilterResults performFiltering(CharSequence constraint) {
132 | final FilterResults filterResults = new FilterResults();
133 | if (TextUtils.isEmpty(constraint)) {
134 | filterResults.count = mBackupStrings.size();
135 | filterResults.values = mBackupStrings;
136 | return filterResults;
137 | }
138 | final ArrayList filterStrings = new ArrayList<>();
139 | for (String text : mBackupStrings) {
140 | if (text.toLowerCase().contains(constraint)) {
141 | filterStrings.add(text);
142 | }
143 | }
144 | filterResults.count = filterStrings.size();
145 | filterResults.values = filterStrings;
146 | return filterResults;
147 | }
148 |
149 | @Override
150 | protected void publishResults(CharSequence constraint, FilterResults results) {
151 | mStrings = (ArrayList) results.values;
152 | notifyDataSetChanged();
153 | }
154 | }
155 |
156 | private class ItemView {
157 | public ImageView mImageView;
158 | public TextView mTextView;
159 | }
160 |
161 | public enum ItemViewType {
162 | ITEM, NO_SELECTION_ITEM;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/res/layout/view_searchable_spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
20 |
29 |
30 |
33 |
34 |
40 |
41 |
51 |
52 |
56 |
57 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
92 |
93 |
96 |
97 |
102 |
103 |
114 |
115 |
119 |
120 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/escsoft/michaelprimez/searchablespinnerexamples/MainActivity.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinnerexamples;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.Toolbar;
6 | import android.view.MotionEvent;
7 | import android.view.View;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import android.widget.Toast;
11 |
12 | import java.util.ArrayList;
13 |
14 | import gr.escsoft.michaelprimez.searchablespinner.SearchableSpinner;
15 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.IStatusListener;
16 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.OnItemSelectedListener;
17 |
18 | public class MainActivity extends AppCompatActivity {
19 |
20 | private SearchableSpinner mSearchableSpinner;
21 | private SearchableSpinner mSearchableSpinner1;
22 | private SearchableSpinner mSearchableSpinner2;
23 | private SearchableSpinner mSearchableSpinner3;
24 | private SimpleListAdapter mSimpleListAdapter;
25 | private SimpleArrayListAdapter mSimpleArrayListAdapter;
26 | private final ArrayList mStrings = new ArrayList<>();
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.activity_main);
32 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
33 | setSupportActionBar(toolbar);
34 |
35 | initListValues();
36 | mSimpleListAdapter = new SimpleListAdapter(this, mStrings);
37 | mSimpleArrayListAdapter = new SimpleArrayListAdapter(this, mStrings);
38 |
39 | mSearchableSpinner = (SearchableSpinner) findViewById(R.id.SearchableSpinner);
40 | mSearchableSpinner.setAdapter(mSimpleArrayListAdapter);
41 | mSearchableSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
42 | mSearchableSpinner.setStatusListener(new IStatusListener() {
43 | @Override
44 | public void spinnerIsOpening() {
45 | mSearchableSpinner1.hideEdit();
46 | mSearchableSpinner2.hideEdit();
47 | }
48 |
49 | @Override
50 | public void spinnerIsClosing() {
51 |
52 | }
53 | });
54 |
55 | mSearchableSpinner1 = (SearchableSpinner) findViewById(R.id.SearchableSpinner1);
56 | mSearchableSpinner1.setAdapter(mSimpleListAdapter);
57 | mSearchableSpinner1.setOnItemSelectedListener(mOnItemSelectedListener);
58 | mSearchableSpinner1.setStatusListener(new IStatusListener() {
59 | @Override
60 | public void spinnerIsOpening() {
61 | mSearchableSpinner.hideEdit();
62 | mSearchableSpinner2.hideEdit();
63 | }
64 |
65 | @Override
66 | public void spinnerIsClosing() {
67 |
68 | }
69 | });
70 |
71 | mSearchableSpinner2 = (SearchableSpinner) findViewById(R.id.SearchableSpinner2);
72 | mSearchableSpinner2.setAdapter(mSimpleListAdapter);
73 | mSearchableSpinner2.setOnItemSelectedListener(mOnItemSelectedListener);
74 | mSearchableSpinner2.setStatusListener(new IStatusListener() {
75 | @Override
76 | public void spinnerIsOpening() {
77 | mSearchableSpinner.hideEdit();
78 | mSearchableSpinner1.hideEdit();
79 | }
80 |
81 | @Override
82 | public void spinnerIsClosing() {
83 |
84 | }
85 | });
86 |
87 | mSearchableSpinner3 = (SearchableSpinner) findViewById(R.id.SearchableSpinner3);
88 | mSearchableSpinner3.setAdapter(mSimpleListAdapter);
89 | mSearchableSpinner3.setOnItemSelectedListener(mOnItemSelectedListener);
90 | mSearchableSpinner3.setStatusListener(new IStatusListener() {
91 | @Override
92 | public void spinnerIsOpening() {
93 | mSearchableSpinner.hideEdit();
94 | mSearchableSpinner3.hideEdit();
95 | }
96 |
97 | @Override
98 | public void spinnerIsClosing() {
99 |
100 | }
101 | });
102 | }
103 |
104 | @Override
105 | public boolean onTouchEvent(MotionEvent event) {
106 | if (!mSearchableSpinner.isInsideSearchEditText(event)) {
107 | mSearchableSpinner.hideEdit();
108 | }
109 | if (!mSearchableSpinner1.isInsideSearchEditText(event)) {
110 | mSearchableSpinner1.hideEdit();
111 | }
112 | if (!mSearchableSpinner2.isInsideSearchEditText(event)) {
113 | mSearchableSpinner2.hideEdit();
114 | }
115 | return super.onTouchEvent(event);
116 | }
117 |
118 | private OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
119 | @Override
120 | public void onItemSelected(View view, int position, long id) {
121 | Toast.makeText(MainActivity.this, "Item on position " + position + " : " + mSimpleListAdapter.getItem(position) + " Selected", Toast.LENGTH_SHORT).show();
122 | }
123 |
124 | @Override
125 | public void onNothingSelected() {
126 | Toast.makeText(MainActivity.this, "Nothing Selected", Toast.LENGTH_SHORT).show();
127 | }
128 | };
129 |
130 | private void initListValues() {
131 | mStrings.add("Brigida Kurz");
132 | mStrings.add("Tracy Mckim");
133 | mStrings.add("Iesha Davids");
134 | mStrings.add("Ozella Provenza");
135 | mStrings.add("Florentina Carriere");
136 | mStrings.add("Geri Eiler");
137 | mStrings.add("Tammara Belgrave");
138 | mStrings.add("Ashton Ridinger");
139 | mStrings.add("Jodee Dawkins");
140 | mStrings.add("Florine Cruzan");
141 | mStrings.add("Latia Stead");
142 | mStrings.add("Kai Urbain");
143 | mStrings.add("Liza Chi");
144 | mStrings.add("Clayton Laprade");
145 | mStrings.add("Wilfredo Mooney");
146 | mStrings.add("Roseline Cain");
147 | mStrings.add("Chadwick Gauna");
148 | mStrings.add("Carmela Bourn");
149 | mStrings.add("Valeri Dedios");
150 | mStrings.add("Calista Mcneese");
151 | mStrings.add("Willard Cuccia");
152 | mStrings.add("Ngan Blakey");
153 | mStrings.add("Reina Medlen");
154 | mStrings.add("Fabian Steenbergen");
155 | mStrings.add("Edmond Pine");
156 | mStrings.add("Teri Quesada");
157 | mStrings.add("Vernetta Fulgham");
158 | mStrings.add("Winnifred Kiefer");
159 | mStrings.add("Chiquita Lichty");
160 | mStrings.add("Elna Stiltner");
161 | mStrings.add("Carly Landon");
162 | mStrings.add("Hans Morford");
163 | mStrings.add("Shawanna Kapoor");
164 | mStrings.add("Thomasina Naron");
165 | mStrings.add("Melba Massi");
166 | mStrings.add("Sal Mangano");
167 | mStrings.add("Mika Weitzel");
168 | mStrings.add("Phylis France");
169 | mStrings.add("Illa Winzer");
170 | mStrings.add("Kristofer Boyden");
171 | mStrings.add("Idalia Cryan");
172 | mStrings.add("Jenni Sousa");
173 | mStrings.add("Eda Forkey");
174 | mStrings.add("Birgit Rispoli");
175 | mStrings.add("Janiece Mcguffey");
176 | mStrings.add("Barton Busick");
177 | mStrings.add("Gerald Westerman");
178 | mStrings.add("Shalanda Baran");
179 | mStrings.add("Margherita Pazos");
180 | mStrings.add("Yuk Fitts");
181 | }
182 |
183 | @Override
184 | public boolean onCreateOptionsMenu(Menu menu) {
185 | // Inflate the menu; this adds items to the action bar if it is present.
186 | getMenuInflater().inflate(R.menu.menu_main, menu);
187 | return true;
188 | }
189 |
190 | @Override
191 | public boolean onOptionsItemSelected(MenuItem item) {
192 | // Handle action bar item clicks here. The action bar will
193 | // automatically handle clicks on the Home/Up button, so long
194 | // as you specify a parent activity in AndroidManifest.xml.
195 | int id = item.getItemId();
196 |
197 | //noinspection SimplifiableIfStatement
198 | if (id == R.id.action_reset) {
199 | mSearchableSpinner.setSelectedItem(0);
200 | mSearchableSpinner1.setSelectedItem(0);
201 | mSearchableSpinner2.setSelectedItem(0);
202 | return true;
203 | }
204 |
205 | return super.onOptionsItemSelected(item);
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/searchablespinner/src/main/java/gr/escsoft/michaelprimez/searchablespinner/SearchableSpinner.java:
--------------------------------------------------------------------------------
1 | package gr.escsoft.michaelprimez.searchablespinner;
2 |
3 | import android.animation.Animator;
4 | import android.content.Context;
5 | import android.content.res.Configuration;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Color;
8 | import android.graphics.Rect;
9 | import android.graphics.drawable.Drawable;
10 | import android.os.Parcel;
11 | import android.os.Parcelable;
12 | import android.support.annotation.AttrRes;
13 | import android.support.annotation.ColorInt;
14 | import android.support.annotation.Dimension;
15 | import android.support.annotation.DrawableRes;
16 | import android.support.annotation.NonNull;
17 | import android.support.annotation.Nullable;
18 | import android.support.annotation.Px;
19 | import android.support.annotation.StyleRes;
20 | import android.support.v4.content.ContextCompat;
21 | import android.support.v7.widget.AppCompatEditText;
22 | import android.support.v7.widget.CardView;
23 | import android.text.Editable;
24 | import android.text.TextUtils;
25 | import android.text.TextWatcher;
26 | import android.util.AttributeSet;
27 | import android.util.DisplayMetrics;
28 | import android.util.Log;
29 | import android.view.LayoutInflater;
30 | import android.view.MotionEvent;
31 | import android.view.View;
32 | import android.view.ViewAnimationUtils;
33 | import android.view.ViewGroup;
34 | import android.view.ViewTreeObserver;
35 | import android.view.WindowManager;
36 | import android.view.inputmethod.EditorInfo;
37 | import android.view.inputmethod.InputMethodManager;
38 | import android.widget.Adapter;
39 | import android.widget.AdapterView;
40 | import android.widget.BaseAdapter;
41 | import android.widget.Filterable;
42 | import android.widget.LinearLayout;
43 | import android.widget.ListAdapter;
44 | import android.widget.ListView;
45 | import android.widget.PopupWindow;
46 | import android.widget.RelativeLayout;
47 | import android.widget.TextView;
48 |
49 | import com.joanzapata.iconify.Iconify;
50 | import com.joanzapata.iconify.fonts.MaterialModule;
51 | import com.joanzapata.iconify.widget.IconTextView;
52 |
53 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.ISpinnerSelectedView;
54 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.IStatusListener;
55 | import gr.escsoft.michaelprimez.searchablespinner.interfaces.OnItemSelectedListener;
56 | import gr.escsoft.michaelprimez.searchablespinner.tools.EditCursorColor;
57 | import gr.escsoft.michaelprimez.searchablespinner.tools.UITools;
58 |
59 | /**
60 | * Created by michael on 1/8/17.
61 | */
62 |
63 | public class SearchableSpinner extends RelativeLayout implements View.OnClickListener {
64 |
65 | private static final int DefaultElevation = 16;
66 | private static final int DefaultAnimationDuration = 400;
67 | private ViewState mViewState = ViewState.ShowingRevealedLayout;
68 | private IStatusListener mStatusListener;
69 | private CardView mRevealContainerCardView;
70 | private LinearLayout mRevealItem;
71 | private IconTextView mStartSearchImageView;
72 |
73 | private CardView mContainerCardView;
74 | private AppCompatEditText mSearchEditText;
75 | private IconTextView mDoneSearchImageView;
76 | private LinearLayout mSpinnerListContainer;
77 | private PopupWindow mPopupWindow;
78 | private ListView mSpinnerListView;
79 | private TextView mEmptyTextView;
80 |
81 | private Context mContext;
82 | private OnItemSelectedListener mOnItemSelected;
83 | private SelectedView mCurrSelectedView;
84 | private int mScreenHeightPixels;
85 | private int mScreenWidthPixels;
86 |
87 | /* Attributes */
88 | private @ColorInt int mRevealViewBackgroundColor;
89 | private @ColorInt int mStartEditTintColor;
90 | private @ColorInt int mEditViewBackgroundColor;
91 | private @ColorInt int mEditViewTextColor;
92 | private @ColorInt int mDoneEditTintColor;
93 | private @ColorInt int mBoarderColor;
94 | private Drawable mListItemDivider;
95 | private @Px int mBordersSize;
96 | private @Px int mExpandSize;
97 | private @Px int mListDividerSize;
98 | private boolean mShowBorders;
99 | private boolean mKeepLastSearch;
100 | private String mRevealEmptyText;
101 | private String mSearchHintText;
102 | private String mNoItemsFoundText;
103 | private int mAnimDuration;
104 |
105 | public enum ViewState {
106 | ShowingRevealedLayout,
107 | ShowingEditLayout,
108 | ShowingAnimation
109 | }
110 |
111 | static {
112 | Iconify.with(new MaterialModule());
113 | }
114 |
115 | public SearchableSpinner(@NonNull Context context) {
116 | this(context, null);
117 | }
118 |
119 | public SearchableSpinner(@NonNull Context context, @Nullable AttributeSet attrs) {
120 | this(context, attrs, -1);
121 | }
122 |
123 | public SearchableSpinner(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
124 | this(context, attrs, defStyleAttr, 0);
125 | }
126 |
127 | public SearchableSpinner(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
128 | super(context, attrs, defStyleAttr, defStyleRes);
129 | mContext = context;
130 | getAttributeSet(attrs, defStyleAttr, defStyleRes);
131 |
132 | final LayoutInflater factory = LayoutInflater.from(context);
133 | factory.inflate(R.layout.view_searchable_spinner, this, true);
134 |
135 | mSpinnerListContainer = (LinearLayout) factory.inflate(R.layout.view_list, this, false);
136 | mSpinnerListView = (ListView) mSpinnerListContainer.findViewById(R.id.LstVw_SpinnerListView);
137 | if (mListItemDivider != null) {
138 | mSpinnerListView.setDivider(mListItemDivider);
139 | mSpinnerListView.setDividerHeight(mListDividerSize);
140 | }
141 | mEmptyTextView = (TextView) mSpinnerListContainer.findViewById(R.id.TxtVw_EmptyText);
142 | mSpinnerListView.setEmptyView(mEmptyTextView);
143 | }
144 |
145 | private void getAttributeSet(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
146 | if (attrs != null) {
147 | try {
148 | TypedArray attributes = mContext.getTheme().obtainStyledAttributes(attrs, R.styleable.SearchableSpinner, defStyleAttr, defStyleRes);
149 | mRevealViewBackgroundColor = attributes.getColor(R.styleable.SearchableSpinner_RevealViewBackgroundColor, Color.WHITE);
150 | mStartEditTintColor = attributes.getColor(R.styleable.SearchableSpinner_StartSearchTintColor, Color.GRAY);
151 | mEditViewBackgroundColor = attributes.getColor(R.styleable.SearchableSpinner_SearchViewBackgroundColor, Color.WHITE);
152 | mEditViewTextColor = attributes.getColor(R.styleable.SearchableSpinner_SearchViewTextColor, Color.BLACK);
153 | mDoneEditTintColor = attributes.getColor(R.styleable.SearchableSpinner_DoneSearchTintColor, Color.GRAY);
154 | mBordersSize = attributes.getDimensionPixelSize(R.styleable.SearchableSpinner_BordersSize, 4);
155 | mExpandSize = attributes.getDimensionPixelSize(R.styleable.SearchableSpinner_SpinnerExpandHeight, 0);
156 | mShowBorders = attributes.getBoolean(R.styleable.SearchableSpinner_ShowBorders, false);
157 | mBoarderColor = attributes.getColor(R.styleable.SearchableSpinner_BoarderColor, Color.GRAY);
158 | mAnimDuration = attributes.getColor(R.styleable.SearchableSpinner_AnimDuration, DefaultAnimationDuration);
159 | mKeepLastSearch = attributes.getBoolean(R.styleable.SearchableSpinner_KeepLastSearch, false);
160 | mRevealEmptyText = attributes.getString(R.styleable.SearchableSpinner_RevealEmptyText);
161 | mSearchHintText = attributes.getString(R.styleable.SearchableSpinner_SearchHintText);
162 | mNoItemsFoundText = attributes.getString(R.styleable.SearchableSpinner_NoItemsFoundText);
163 | mListItemDivider = attributes.getDrawable(R.styleable.SearchableSpinner_ItemsDivider);
164 | mListDividerSize = attributes.getDimensionPixelSize(R.styleable.SearchableSpinner_DividerHeight, 0);
165 | } catch (UnsupportedOperationException e) {
166 | Log.e("SearchableSpinner", "getAttributeSet --> " + e.getLocalizedMessage());
167 | }
168 | }
169 | }
170 |
171 | @Override
172 | protected void onFinishInflate() {
173 | super.onFinishInflate();
174 | mRevealContainerCardView = (CardView) findViewById(R.id.CrdVw_RevealContainer);
175 | mRevealContainerCardView.setOnClickListener(mOnRevelViewClickListener);
176 | mRevealItem = (LinearLayout) findViewById(R.id.FrmLt_SelectedItem);
177 | mStartSearchImageView = (IconTextView) findViewById(R.id.ImgVw_StartSearch);
178 |
179 | mContainerCardView = (CardView) findViewById(R.id.CrdVw_Container);
180 | mSearchEditText = (AppCompatEditText) findViewById(R.id.EdtTxt_SearchEditText);
181 | mDoneSearchImageView = (IconTextView) findViewById(R.id.ImgVw_DoneSearch);
182 | init();
183 | }
184 |
185 | @Override
186 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
187 | getScreenSize();
188 | int width = View.MeasureSpec.getSize(widthMeasureSpec);
189 | if (mShowBorders) { // + 4 because of card layout_margin in the view_searchable_spinner.xml
190 | width -= UITools.dpToPx(mContext, (mBordersSize + 4));
191 | } else {
192 | width -= UITools.dpToPx(mContext, 8);
193 | }
194 | mPopupWindow.setWidth(width);
195 | if (mExpandSize <= 0) {
196 | mPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
197 | } else{
198 | mPopupWindow.setHeight(heightMeasureSpec);
199 | }
200 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
201 | }
202 |
203 | @Override
204 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
205 |
206 | super.onLayout(changed, l, t, r, b);
207 | }
208 |
209 | @Override
210 | protected void onScrollChanged(int l, int t, int oldl, int oldt) {
211 | requestLayout();
212 | super.onScrollChanged(l, t, oldl, oldt);
213 | }
214 |
215 | private void init() {
216 | setupColors();
217 | setupList();
218 | mSearchEditText.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
219 | mStartSearchImageView.setOnClickListener(this);
220 | mDoneSearchImageView.setOnClickListener(this);
221 | mSearchEditText.addTextChangedListener(mTextWatcher);
222 |
223 | mPopupWindow = new PopupWindow(mContext);
224 | mPopupWindow.setContentView(mSpinnerListContainer);
225 | mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
226 | mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
227 | mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
228 | @Override
229 | public void onDismiss() {
230 | hideEdit();
231 | }
232 | });
233 | mPopupWindow.setFocusable(false);
234 | mPopupWindow.setElevation(DefaultElevation);
235 | mPopupWindow.setBackgroundDrawable(ContextCompat.getDrawable(mContext, R.drawable.spinner_drawable));
236 |
237 | mSpinnerListView.setOnItemClickListener(mOnItemSelectedListener);
238 | if (mCurrSelectedView == null) {
239 | if (!TextUtils.isEmpty(mSearchHintText)) {
240 | mSearchEditText.setHint(mSearchHintText);
241 | }
242 | if (!TextUtils.isEmpty(mNoItemsFoundText)) {
243 | mEmptyTextView.setText(mNoItemsFoundText);
244 | }
245 | if (mCurrSelectedView == null && !TextUtils.isEmpty(mRevealEmptyText)) {
246 | TextView textView = new TextView(mContext);
247 | textView.setText(mRevealEmptyText);
248 | mCurrSelectedView = new SelectedView(textView, -1, 0);
249 | mRevealItem.addView(textView);
250 | }
251 | } else {
252 | mSpinnerListView.performItemClick(mCurrSelectedView.getView(), mCurrSelectedView.getPosition(), mCurrSelectedView.getId());
253 | }
254 | clearAnimation();
255 | clearFocus();
256 | }
257 |
258 | private AdapterView.OnItemClickListener mOnItemSelectedListener = new AdapterView.OnItemClickListener() {
259 | @Override
260 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
261 | if (mCurrSelectedView == null) {
262 | Adapter adapter = parent.getAdapter();
263 | if (adapter instanceof ISpinnerSelectedView) {
264 | View selectedView = ((ISpinnerSelectedView) adapter).getSelectedView(position);
265 | mCurrSelectedView = new SelectedView(selectedView, position, selectedView.getId());
266 | } else {
267 | mCurrSelectedView = new SelectedView(view, position, id);
268 | }
269 | mSpinnerListView.setSelection(position);
270 | } else {
271 | Adapter adapter = parent.getAdapter();
272 | if (adapter instanceof ISpinnerSelectedView) {
273 | View selectedView = ((ISpinnerSelectedView) adapter).getSelectedView(position);
274 | mCurrSelectedView = new SelectedView(selectedView, position, selectedView.getId());
275 | } else {
276 | mCurrSelectedView.setView(view);
277 | mCurrSelectedView.setPosition(position);
278 | mCurrSelectedView.setId(id);
279 | }
280 | mSpinnerListView.setSelection(position);
281 | }
282 | if (mCurrSelectedView == null) {
283 | if (mOnItemSelected != null)
284 | mOnItemSelected.onNothingSelected();
285 | } else if (mCurrSelectedView != null) {
286 | mRevealItem.removeAllViews();
287 | mSpinnerListView.removeViewInLayout(mCurrSelectedView.getView());
288 | mRevealItem.addView(mCurrSelectedView.getView());
289 | ((BaseAdapter) mSpinnerListView.getAdapter()).notifyDataSetChanged();
290 | if (mOnItemSelected != null)
291 | mOnItemSelected.onItemSelected(mCurrSelectedView.getView(), mCurrSelectedView.getPosition(), mCurrSelectedView.getId());
292 | }
293 | hideEdit();
294 | }
295 | };
296 |
297 | public Object getSelectedItem() {
298 | if (mCurrSelectedView != null) {
299 | int position = mCurrSelectedView.getPosition();
300 | Adapter adapter = mSpinnerListView.getAdapter();
301 | if (adapter != null && adapter.getCount() > 0 && position >= 0) {
302 | return adapter.getItem(position);
303 | } else {
304 | return null;
305 | }
306 | }
307 | return null;
308 | }
309 |
310 | public void setSelectedItem(int position) {
311 | Adapter adapter = mSpinnerListView.getAdapter();
312 | if (adapter instanceof ISpinnerSelectedView) {
313 | View selectedView = ((ISpinnerSelectedView) adapter).getSelectedView(position);
314 | mCurrSelectedView = new SelectedView(selectedView, position, selectedView.getId());
315 | mSpinnerListView.setSelection(position);
316 | } else {
317 | TextView textView = new TextView(mContext);
318 | textView.setText(mRevealEmptyText);
319 | mCurrSelectedView = new SelectedView(textView, -1, 0);
320 | mRevealItem.addView(textView);
321 | }
322 | if (mCurrSelectedView == null) {
323 | if (mOnItemSelected != null)
324 | mOnItemSelected.onNothingSelected();
325 | } else if (mCurrSelectedView != null) {
326 | mRevealItem.removeAllViews();
327 | mSpinnerListView.removeViewInLayout(mCurrSelectedView.getView());
328 | mRevealItem.addView(mCurrSelectedView.getView());
329 | ((BaseAdapter) mSpinnerListView.getAdapter()).notifyDataSetChanged();
330 | if (mOnItemSelected != null)
331 | mOnItemSelected.onItemSelected(mCurrSelectedView.getView(), mCurrSelectedView.getPosition(), mCurrSelectedView.getId());
332 | }
333 | hideEdit();
334 | }
335 |
336 | public int getItemPosition(Object item) {
337 | if (item == null)
338 | return -1;
339 | Adapter adapter = mSpinnerListView.getAdapter();
340 | if (adapter != null) {
341 | for (int i = 0; i < adapter.getCount(); i++) {
342 | Object adpItem = adapter.getItem(i);
343 | if (adpItem != null && adpItem.equals(item)) {
344 | return i;
345 | }
346 | }
347 | }
348 | return -1;
349 | }
350 |
351 | public void setSelectedItem(Object item) {
352 | int itemPosition = getItemPosition(item);
353 | if (itemPosition >= 0) {
354 | setSelectedItem(itemPosition);
355 | }
356 | }
357 |
358 | public int getSelectedPosition() {
359 | if (mCurrSelectedView != null) {
360 | return mCurrSelectedView.getPosition();
361 | }
362 | return -1;
363 | }
364 |
365 | private OnClickListener mOnRevelViewClickListener = new OnClickListener() {
366 | @Override
367 | public void onClick(View v) {
368 | if (mViewState == ViewState.ShowingRevealedLayout) {
369 | revealEditView();
370 | } else if (mViewState == ViewState.ShowingEditLayout) {
371 | hideEditView();
372 | }
373 | }
374 | };
375 |
376 | private TextWatcher mTextWatcher = new TextWatcher() {
377 | @Override
378 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
379 |
380 | }
381 |
382 | @Override
383 | public void onTextChanged(CharSequence s, int start, int before, int count) {
384 | Filterable filterable = (Filterable) mSpinnerListView.getAdapter();
385 | if (filterable != null)
386 | filterable.getFilter().filter(s);
387 | }
388 |
389 | @Override
390 | public void afterTextChanged(Editable s) {
391 |
392 | }
393 | };
394 |
395 | private void setupColors() {
396 | mRevealContainerCardView.setBackgroundColor(mRevealViewBackgroundColor);
397 | mRevealItem.setBackgroundColor(mRevealViewBackgroundColor);
398 | mStartSearchImageView.setBackgroundColor(mRevealViewBackgroundColor);
399 | mStartSearchImageView.setTextColor(mStartEditTintColor);
400 |
401 | mContainerCardView.setBackgroundColor(mEditViewBackgroundColor);
402 | mSearchEditText.setBackgroundColor(mEditViewBackgroundColor);
403 | mSearchEditText.setTextColor(mEditViewTextColor);
404 | mSearchEditText.setHintTextColor(mStartEditTintColor);
405 | EditCursorColor.setCursorColor(mSearchEditText, mEditViewTextColor);
406 | mDoneSearchImageView.setBackgroundColor(mEditViewBackgroundColor);
407 | mDoneSearchImageView.setTextColor(mDoneEditTintColor);
408 | }
409 |
410 | private void setupList() {
411 | ViewGroup.MarginLayoutParams spinnerListViewLayoutParams = (ViewGroup.MarginLayoutParams) mSpinnerListView.getLayoutParams();
412 | ViewGroup.LayoutParams spinnerListContainerLayoutParams = mSpinnerListContainer.getLayoutParams();
413 | LinearLayout.LayoutParams listLayoutParams = (LinearLayout.LayoutParams) mSpinnerListView.getLayoutParams();
414 |
415 | spinnerListContainerLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
416 |
417 | if (mExpandSize <= 0) {
418 | listLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
419 | } else {
420 | listLayoutParams.height = mExpandSize;
421 | }
422 | mSpinnerListContainer.setBackgroundColor(mBoarderColor);
423 | if (mShowBorders && mBordersSize > 0) {
424 | spinnerListViewLayoutParams.setMargins(mBordersSize, mBordersSize, mBordersSize, mBordersSize);
425 | } else {
426 | spinnerListViewLayoutParams.setMargins(0, 0, 0, 0);
427 | }
428 | }
429 |
430 | public void setAdapter(ListAdapter adapter) {
431 | if (!(adapter instanceof Filterable))
432 | throw new IllegalArgumentException("Adapter should implement the Filterable interface");
433 | mSpinnerListView.setAdapter(adapter);
434 | }
435 |
436 | public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
437 | mOnItemSelected = onItemSelectedListener;
438 | }
439 |
440 | @Override
441 | protected void onConfigurationChanged(Configuration newConfig) {
442 | super.onConfigurationChanged(newConfig);
443 | hideEdit();
444 | getScreenSize();
445 | }
446 |
447 | private void getScreenSize() {
448 | DisplayMetrics metrics = new DisplayMetrics();
449 | WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
450 | wm.getDefaultDisplay().getMetrics(metrics);
451 | mScreenHeightPixels = metrics.heightPixels;
452 | mScreenWidthPixels = metrics.widthPixels;
453 | }
454 |
455 | @Override
456 | public void onClick(View v) {
457 | int id = v.getId();
458 | if (id == R.id.ImgVw_StartSearch) {
459 | revealEdit();
460 | } else if (id == R.id.ImgVw_DoneSearch) {
461 | hideEdit();
462 | }
463 | }
464 |
465 | public void revealEdit() {
466 | if (mViewState == ViewState.ShowingRevealedLayout) {
467 | if (!mKeepLastSearch)
468 | mSearchEditText.setText(null);
469 | revealEditView();
470 | }
471 | }
472 |
473 | public void hideEdit() {
474 | if (mViewState == ViewState.ShowingEditLayout) {
475 | hideEditView();
476 | }
477 | }
478 |
479 | private void revealEditView() {
480 | mViewState = ViewState.ShowingAnimation;
481 | if (mStatusListener != null)
482 | mStatusListener.spinnerIsOpening();
483 | final int cx = mRevealContainerCardView.getLeft();
484 | final int cxr = mRevealContainerCardView.getRight();
485 | final int cy = (mRevealContainerCardView.getTop() + mRevealContainerCardView.getHeight())/2;
486 | final int reverse_startradius = Math.max(mRevealContainerCardView.getWidth(), mRevealContainerCardView.getHeight());
487 | final int reverse_endradius = 0;
488 |
489 | if (!mPopupWindow.isShowing())
490 | mPopupWindow.showAsDropDown(this, cx, 0);
491 |
492 | final Animator revealAnimator = ViewAnimationUtils.createCircularReveal(mRevealContainerCardView, cx, cy, reverse_startradius, reverse_endradius);
493 | revealAnimator.addListener(new Animator.AnimatorListener() {
494 | @Override
495 | public void onAnimationStart(Animator animation) {
496 | }
497 |
498 | @Override
499 | public void onAnimationEnd(Animator animation) {
500 | mViewState = ViewState.ShowingEditLayout;
501 | mRevealContainerCardView.setVisibility(View.INVISIBLE);
502 | }
503 |
504 | @Override
505 | public void onAnimationCancel(Animator animation) {
506 |
507 | }
508 |
509 | @Override
510 | public void onAnimationRepeat(Animator animation) {
511 |
512 | }
513 | });
514 |
515 | final Animator animator = ViewAnimationUtils.createCircularReveal(mContainerCardView, cxr, cy, reverse_endradius, reverse_startradius);
516 | animator.addListener(new Animator.AnimatorListener() {
517 | @Override
518 | public void onAnimationStart(Animator animation) {
519 |
520 | }
521 |
522 | @Override
523 | public void onAnimationEnd(Animator animation) {
524 | mContainerCardView.setVisibility(View.VISIBLE);
525 | mViewState = ViewState.ShowingEditLayout;
526 | }
527 |
528 | @Override
529 | public void onAnimationCancel(Animator animation) {
530 |
531 | }
532 |
533 | @Override
534 | public void onAnimationRepeat(Animator animation) {
535 |
536 | }
537 | });
538 |
539 | mSpinnerListContainer.setVisibility(View.VISIBLE);
540 | mContainerCardView.setVisibility(View.VISIBLE);
541 |
542 | animator.setDuration(mAnimDuration);
543 | revealAnimator.setDuration(mAnimDuration);
544 |
545 |
546 | animator.start();
547 | revealAnimator.start();
548 |
549 | mPopupWindow.getContentView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
550 | @Override
551 | public void onGlobalLayout() {
552 | mPopupWindow.getContentView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
553 | final Animator spinnerListContainerAnimator = ViewAnimationUtils.createCircularReveal(mPopupWindow.getContentView(), cxr, cy, reverse_endradius, reverse_startradius);
554 | spinnerListContainerAnimator.addListener(new Animator.AnimatorListener() {
555 | @Override
556 | public void onAnimationStart(Animator animation) {
557 |
558 | }
559 |
560 | @Override
561 | public void onAnimationEnd(Animator animation) {
562 | mSpinnerListContainer.setVisibility(View.VISIBLE);
563 | }
564 |
565 | @Override
566 | public void onAnimationCancel(Animator animation) {
567 |
568 | }
569 |
570 | @Override
571 | public void onAnimationRepeat(Animator animation) {
572 |
573 | }
574 | });
575 | spinnerListContainerAnimator.setDuration(mAnimDuration);
576 | spinnerListContainerAnimator.start();
577 | }
578 | });
579 |
580 | }
581 |
582 | private void hideEditView() {
583 | mViewState = ViewState.ShowingAnimation;
584 | if (mStatusListener != null)
585 | mStatusListener.spinnerIsClosing();
586 | final int cx = mContainerCardView.getLeft();
587 | final int cxr = mContainerCardView.getRight();
588 | final int cy = (mContainerCardView.getTop() + mContainerCardView.getHeight())/2;
589 | final int reverse_startradius = Math.max(mContainerCardView.getWidth(), mContainerCardView.getHeight());
590 | final int reverse_endradius = 0;
591 |
592 | final Animator revealAnimator = ViewAnimationUtils.createCircularReveal(mRevealContainerCardView, cx, cy, reverse_endradius, reverse_startradius);
593 | revealAnimator.addListener(new Animator.AnimatorListener() {
594 | @Override
595 | public void onAnimationStart(Animator animation) {
596 | }
597 |
598 | @Override
599 | public void onAnimationEnd(Animator animation) {
600 | mViewState = ViewState.ShowingRevealedLayout;
601 | mRevealContainerCardView.setVisibility(View.VISIBLE);
602 | }
603 |
604 | @Override
605 | public void onAnimationCancel(Animator animation) {
606 |
607 | }
608 |
609 | @Override
610 | public void onAnimationRepeat(Animator animation) {
611 |
612 | }
613 | });
614 |
615 | final Animator cardViewanimator = ViewAnimationUtils.createCircularReveal(mContainerCardView, cxr, cy, reverse_startradius, reverse_endradius);
616 | cardViewanimator.addListener(new Animator.AnimatorListener() {
617 | @Override
618 | public void onAnimationStart(Animator animation) {
619 |
620 | }
621 |
622 | @Override
623 | public void onAnimationEnd(Animator animation) {
624 | mContainerCardView.setVisibility(View.INVISIBLE);
625 | mViewState = ViewState.ShowingRevealedLayout;
626 | ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(mSearchEditText.getWindowToken(), 0);
627 | }
628 |
629 | @Override
630 | public void onAnimationCancel(Animator animation) {
631 |
632 | }
633 |
634 | @Override
635 | public void onAnimationRepeat(Animator animation) {
636 |
637 | }
638 | });
639 |
640 | mRevealContainerCardView.setVisibility(View.VISIBLE);
641 | cardViewanimator.setDuration(mAnimDuration);
642 | cardViewanimator.start();
643 |
644 | revealAnimator.setDuration(mAnimDuration);
645 | revealAnimator.start();
646 |
647 | if (mPopupWindow.isShowing()) {
648 | final Animator spinnerListContainerAnimator = ViewAnimationUtils.createCircularReveal(mPopupWindow.getContentView(), cxr, cy, reverse_startradius, reverse_endradius);
649 | spinnerListContainerAnimator.addListener(new Animator.AnimatorListener() {
650 | @Override
651 | public void onAnimationStart(Animator animation) {
652 |
653 | }
654 |
655 | @Override
656 | public void onAnimationEnd(Animator animation) {
657 | mSpinnerListContainer.setVisibility(View.GONE);
658 | mPopupWindow.dismiss();
659 | }
660 |
661 | @Override
662 | public void onAnimationCancel(Animator animation) {
663 |
664 | }
665 |
666 | @Override
667 | public void onAnimationRepeat(Animator animation) {
668 |
669 | }
670 | });
671 | spinnerListContainerAnimator.setDuration(mAnimDuration);
672 | spinnerListContainerAnimator.start();
673 | }
674 | }
675 |
676 | public boolean isInsideSearchEditText(MotionEvent event) {
677 | Rect editTextRect = new Rect();
678 | mSearchEditText.getHitRect(editTextRect);
679 | if (!editTextRect.contains((int)event.getX(), (int)event.getY())) {
680 | return false;
681 | }
682 | return true;
683 | }
684 |
685 | public void setStatusListener(IStatusListener statusListener) {
686 | mStatusListener = statusListener;
687 | }
688 |
689 | @Override
690 | public Parcelable onSaveInstanceState() {
691 | Parcelable superState = super.onSaveInstanceState();
692 | SavedState ss = new SavedState(superState);
693 | ss.mViewState = ViewState.ShowingRevealedLayout;
694 | ss.mAnimDuration = mAnimDuration;
695 | ss.mBordersSize = mBordersSize;
696 | ss.mExpandSize = mExpandSize;
697 | ss.mBoarderColor = mBoarderColor;
698 | ss.mRevealViewBackgroundColor = mRevealViewBackgroundColor;
699 | ss.mStartEditTintColor = mStartEditTintColor;
700 | ss.mEditViewBackgroundColor = mEditViewBackgroundColor;
701 | ss.mEditViewTextColor = mEditViewTextColor;
702 | ss.mDoneEditTintColor = mDoneEditTintColor;
703 | ss.mShowBorders = mShowBorders;
704 | ss.mKeepLastSearch = mKeepLastSearch;
705 | ss.mRevealEmptyText = mRevealEmptyText;
706 | ss.mSearchHintText = mSearchHintText;
707 | ss.mNoItemsFoundText = mNoItemsFoundText;
708 | ss.mSelectedViewPosition = mCurrSelectedView != null ? mCurrSelectedView.getPosition() : -1;
709 | return ss;
710 | }
711 |
712 | @Override
713 | public void onRestoreInstanceState(Parcelable state) {
714 | super.onRestoreInstanceState(state);
715 | SavedState ss = (SavedState) state;
716 | super.onRestoreInstanceState(ss.getSuperState());
717 | mViewState = ss.mViewState;
718 | mAnimDuration = ss.mAnimDuration;
719 | mBordersSize = ss.mBordersSize;
720 | mExpandSize = ss.mExpandSize;
721 | mBoarderColor = ss.mBoarderColor;
722 | mRevealViewBackgroundColor = ss.mRevealViewBackgroundColor;
723 | mStartEditTintColor = ss.mStartEditTintColor;
724 | mEditViewBackgroundColor = ss.mEditViewBackgroundColor;
725 | mEditViewTextColor = ss.mEditViewTextColor;
726 | mDoneEditTintColor = ss.mDoneEditTintColor;
727 | mShowBorders = ss.mShowBorders;
728 | mKeepLastSearch = ss.mKeepLastSearch;
729 | mRevealEmptyText = ss.mRevealEmptyText;
730 | mSearchHintText = ss.mSearchHintText;
731 | mNoItemsFoundText = ss.mNoItemsFoundText;
732 | int mSelectedViewPosition = ss.mSelectedViewPosition;
733 |
734 | if (mSelectedViewPosition >= 0) {
735 | View v = mSpinnerListView.getAdapter().getView(mSelectedViewPosition, null, null);
736 | mSpinnerListView.performItemClick(v, mSelectedViewPosition, v.getId());
737 | }
738 | }
739 |
740 | static class SavedState extends BaseSavedState {
741 | ViewState mViewState;
742 | int mAnimDuration;
743 | @Px int mBordersSize;
744 | @Px int mExpandSize;
745 | @ColorInt int mBoarderColor;
746 | @ColorInt int mRevealViewBackgroundColor;
747 | @ColorInt int mStartEditTintColor;
748 | @ColorInt int mEditViewBackgroundColor;
749 | @ColorInt int mEditViewTextColor;
750 | @ColorInt int mDoneEditTintColor;
751 | boolean mShowBorders;
752 | boolean mKeepLastSearch;
753 | String mRevealEmptyText;
754 | String mSearchHintText;
755 | String mNoItemsFoundText;
756 | int mSelectedViewPosition;
757 |
758 | SavedState(Parcelable superState) {
759 | super(superState);
760 | }
761 |
762 | private SavedState(Parcel in) {
763 | super(in);
764 | mViewState = ViewState.values()[in.readInt()];
765 | mAnimDuration = in.readInt();
766 | mBordersSize = in.readInt();
767 | mExpandSize = in.readInt();
768 | mBoarderColor = in.readInt();
769 | mRevealViewBackgroundColor = in.readInt();
770 | mStartEditTintColor = in.readInt();
771 | mEditViewBackgroundColor = in.readInt();
772 | mEditViewTextColor = in.readInt();
773 | mDoneEditTintColor = in.readInt();
774 | mShowBorders = in.readInt() > 0 ? true : false;
775 | mKeepLastSearch = in.readInt() > 0 ? true : false;
776 | mRevealEmptyText = in.readString();
777 | mSearchHintText = in.readString();
778 | mNoItemsFoundText = in.readString();
779 | mSelectedViewPosition = in.readInt();
780 | }
781 |
782 | @Override
783 | public void writeToParcel(Parcel out, int flags) {
784 | super.writeToParcel(out, flags);
785 | out.writeInt(mViewState.ordinal());
786 | out.writeInt(mAnimDuration);
787 | out.writeInt(mBordersSize);
788 | out.writeInt(mExpandSize);
789 | out.writeInt(mBoarderColor);
790 | out.writeInt(mRevealViewBackgroundColor);
791 | out.writeInt(mStartEditTintColor);
792 | out.writeInt(mEditViewBackgroundColor);
793 | out.writeInt(mEditViewTextColor);
794 | out.writeInt(mDoneEditTintColor);
795 | out.writeInt(mShowBorders ? 1 : 0);
796 | out.writeInt(mKeepLastSearch ? 1 : 0);
797 | out.writeString(mRevealEmptyText);
798 | out.writeString(mSearchHintText);
799 | out.writeString(mNoItemsFoundText);
800 | out.writeInt(mSelectedViewPosition);
801 | }
802 |
803 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
804 | public SavedState createFromParcel(Parcel in) {
805 | return new SavedState(in);
806 | }
807 | public SavedState[] newArray(int size) {
808 | return new SavedState[size];
809 | }
810 | };
811 | }
812 | }
813 |
--------------------------------------------------------------------------------