diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..39fb081a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..943201a4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017 pchmn
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..211e5553
--- /dev/null
+++ b/README.md
@@ -0,0 +1,284 @@
+# MaterialChipsInput
+
+Implementation of Material Design [Chips](https://material.io/guidelines/components/chips.html) component for Android. The library provides two views : [`ChipsInput`](#chipsinput) and [`ChipView`](#chipview).
+
+[![Release](https://jitpack.io/v/pchmn/MaterialChipsInput.svg)](https://jitpack.io/#pchmn/MaterialChipsInput)
+
+
+
+## Sample APK
+[sample-v1.0.0.apk](https://github.com/pchmn/MaterialChipsInput/raw/master/docs/material-chips-input-sample-v1.0.0.apk)
+
+## Setup
+
+To use this library your `minSdkVersion` must be >= 15.
+
+In your project level build.gradle :
+```java
+allprojects {
+ repositories {
+ ...
+ maven { url "https://jitpack.io" }
+ }
+}
+```
+
+In your app level build.gradle :
+```java
+dependencies {
+ compile 'com.github.pchmn:MaterialChipsInput:1.0.0'
+}
+```
+
+## ChipsInput
+This view implements the Material Design [Contact chips component](https://material.io/guidelines/components/chips.html#chips-contact-chips).
+
+It is composed of a collection of chips (`ChipView`) and an input (`EditText`). Touching a chip open a full detailed view (if non disable). The [GIF](#materialchipsinput) above describes the behavior of the `ChipsInput` view.
+
+But everything is configurable (optional avatar icon, optional full detailed view, ...) so you can use the `ChipsInput` view for non contact chips.
+### Basic Usage
+
+#### XML
+Use the ChipsInput view in your layout and customize it ([see](#chipsinput-attributes) all attributes) :
+
+```xml
+
+```
+
+#### Suggestions
+You can pass a `List extends ChipInterface>` object, which represents your suggestions, to the `ChipsInput` view, so it will work as a
+`MultiAutoCompleteTextView` :
+
+##### 1. Create a class that implements `ChipInterface` (or use directly the [`Chip`](https://github.com/pchmn/MaterialChipsInput/blob/master/library/src/main/java/com/pchmn/materialchips/model/Chip.java) class included in the library) :
+```java
+public class ContactChip implements ChipInterface {
+ ...
+}
+```
+
+##### 2. Then in your activity, or anything else, build your suggestion list of `ContactChip` (or `Chip`) and pass it to the `ChipsInput` view :
+```java
+// get ChipsInput view
+ChipsInput chipsInput = (ChipsInput) findViewById(R.id.chips_input);
+
+// build the ContactChip list
+List contactList = new ArrayList<>();
+contactList.add(new ContactChip());
+...
+
+// pass the ContactChip list
+chipsInput.setFilterableList(contactList);
+```
+
+#### Get the selected list
+When you want you can get the current list of chips selected by the user :
+```java
+// get the list
+List contactsSelected = (List) chipsInput.getSelectedChipList();
+```
+
+That's it, there is nothing more to do.
+
+### Advanced Usage
+#### ChipsListener
+The `ChipsInput` view provides a listener to interact with the input :
+```java
+chipsInput.addChipsListener(new ChipsInput.ChipsListener() {
+ @Override
+ public void onChipAdded(ChipInterface chip, int newSize) {
+ // chip added
+ // newSize is the size of the updated selected chip list
+ }
+
+ @Override
+ public void onChipRemoved(ChipInterface chip, int newSize) {
+ // chip removed
+ // newSize is the size of the updated selected chip list
+ }
+
+ @Override
+ public void onTextChanged(CharSequence text) {
+ // text changed
+ }
+ });
+```
+
+#### Add and remove chips manually
+You don't have to pass a `List extends ChipInterface>` to the `ChipsInput` view and you can do the trick manually. Thanks to the `ChipsListener` you can be notified when the user is typing and do your own work.
+
+```java
+ChipsInput chipsInput = (ChipsInput) findViewById(R.id.chips_input);
+```
+
+##### Add a chip
+There are multiple implementations :
+```java
+chipsInput.addChip(ChipInterface chip);
+// or
+chipsInput.addChip(Object id, Drawable icon, String label, String info);
+// or
+chipsInput.addChip(Drawable icon, String label, String info);
+// or
+chipsInput.addChip(Object id, Uri iconUri, String label, String info);
+// or
+chipsInput.addChip(Uri iconUri, String label, String info);
+// or
+chipsInput.addChip(String label, String info);
+```
+
+##### Remove a chip
+There are multiple implementations :
+```java
+chipsInput.removeChip(ChipInterface chip);
+// or
+chipsInput.removeChipById(Object id);
+// or
+chipsInput.removeChipByLabel(String label);
+// or
+chipsInput.removeChipByInfo(String info);
+```
+
+After you added or removed a chip the `ChipsListener` will be triggered.
+
+##### Get the selected list
+When you want you can get the current list of chips selected by the user :
+```java
+// get the list
+List contactsSelected = chipsInput.getSelectedChipList();
+```
+
+### ChipsInput attributes
+
+Attribute | Type | Description | Default
+--- | --- | --- | ---
+`app:hint` | `string` | Hint of the input when there is no chip | null
+`app:hintColor` | `color` | Hint color | android default
+`app:textColor` | `color` | Text color when user types | android default
+`app:maxRows` | `int` | Max rows of chips | 2
+`app:chip_labelColor` | `color` | Label color of the chips | android default
+`app:chip_hasAvatarIcon` | `boolean` | Whether the chips have avatar icon or not | true
+`app:chip_deletable` | `boolean` | Whether the chips are deletable (delete icon) or not | false
+`app:chip_deleteIconColor` | `color` | Delete icon color of the chips | white/black
+`app:chip_backgroundColor` | `color` | Background color of the chips | grey
+`app:showChipDetailed` | `boolean` | Whether to show full detailed view or not when touching a chip | true
+`app:chip_detailed_textColor` | `color` | Full detailed view text color | white/balck
+`app:chip_detailed_backgroundColor` | `color` | Background color of the full detailed view | colorAccent
+`app:chip_detailed_deleteIconColor` | `color` | Delete icon color of the full detailed view | white/black
+`app:filterable_list_backgroundColor` | `color` | Background color of the filterable list of suggestions | white
+`app:filterable_list_textColor` | `color` | Text color of the filterable list of suggestions | black
+
+
+## ChipView
+This view implements the chip component according to the [Material Design guidelines](https://material.io/guidelines/components/chips.html#chips-usage) with configurable options (background color, text color, ...).
+
+
+
+### Usage
+```xml
+
+
+
+
+
+
+```
+
+### ChipView attributes
+
+Attribute | Type | Description | Default
+--- | --- | --- | ---
+`app:label` | `string` | Label of the chip | null
+`app:labelColor` | `color` | Label color of the chip | android default
+`app:hasAvatarIcon` | `boolean` | Whether the chip has avatar icon or not | false
+`app:avatarIcon` | `drawable` | Avatar icon resource | null
+`app:deletable` | `boolean` | Whether the chip is deletable (delete icon) or not | false
+`app:deleteIconColor` | `color` | Delete icon color of the chip | grey
+`app:backgroundColor` | `color` | Background color of the chip | grey
+
+### Listeners
+```java
+ChipView chip = (ChipView) findViewById(R.id.chip_view);
+```
+
+On chip click listener :
+```java
+chip.setOnChipClicked(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // handle click
+ }
+});
+```
+
+On delete button click listener :
+```java
+chip.setOnDeleteClicked(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // handle click
+ }
+});
+```
+
+## Sample
+
+A sample app with some use cases of the library is available on this [link](https://github.com/pchmn/MaterialChipsInput/tree/master/sample)
+
+You can also download the sample APK [here](https://github.com/pchmn/MaterialChipsInput/raw/master/docs/material-chips-input-sample-v1.0.0.apk)
+
+## Credits
+
+* [Android Material Chips](https://github.com/DoodleScheduling/android-material-chips)
+* [Material Chip View](https://github.com/robertlevonyan/materialChipView?utm_source=android-arsenal.com&utm_medium=referral&utm_campaign=5396)
+* [ChipsLayoutManager](https://github.com/BelooS/ChipsLayoutManager)
+
+## License
+
+```
+Copyright 2017 pchmn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+```
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..64dc55d3
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
+ classpath 'me.tatarka:gradle-retrolambda:3.6.0'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/docs/Screenshot_20170413-164058.png b/docs/Screenshot_20170413-164058.png
new file mode 100644
index 00000000..c25598a3
Binary files /dev/null and b/docs/Screenshot_20170413-164058.png differ
diff --git a/docs/chips-examples.png b/docs/chips-examples.png
new file mode 100644
index 00000000..57b7b930
Binary files /dev/null and b/docs/chips-examples.png differ
diff --git a/docs/chips-input-illustration.png b/docs/chips-input-illustration.png
new file mode 100644
index 00000000..2578a1af
Binary files /dev/null and b/docs/chips-input-illustration.png differ
diff --git a/docs/demo.gif b/docs/demo.gif
new file mode 100755
index 00000000..7e3c93a8
Binary files /dev/null and b/docs/demo.gif differ
diff --git a/docs/demo2.gif b/docs/demo2.gif
new file mode 100644
index 00000000..66004b3c
Binary files /dev/null and b/docs/demo2.gif differ
diff --git a/docs/material-chips-input-sample-v1.0.0.apk b/docs/material-chips-input-sample-v1.0.0.apk
new file mode 100644
index 00000000..324c89ed
Binary files /dev/null and b/docs/material-chips-input-sample-v1.0.0.apk differ
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..aac7c9b4
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..13372aef
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..7f3b2ff9
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Mar 28 20:31:03 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..9d82f789
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/library/.gitignore b/library/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/library/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 00000000..0fb0cc05
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,46 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.jakewharton.butterknife'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 25
+ versionCode 1
+ versionName "1.0.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ compile 'com.android.support:appcompat-v7:25.3.0'
+ testCompile 'junit:junit:4.12'
+
+ // recycler
+ compile 'com.android.support:recyclerview-v7:25.3.0'
+ compile 'com.beloo.widget:ChipsLayoutManager:0.3.7@aar'
+
+ // circle image view
+ compile 'de.hdodenhof:circleimageview:2.1.0'
+
+ // butter knife
+ compile 'com.jakewharton:butterknife:8.5.1'
+ annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
+}
+
+apply plugin: 'com.github.dcendents.android-maven'
+group='com.github.pchmn'
diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro
new file mode 100644
index 00000000..0ae68584
--- /dev/null
+++ b/library/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/couleurwhatever/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/library/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java b/library/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..a28c3f32
--- /dev/null
+++ b/library/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.pchmn.materialchips;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.pchmn.library.test", appContext.getPackageName());
+ }
+}
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..633e89cb
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/library/src/main/java/com/pchmn/materialchips/ChipView.java b/library/src/main/java/com/pchmn/materialchips/ChipView.java
new file mode 100644
index 00000000..6c6b27bd
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/ChipView.java
@@ -0,0 +1,466 @@
+package com.pchmn.materialchips;
+
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.annotation.ColorInt;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.pchmn.materialchips.model.Chip;
+import com.pchmn.materialchips.model.ChipInterface;
+import com.pchmn.materialchips.util.LetterTileProvider;
+import com.pchmn.materialchips.util.ViewUtil;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import de.hdodenhof.circleimageview.CircleImageView;
+
+public class ChipView extends RelativeLayout {
+
+ private static final String TAG = ChipView.class.toString();
+ // context
+ private Context mContext;
+ // xml elements
+ @BindView(R2.id.content) LinearLayout mContentLayout;
+ @BindView(R2.id.icon) CircleImageView mAvatarIconImageView;
+ @BindView(R2.id.label) TextView mLabelTextView;
+ @BindView(R2.id.delete_button) ImageButton mDeleteButton;
+ // attributes
+ private static final int NONE = -1;
+ private String mLabel;
+ private ColorStateList mLabelColor;
+ private boolean mHasAvatarIcon = false;
+ private Drawable mAvatarIconDrawable;
+ private Uri mAvatarIconUri;
+ private boolean mDeletable = false;
+ private Drawable mDeleteIcon;
+ private ColorStateList mDeleteIconColor;
+ private ColorStateList mBackgroundColor;
+ // letter tile provider
+ private LetterTileProvider mLetterTileProvider;
+ // chip
+ private ChipInterface mChip;
+
+ public ChipView(Context context) {
+ super(context);
+ mContext = context;
+ init(null);
+ }
+
+ public ChipView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ init(attrs);
+ }
+
+ /**
+ * Inflate the view according to attributes
+ *
+ * @param attrs the attributes
+ */
+ private void init(AttributeSet attrs) {
+ // inflate layout
+ View rootView = inflate(getContext(), R.layout.chip_view, this);
+ // butter knife
+ ButterKnife.bind(this, rootView);
+ // letter tile provider
+ mLetterTileProvider = new LetterTileProvider(mContext);
+
+ // attributes
+ if(attrs != null) {
+ TypedArray a = mContext.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.ChipView,
+ 0, 0);
+
+ try {
+ // label
+ mLabel = a.getString(R.styleable.ChipView_label);
+ mLabelColor = a.getColorStateList(R.styleable.ChipView_labelColor);
+ // avatar icon
+ mHasAvatarIcon = a.getBoolean(R.styleable.ChipView_hasAvatarIcon, false);
+ int avatarIconId = a.getResourceId(R.styleable.ChipView_avatarIcon, NONE);
+ if(avatarIconId != NONE) mAvatarIconDrawable = ContextCompat.getDrawable(mContext, avatarIconId);
+ // delete icon
+ mDeletable = a.getBoolean(R.styleable.ChipView_deletable, false);
+ mDeleteIconColor = a.getColorStateList(R.styleable.ChipView_deleteIconColor);
+ int deleteIconId = a.getResourceId(R.styleable.ChipView_deleteIcon, NONE);
+ if(deleteIconId != NONE) mDeleteIcon = ContextCompat.getDrawable(mContext, deleteIconId);
+ // background color
+ mBackgroundColor = a.getColorStateList(R.styleable.ChipView_backgroundColor);
+ }
+ finally {
+ a.recycle();
+ }
+ }
+
+ // inflate
+ inflateWithAttributes();
+ }
+
+ /**
+ * Inflate the view
+ */
+ private void inflateWithAttributes() {
+ // label
+ setLabel(mLabel);
+ if(mLabelColor != null)
+ setLabelColor(mLabelColor);
+
+ // avatar
+ setHasAvatarIcon(mHasAvatarIcon);
+ if(mAvatarIconUri != null)
+ setAvatarIcon(mAvatarIconUri);
+ else if(mAvatarIconDrawable != null)
+ setAvatarIcon(mAvatarIconDrawable);
+ else
+ mAvatarIconImageView.setImageBitmap(mLetterTileProvider.getLetterTile(getLabel()));
+
+ // delete button
+ setDeletable(mDeletable);
+ if(mDeleteIcon != null)
+ setDeleteIcon(mDeleteIcon);
+ if(mDeleteIconColor != null)
+ setDeleteIconColor(mDeleteIconColor);
+
+ // background color
+ if(mBackgroundColor != null)
+ setChipBackgroundColor(mBackgroundColor);
+ }
+
+ public void inflate(ChipInterface chip) {
+ mChip = chip;
+
+ // icon
+ if(mHasAvatarIcon && mChip.getAvatarUri() != null)
+ setAvatarIcon(mChip.getAvatarUri());
+ else if(mHasAvatarIcon && mChip.getAvatarDrawable() != null)
+ setAvatarIcon(mChip.getAvatarDrawable());
+ else if(mHasAvatarIcon)
+ mAvatarIconImageView.setImageBitmap(mLetterTileProvider.getLetterTile(chip.getLabel()));
+
+ // label
+ mLabelTextView.setText(mChip.getLabel());
+ }
+
+ /**
+ * Get label
+ *
+ * @return the label
+ */
+ public String getLabel() {
+ return mLabelTextView.getText().toString();
+ }
+
+ /**
+ * Set label
+ *
+ * @param label the label to set
+ */
+ public void setLabel(String label) {
+ mLabelTextView.setText(label);
+ }
+
+ /**
+ * Set label color
+ *
+ * @param color the color to set
+ */
+ public void setLabelColor(ColorStateList color) {
+ mLabelTextView.setTextColor(color);
+ }
+
+ /**
+ * Set label color
+ *
+ * @param color the color to set
+ */
+ public void setLabelColor(@ColorInt int color) {
+ mLabelTextView.setTextColor(color);
+ }
+
+ /**
+ * Show or hide avatar icon
+ *
+ * @param hasAvatarIcon true to show, false to hide
+ */
+ public void setHasAvatarIcon(boolean hasAvatarIcon) {
+ if(!hasAvatarIcon) {
+ // hide icon
+ mAvatarIconImageView.setVisibility(GONE);
+ // adjust padding
+ if(mDeleteButton.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, ViewUtil.dpToPx(12), 0);
+
+ }
+ else {
+ // show icon
+ mAvatarIconImageView.setVisibility(VISIBLE);
+ // adjust padding
+ if(mDeleteButton.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, ViewUtil.dpToPx(12), 0);
+ }
+ }
+
+ /**
+ * Set avatar icon
+ *
+ * @param avatarIcon the icon to set
+ */
+ public void setAvatarIcon(Drawable avatarIcon) {
+ // set icon
+ mAvatarIconImageView.setImageDrawable(avatarIcon);
+
+ // show icon
+ mAvatarIconImageView.setVisibility(VISIBLE);
+ // adjust padding
+ if(mDeleteButton.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, ViewUtil.dpToPx(12), 0);
+ }
+
+ /**
+ * Set avatar icon
+ *
+ * @param avatarUri the uri of the icon to set
+ */
+ public void setAvatarIcon(Uri avatarUri) {
+ // set icon
+ mAvatarIconImageView.setImageURI(avatarUri);
+
+ // show icon
+ mAvatarIconImageView.setVisibility(VISIBLE);
+ // adjust padding
+ if(mDeleteButton.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, ViewUtil.dpToPx(12), 0);
+ }
+
+ /**
+ * Show or hide delte button
+ *
+ * @param deletable true to show, false to hide
+ */
+ public void setDeletable(boolean deletable) {
+ if(!deletable) {
+ // hide delete icon
+ mDeleteButton.setVisibility(GONE);
+ // adjust padding
+ if(mAvatarIconImageView.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, ViewUtil.dpToPx(12), 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, ViewUtil.dpToPx(12), 0);
+ }
+ else {
+ // show icon
+ mDeleteButton.setVisibility(VISIBLE);
+ // adjust padding
+ if(mAvatarIconImageView.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0);
+ }
+ }
+
+ /**
+ * Set delete icon color
+ *
+ * @param color the color to set
+ */
+ public void setDeleteIconColor(ColorStateList color) {
+ setDeleteIconColor(color.getDefaultColor());
+ }
+
+ /**
+ * Set delete icon color
+ *
+ * @param color the color to set
+ */
+ public void setDeleteIconColor(@ColorInt int color) {
+ // set color
+ mDeleteButton.getDrawable().mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+
+ // show icon
+ mDeleteButton.setVisibility(VISIBLE);
+ // adjust padding
+ if(mAvatarIconImageView.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0);
+ }
+
+ /**
+ * Set delete icon
+ *
+ * @param deleteIcon the icon to set
+ */
+ public void setDeleteIcon(Drawable deleteIcon) {
+ // set icon
+ mDeleteButton.setImageDrawable(deleteIcon);
+
+ // show icon
+ mDeleteButton.setVisibility(VISIBLE);
+ // adjust padding
+ if(mAvatarIconImageView.getVisibility() == VISIBLE)
+ mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0);
+ else
+ mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0);
+ }
+
+ /**
+ * Set background color
+ *
+ * @param color the color to set
+ */
+ public void setChipBackgroundColor(ColorStateList color) {
+ setChipBackgroundColor(color.getDefaultColor());
+ }
+
+ /**
+ * Set background color
+ *
+ * @param color the color to set
+ */
+ public void setChipBackgroundColor(@ColorInt int color) {
+ mContentLayout.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ }
+
+ /**
+ * Set the chip object
+ *
+ * @param chip the chip
+ */
+ public void setChip(ChipInterface chip) {
+ mChip = chip;
+ }
+
+ /**
+ * Set OnClickListener on the delete button
+ *
+ * @param onClickListener the OnClickListener
+ */
+ public void setOnDeleteClicked(OnClickListener onClickListener) {
+ mDeleteButton.setOnClickListener(onClickListener);
+ }
+
+ /**
+ * Set OnclickListener on the entire chip
+ *
+ * @param onClickListener the OnClickListener
+ */
+ public void setOnChipClicked(OnClickListener onClickListener) {
+ mContentLayout.setOnClickListener(onClickListener);
+ }
+
+ /**
+ * Builder class
+ */
+ public static class Builder {
+ private Context context;
+ private String label;
+ private ColorStateList labelColor;
+ private boolean hasAvatarIcon = false;
+ private Uri avatarIconUri;
+ private Drawable avatarIconDrawable;
+ private boolean deletable = false;
+ private Drawable deleteIcon;
+ private ColorStateList deleteIconColor;
+ private ColorStateList backgroundColor;
+ private ChipInterface chip;
+
+ public Builder(Context context) {
+ this.context = context;
+ }
+
+ public Builder label(String label) {
+ this.label = label;
+ return this;
+ }
+
+ public Builder labelColor(ColorStateList labelColor) {
+ this.labelColor = labelColor;
+ return this;
+ }
+
+ public Builder hasAvatarIcon(boolean hasAvatarIcon) {
+ this.hasAvatarIcon = hasAvatarIcon;
+ return this;
+ }
+
+ public Builder avatarIcon(Uri avatarUri) {
+ this.avatarIconUri = avatarUri;
+ return this;
+ }
+
+ public Builder avatarIcon(Drawable avatarIcon) {
+ this.avatarIconDrawable = avatarIcon;
+ return this;
+ }
+
+ public Builder deletable(boolean deletable) {
+ this.deletable = deletable;
+ return this;
+ }
+
+ public Builder deleteIcon(Drawable deleteIcon) {
+ this.deleteIcon = deleteIcon;
+ return this;
+ }
+
+ public Builder deleteIconColor(ColorStateList deleteIconColor) {
+ this.deleteIconColor = deleteIconColor;
+ return this;
+ }
+
+ public Builder backgroundColor(ColorStateList backgroundColor) {
+ this.backgroundColor = backgroundColor;
+ return this;
+ }
+
+ public Builder chip(ChipInterface chip) {
+ this.chip = chip;
+ this.label = chip.getLabel();
+ this.avatarIconDrawable = chip.getAvatarDrawable();
+ this.avatarIconUri = chip.getAvatarUri();
+ return this;
+ }
+
+ public ChipView build() {
+ return newInstance(this);
+ }
+ }
+
+ private static ChipView newInstance(Builder builder) {
+ ChipView chipView = new ChipView(builder.context);
+ chipView.mLabel = builder.label;
+ chipView.mLabelColor = builder.labelColor;
+ chipView.mHasAvatarIcon = builder.hasAvatarIcon;
+ chipView.mAvatarIconUri = builder.avatarIconUri;
+ chipView.mAvatarIconDrawable = builder.avatarIconDrawable;
+ chipView.mDeletable = builder.deletable;
+ chipView.mDeleteIcon = builder.deleteIcon;
+ chipView.mDeleteIconColor = builder.deleteIconColor;
+ chipView.mBackgroundColor = builder.backgroundColor;
+ chipView.mChip = builder.chip;
+ chipView.inflateWithAttributes();
+
+ return chipView;
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/ChipsInput.java b/library/src/main/java/com/pchmn/materialchips/ChipsInput.java
new file mode 100644
index 00000000..ba729fd5
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/ChipsInput.java
@@ -0,0 +1,362 @@
+package com.pchmn.materialchips;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+
+import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager;
+import com.pchmn.materialchips.adapter.ChipsAdapter;
+import com.pchmn.materialchips.model.Chip;
+import com.pchmn.materialchips.model.ChipInterface;
+import com.pchmn.materialchips.util.MyWindowCallback;
+import com.pchmn.materialchips.util.ViewUtil;
+import com.pchmn.materialchips.views.ChipsInputEditText;
+import com.pchmn.materialchips.views.DetailedChipView;
+import com.pchmn.materialchips.views.FilterableListView;
+import com.pchmn.materialchips.views.ScrollViewMaxHeight;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ChipsInput extends ScrollViewMaxHeight {
+
+ private static final String TAG = ChipsInput.class.toString();
+ // context
+ private Context mContext;
+ // xml element
+ @BindView(R2.id.chips_recycler) RecyclerView mRecyclerView;
+ // adapter
+ private ChipsAdapter mChipsAdapter;
+ // attributes
+ private static final int NONE = -1;
+ private String mHint;
+ private ColorStateList mHintColor;
+ private ColorStateList mTextColor;
+ private int mMaxRows = 2;
+ private ColorStateList mChipLabelColor;
+ private boolean mChipHasAvatarIcon = true;
+ private boolean mChipDeletable = false;
+ private Drawable mChipDeleteIcon;
+ private ColorStateList mChipDeleteIconColor;
+ private ColorStateList mChipBackgroundColor;
+ private boolean mShowChipDetailed = true;
+ private ColorStateList mChipDetailedTextColor;
+ private ColorStateList mChipDetailedDeleteIconColor;
+ private ColorStateList mChipDetailedBackgroundColor;
+ private ColorStateList mFilterableListBackgroundColor;
+ private ColorStateList mFilterableListTextColor;
+ // chips listener
+ private List mChipsListenerList = new ArrayList<>();
+ private ChipsListener mChipsListener;
+ // chip list
+ private List extends ChipInterface> mChipList;
+ private FilterableListView mFilterableListView;
+ // chip validator
+ private ChipValidator mChipValidator;
+
+ public ChipsInput(Context context) {
+ super(context);
+ mContext = context;
+ init(null);
+ }
+
+ public ChipsInput(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ init(attrs);
+ }
+
+ /**
+ * Inflate the view according to attributes
+ *
+ * @param attrs the attributes
+ */
+ private void init(AttributeSet attrs) {
+ // inflate layout
+ View rootView = inflate(getContext(), R.layout.chips_input, this);
+ // butter knife
+ ButterKnife.bind(this, rootView);
+
+ // attributes
+ if(attrs != null) {
+ TypedArray a = mContext.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.ChipsInput,
+ 0, 0);
+
+ try {
+ // hint
+ mHint = a.getString(R.styleable.ChipsInput_hint);
+ mHintColor = a.getColorStateList(R.styleable.ChipsInput_hintColor);
+ mTextColor = a.getColorStateList(R.styleable.ChipsInput_textColor);
+ mMaxRows = a.getInteger(R.styleable.ChipsInput_maxRows, 2);
+ setMaxHeight(ViewUtil.dpToPx((40 * mMaxRows) + 8));
+ //setVerticalScrollBarEnabled(true);
+ // chip label color
+ mChipLabelColor = a.getColorStateList(R.styleable.ChipsInput_chip_labelColor);
+ // chip avatar icon
+ mChipHasAvatarIcon = a.getBoolean(R.styleable.ChipsInput_chip_hasAvatarIcon, true);
+ // chip delete icon
+ mChipDeletable = a.getBoolean(R.styleable.ChipsInput_chip_deletable, false);
+ mChipDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_deleteIconColor);
+ int deleteIconId = a.getResourceId(R.styleable.ChipsInput_chip_deleteIcon, NONE);
+ if(deleteIconId != NONE) mChipDeleteIcon = ContextCompat.getDrawable(mContext, deleteIconId);
+ // chip background color
+ mChipBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_backgroundColor);
+ // show chip detailed
+ mShowChipDetailed = a.getBoolean(R.styleable.ChipsInput_showChipDetailed, true);
+ // chip detailed text color
+ mChipDetailedTextColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_textColor);
+ mChipDetailedBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_backgroundColor);
+ mChipDetailedDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_deleteIconColor);
+ // filterable list
+ mFilterableListBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_backgroundColor);
+ mFilterableListTextColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_textColor);
+ }
+ finally {
+ a.recycle();
+ }
+ }
+
+ // adapter
+ mChipsAdapter = new ChipsAdapter(mContext, this, mRecyclerView);
+ ChipsLayoutManager chipsLayoutManager = ChipsLayoutManager.newBuilder(mContext)
+ .setOrientation(ChipsLayoutManager.HORIZONTAL)
+ .build();
+ mRecyclerView.setLayoutManager(chipsLayoutManager);
+ mRecyclerView.setNestedScrollingEnabled(false);
+ mRecyclerView.setAdapter(mChipsAdapter);
+
+ // set window callback
+ // will hide DetailedOpenView and hide keyboard on touch outside
+ android.view.Window.Callback mCallBack = ((Activity) mContext).getWindow().getCallback();
+ ((Activity) mContext).getWindow().setCallback(new MyWindowCallback(mCallBack, ((Activity) mContext)));
+ }
+
+ public void addChip(ChipInterface chip) {
+ mChipsAdapter.addChip(chip);
+ }
+
+ public void addChip(Object id, Drawable icon, String label, String info) {
+ Chip chip = new Chip(id, icon, label, info);
+ mChipsAdapter.addChip(chip);
+ }
+
+ public void addChip(Drawable icon, String label, String info) {
+ Chip chip = new Chip(icon, label, info);
+ mChipsAdapter.addChip(chip);
+ }
+
+ public void addChip(Object id, Uri iconUri, String label, String info) {
+ Chip chip = new Chip(id, iconUri, label, info);
+ mChipsAdapter.addChip(chip);
+ }
+
+ public void addChip(Uri iconUri, String label, String info) {
+ Chip chip = new Chip(iconUri, label, info);
+ mChipsAdapter.addChip(chip);
+ }
+
+ public void addChip(String label, String info) {
+ ChipInterface chip = new Chip(label, info);
+ mChipsAdapter.addChip(chip);
+ }
+
+ public void removeChip(ChipInterface chip) {
+ mChipsAdapter.removeChip(chip);
+ }
+
+ public void removeChipById(Object id) {
+ mChipsAdapter.removeChipById(id);
+ }
+
+ public void removeChipByLabel(String label) {
+ mChipsAdapter.removeChipByLabel(label);
+ }
+
+ public void removeChipByInfo(String info) {
+ mChipsAdapter.removeChipByInfo(info);
+ }
+
+ public ChipView getChipView() {
+ int padding = ViewUtil.dpToPx(4);
+ ChipView chipView = new ChipView.Builder(mContext)
+ .labelColor(mChipLabelColor)
+ .hasAvatarIcon(mChipHasAvatarIcon)
+ .deletable(mChipDeletable)
+ .deleteIcon(mChipDeleteIcon)
+ .deleteIconColor(mChipDeleteIconColor)
+ .backgroundColor(mChipBackgroundColor)
+ .build();
+
+ chipView.setPadding(padding, padding, padding, padding);
+
+ return chipView;
+ }
+
+ public ChipsInputEditText getEditText() {
+ ChipsInputEditText editText = new ChipsInputEditText(mContext);
+ if(mHintColor != null)
+ editText.setHintTextColor(mHintColor);
+ if(mTextColor != null)
+ editText.setTextColor(mTextColor);
+
+ return editText;
+ }
+
+ public DetailedChipView getDetailedChipView(ChipInterface chip) {
+ return new DetailedChipView.Builder(mContext)
+ .chip(chip)
+ .textColor(mChipDetailedTextColor)
+ .backgroundColor(mChipDetailedBackgroundColor)
+ .deleteIconColor(mChipDetailedDeleteIconColor)
+ .build();
+ }
+
+ public void addChipsListener(ChipsListener chipsListener) {
+ mChipsListenerList.add(chipsListener);
+ mChipsListener = chipsListener;
+ }
+
+ public void onChipAdded(ChipInterface chip, int size) {
+ for(ChipsListener chipsListener: mChipsListenerList) {
+ chipsListener.onChipAdded(chip, size);
+ }
+ }
+
+ public void onChipRemoved(ChipInterface chip, int size) {
+ for(ChipsListener chipsListener: mChipsListenerList) {
+ chipsListener.onChipRemoved(chip, size);
+ }
+ }
+
+ public void onTextChanged(CharSequence text) {
+ if(mChipsListener != null) {
+ for(ChipsListener chipsListener: mChipsListenerList) {
+ chipsListener.onTextChanged(text);
+ }
+ // show filterable list
+ if(mFilterableListView != null) {
+ if(text.length() > 0)
+ mFilterableListView.filterList(text);
+ else
+ mFilterableListView.fadeOut();
+ }
+ }
+ }
+
+ public List extends ChipInterface> getSelectedChipList() {
+ return mChipsAdapter.getChipList();
+ }
+
+ public String getHint() {
+ return mHint;
+ }
+
+ public void setHint(String mHint) {
+ this.mHint = mHint;
+ }
+
+ public void setHintColor(ColorStateList mHintColor) {
+ this.mHintColor = mHintColor;
+ }
+
+ public void setTextColor(ColorStateList mTextColor) {
+ this.mTextColor = mTextColor;
+ }
+
+ public ChipsInput setMaxRows(int mMaxRows) {
+ this.mMaxRows = mMaxRows;
+ return this;
+ }
+
+ public void setChipLabelColor(ColorStateList mLabelColor) {
+ this.mChipLabelColor = mLabelColor;
+ }
+
+ public void setChipHasAvatarIcon(boolean mHasAvatarIcon) {
+ this.mChipHasAvatarIcon = mHasAvatarIcon;
+ }
+
+ public boolean chipHasAvatarIcon() {
+ return mChipHasAvatarIcon;
+ }
+
+ public void setChipDeletable(boolean mDeletable) {
+ this.mChipDeletable = mDeletable;
+ }
+
+ public void setChipDeleteIcon(Drawable mDeleteIcon) {
+ this.mChipDeleteIcon = mDeleteIcon;
+ }
+
+ public void setChipDeleteIconColor(ColorStateList mDeleteIconColor) {
+ this.mChipDeleteIconColor = mDeleteIconColor;
+ }
+
+ public void setChipBackgroundColor(ColorStateList mBackgroundColor) {
+ this.mChipBackgroundColor = mBackgroundColor;
+ }
+
+ public ChipsInput setShowChipDetailed(boolean mShowChipDetailed) {
+ this.mShowChipDetailed = mShowChipDetailed;
+ return this;
+ }
+
+ public boolean isShowChipDetailed() {
+ return mShowChipDetailed;
+ }
+
+ public void setChipDetailedTextColor(ColorStateList mChipDetailedTextColor) {
+ this.mChipDetailedTextColor = mChipDetailedTextColor;
+ }
+
+ public void setChipDetailedDeleteIconColor(ColorStateList mChipDetailedDeleteIconColor) {
+ this.mChipDetailedDeleteIconColor = mChipDetailedDeleteIconColor;
+ }
+
+ public void setChipDetailedBackgroundColor(ColorStateList mChipDetailedBackgroundColor) {
+ this.mChipDetailedBackgroundColor = mChipDetailedBackgroundColor;
+ }
+
+ public void setFilterableList(List extends ChipInterface> list) {
+ mChipList = list;
+ mFilterableListView = new FilterableListView(mContext);
+ mFilterableListView.build(mChipList, this, mFilterableListBackgroundColor, mFilterableListTextColor);
+ mChipsAdapter.setFilterableListView(mFilterableListView);
+ }
+
+ public List extends ChipInterface> getFilterableList() {
+ return mChipList;
+ }
+
+ public ChipValidator getChipValidator() {
+ return mChipValidator;
+ }
+
+ public void setChipValidator(ChipValidator mChipValidator) {
+ this.mChipValidator = mChipValidator;
+ }
+
+ public interface ChipsListener {
+ void onChipAdded(ChipInterface chip, int newSize);
+ void onChipRemoved(ChipInterface chip, int newSize);
+ void onTextChanged(CharSequence text);
+ }
+
+ public interface ChipValidator {
+ boolean areEquals(ChipInterface chip1, ChipInterface chip2);
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/library/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java
new file mode 100644
index 00000000..c38ef36d
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java
@@ -0,0 +1,392 @@
+package com.pchmn.materialchips.adapter;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+
+import com.pchmn.materialchips.ChipView;
+import com.pchmn.materialchips.ChipsInput;
+import com.pchmn.materialchips.model.ChipInterface;
+import com.pchmn.materialchips.views.ChipsInputEditText;
+import com.pchmn.materialchips.views.DetailedChipView;
+import com.pchmn.materialchips.model.Chip;
+import com.pchmn.materialchips.util.ViewUtil;
+import com.pchmn.materialchips.views.FilterableListView;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+public class ChipsAdapter extends RecyclerView.Adapter {
+
+ private static final String TAG = ChipsAdapter.class.toString();
+ private static final int TYPE_EDIT_TEXT = 0;
+ private static final int TYPE_ITEM = 1;
+ private Context mContext;
+ private ChipsInput mChipsInput;
+ private List mChipList = new ArrayList<>();
+ private String mHintLabel;
+ private ChipsInputEditText mEditText;
+ private RecyclerView mRecycler;
+
+ public ChipsAdapter(Context context, ChipsInput chipsInput, RecyclerView recycler) {
+ mContext = context;
+ mChipsInput = chipsInput;
+ mRecycler = recycler;
+ mHintLabel = mChipsInput.getHint();
+ mEditText = mChipsInput.getEditText();
+ initEditText();
+ }
+
+ private class ItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ChipView chipView;
+
+ ItemViewHolder(View view) {
+ super(view);
+ chipView = (ChipView) view;
+ }
+ }
+
+ private class EditTextViewHolder extends RecyclerView.ViewHolder {
+
+ private final EditText editText;
+
+ EditTextViewHolder(View view) {
+ super(view);
+ editText = (EditText) view;
+ }
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if(viewType == TYPE_EDIT_TEXT)
+ return new EditTextViewHolder(mEditText);
+ else
+ return new ItemViewHolder(mChipsInput.getChipView());
+
+ }
+
+ @Override
+ public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
+ // edit text
+ if(position == mChipList.size()) {
+ if(mChipList.size() == 0)
+ mEditText.setHint(mHintLabel);
+
+ // auto fit edit text
+ autofitEditText();
+ }
+ // chip
+ else if(getItemCount() > 1) {
+ ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
+ itemViewHolder.chipView.inflate(getItem(position));
+ // handle click
+ handleClickOnEditText(itemViewHolder.chipView, position);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mChipList.size() + 1;
+ }
+
+ private ChipInterface getItem(int position) {
+ return mChipList.get(position);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position == mChipList.size())
+ return TYPE_EDIT_TEXT;
+
+ return TYPE_ITEM;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mChipList.get(position).hashCode();
+ }
+
+ private void initEditText() {
+ mEditText.setLayoutParams(new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mEditText.setHint(mHintLabel);
+ mEditText.setBackgroundResource(android.R.color.transparent);
+ // prevent fullscreen on landscape
+ mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+ mEditText.setPrivateImeOptions("nm");
+ // no suggestion
+ mEditText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+
+ // handle back space
+ mEditText.setOnKeyListener(new View.OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // backspace
+ if(event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
+ // remove last chip
+ if(mChipList.size() > 0)
+ removeChip(mChipList.size() - 1);
+ }
+ return false;
+ }
+ });
+
+ // text changed
+ mEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mChipsInput.onTextChanged(s);
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+ });
+ }
+
+ private void autofitEditText() {
+ // min width of edit text = 50 dp
+ ViewGroup.LayoutParams params = mEditText.getLayoutParams();
+ params.width = ViewUtil.dpToPx(50);
+ mEditText.setLayoutParams(params);
+
+ // listen to change in the tree
+ mEditText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+
+ @Override
+ public void onGlobalLayout() {
+ // get right of recycler and left of edit text
+ int right = mRecycler.getRight();
+ int left = mEditText.getLeft();
+
+ // edit text will fill the space
+ ViewGroup.LayoutParams params = mEditText.getLayoutParams();
+ params.width = right - left - ViewUtil.dpToPx(8);
+ mEditText.setLayoutParams(params);
+
+ // request focus
+ mEditText.requestFocus();
+
+ // remove the listener:
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ mEditText.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ } else {
+ mEditText.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ }
+
+ });
+ }
+
+ private void handleClickOnEditText(ChipView chipView, final int position) {
+ // delete chip
+ chipView.setOnDeleteClicked(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ removeChip(position);
+ }
+ });
+
+ // show detailed chip
+ if(mChipsInput.isShowChipDetailed()) {
+ chipView.setOnChipClicked(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // get chip position
+ int[] coord = new int[2];
+ v.getLocationInWindow(coord);
+
+ final DetailedChipView detailedChipView = mChipsInput.getDetailedChipView(getItem(position));
+ setDetailedChipViewPosition(detailedChipView, coord);
+
+ // delete button
+ detailedChipView.setOnDeleteClicked(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ removeChip(position);
+ detailedChipView.fadeOut();
+ }
+ });
+ }
+ });
+ }
+ }
+
+ private void setDetailedChipViewPosition(DetailedChipView detailedChipView, int[] coord) {
+ // window width
+ ViewGroup rootView = (ViewGroup) mRecycler.getRootView();
+ int windowWidth = ViewUtil.getWindowWidth(mContext);
+
+ // chip size
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
+ ViewUtil.dpToPx(300),
+ ViewUtil.dpToPx(100));
+
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+
+ // align left window
+ if(coord[0] <= 0) {
+ layoutParams.leftMargin = 0;
+ layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13);
+ detailedChipView.alignLeft();
+ }
+ // align right
+ else if(coord[0] + ViewUtil.dpToPx(300) > windowWidth + ViewUtil.dpToPx(13)) {
+ layoutParams.leftMargin = windowWidth - ViewUtil.dpToPx(300);
+ layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13);
+ detailedChipView.alignRight();
+ }
+ // same position as chip
+ else {
+ layoutParams.leftMargin = coord[0] - ViewUtil.dpToPx(13);
+ layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13);
+ }
+
+ // show view
+ rootView.addView(detailedChipView, layoutParams);
+ detailedChipView.fadeIn();
+ }
+
+ public void setFilterableListView(FilterableListView filterableListView) {
+ if(mEditText != null)
+ mEditText.setFilterableListView(filterableListView);
+ }
+
+ public void addChip(ChipInterface chip) {
+ if(!listContains(mChipList, chip)) {
+ mChipList.add(chip);
+ // notify listener
+ mChipsInput.onChipAdded(chip, mChipList.size());
+ // hide hint
+ mEditText.setHint(null);
+ // reset text
+ mEditText.setText(null);
+ // refresh data
+ notifyItemInserted(mChipList.size());
+ }
+ }
+
+ public void removeChip(ChipInterface chip) {
+ int position = mChipList.indexOf(chip);
+ mChipList.remove(position);
+ // notify listener
+ notifyItemRangeChanged(position, getItemCount());
+ // if 0 chip
+ if (mChipList.size() == 0)
+ mEditText.setHint(mHintLabel);
+ // refresh data
+ notifyDataSetChanged();
+ }
+
+ public void removeChip(int position) {
+ ChipInterface chip = mChipList.get(position);
+ // remove contact
+ mChipList.remove(position);
+ // notify listener
+ mChipsInput.onChipRemoved(chip, mChipList.size());
+ // if 0 chip
+ if (mChipList.size() == 0)
+ mEditText.setHint(mHintLabel);
+ // refresh data
+ notifyDataSetChanged();
+ }
+
+ public void removeChipById(Object id) {
+ for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) {
+ ChipInterface chip = iter.next();
+ if (chip.getId() != null && chip.getId().equals(id)) {
+ // remove chip
+ iter.remove();
+ // notify listener
+ mChipsInput.onChipRemoved(chip, mChipList.size());
+ }
+ }
+ // if 0 chip
+ if (mChipList.size() == 0)
+ mEditText.setHint(mHintLabel);
+ // refresh data
+ notifyDataSetChanged();
+ }
+
+ public void removeChipByLabel(String label) {
+ for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) {
+ ChipInterface chip = iter.next();
+ if (chip.getLabel().equals(label)) {
+ // remove chip
+ iter.remove();
+ // notify listener
+ mChipsInput.onChipRemoved(chip, mChipList.size());
+ }
+ }
+ // if 0 chip
+ if (mChipList.size() == 0)
+ mEditText.setHint(mHintLabel);
+ // refresh data
+ notifyDataSetChanged();
+ }
+
+ public void removeChipByInfo(String info) {
+ for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) {
+ ChipInterface chip = iter.next();
+ if (chip.getInfo() != null && chip.getInfo().equals(info)) {
+ // remove chip
+ iter.remove();
+ // notify listener
+ mChipsInput.onChipRemoved(chip, mChipList.size());
+ }
+ }
+ // if 0 chip
+ if (mChipList.size() == 0)
+ mEditText.setHint(mHintLabel);
+ // refresh data
+ notifyDataSetChanged();
+ }
+
+ public List getChipList() {
+ return mChipList;
+ }
+
+ private boolean listContains(List contactList, ChipInterface chip) {
+ Log.e(TAG, mChipsInput.getChipValidator() == null ? "null": "not null");
+
+ if(mChipsInput.getChipValidator() != null) {
+ for(ChipInterface item: contactList) {
+ if(mChipsInput.getChipValidator().areEquals(item, chip))
+ return true;
+ }
+ }
+ else {
+ for(ChipInterface item: contactList) {
+ if(chip.getId() != null && chip.getId().equals(item.getId()))
+ return true;
+ if(chip.getLabel().equals(item.getLabel()))
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java b/library/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java
new file mode 100644
index 00000000..b5d8903b
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java
@@ -0,0 +1,275 @@
+package com.pchmn.materialchips.adapter;
+
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.TextView;
+
+import com.pchmn.materialchips.ChipsInput;
+import com.pchmn.materialchips.R;
+import com.pchmn.materialchips.model.ChipInterface;
+import com.pchmn.materialchips.util.ColorUtil;
+import com.pchmn.materialchips.util.LetterTileProvider;
+import com.pchmn.materialchips.util.ViewUtil;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import de.hdodenhof.circleimageview.CircleImageView;
+
+import static android.view.View.GONE;
+
+public class FilterableAdapter extends RecyclerView.Adapter implements Filterable {
+
+ private static final String TAG = FilterableAdapter.class.toString();
+ // context
+ private Context mContext;
+ // list
+ private List mOriginalList = new ArrayList<>();
+ private List mChipList = new ArrayList<>();
+ private List mFilteredList = new ArrayList<>();
+ private ChipFilter mFilter;
+ private ChipsInput mChipsInput;
+ private LetterTileProvider mLetterTileProvider;
+ private ColorStateList mBackgroundColor;
+ private ColorStateList mTextColor;
+ // recycler
+ private RecyclerView mRecyclerView;
+
+
+ public FilterableAdapter(Context context,
+ RecyclerView recyclerView,
+ List extends ChipInterface> chipList,
+ ChipsInput chipsInput,
+ ColorStateList backgroundColor,
+ ColorStateList textColor) {
+ mContext = context;
+ mRecyclerView = recyclerView;
+ Collections.sort(chipList, new Comparator() {
+ @Override
+ public int compare(ChipInterface o1, ChipInterface o2) {
+ Collator collator = Collator.getInstance(Locale.getDefault());
+ collator.setStrength(Collator.PRIMARY);
+ return collator.compare(o1.getLabel(), o2.getLabel());
+ }
+ });
+ mOriginalList.addAll(chipList);
+ mChipList.addAll(chipList);
+ mFilteredList.addAll(chipList);
+ mLetterTileProvider = new LetterTileProvider(mContext);
+ mBackgroundColor = backgroundColor;
+ mTextColor = textColor;
+ mChipsInput = chipsInput;
+
+ mChipsInput.addChipsListener(new ChipsInput.ChipsListener() {
+ @Override
+ public void onChipAdded(ChipInterface chip, int newSize) {
+ removeChip(chip);
+ }
+
+ @Override
+ public void onChipRemoved(ChipInterface chip, int newSize) {
+ addChip(chip);
+ }
+
+ @Override
+ public void onTextChanged(CharSequence text) {
+ mRecyclerView.scrollToPosition(0);
+ }
+ });
+ }
+
+ private class ItemViewHolder extends RecyclerView.ViewHolder {
+
+ private CircleImageView mAvatar;
+ private TextView mLabel;
+ private TextView mInfo;
+
+ ItemViewHolder(View view) {
+ super(view);
+ mAvatar = (CircleImageView) view.findViewById(R.id.avatar);
+ mLabel = (TextView) view.findViewById(R.id.label);
+ mInfo = (TextView) view.findViewById(R.id.info);
+ }
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(mContext).inflate(R.layout.item_list_filterable, parent, false);
+ return new ItemViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
+ final ChipInterface chip = getItem(position);
+
+ // avatar
+ if(mChipsInput.chipHasAvatarIcon() && chip.getAvatarUri() != null) {
+ itemViewHolder.mAvatar.setVisibility(View.VISIBLE);
+ itemViewHolder.mAvatar.setImageURI(chip.getAvatarUri());
+ }
+ else if(mChipsInput.chipHasAvatarIcon() && chip.getAvatarDrawable() != null) {
+ itemViewHolder.mAvatar.setVisibility(View.VISIBLE);
+ itemViewHolder.mAvatar.setImageDrawable(chip.getAvatarDrawable());
+ }
+ else if(mChipsInput.chipHasAvatarIcon()) {
+ itemViewHolder.mAvatar.setVisibility(View.VISIBLE);
+ itemViewHolder.mAvatar.setImageBitmap(mLetterTileProvider.getLetterTile(chip.getLabel()));
+ }
+ else {
+ itemViewHolder.mAvatar.setVisibility(GONE);
+ }
+
+ // label
+ itemViewHolder.mLabel.setText(chip.getLabel());
+
+ // info
+ if(chip.getInfo() != null) {
+ itemViewHolder.mInfo.setVisibility(View.VISIBLE);
+ itemViewHolder.mInfo.setText(chip.getInfo());
+ }
+ else {
+ itemViewHolder.mInfo.setVisibility(GONE);
+ }
+
+ // colors
+ if(mBackgroundColor != null)
+ itemViewHolder.itemView.getBackground().setColorFilter(mBackgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP);
+ if(mTextColor != null) {
+ itemViewHolder.mLabel.setTextColor(mTextColor);
+ itemViewHolder.mInfo.setTextColor(ColorUtil.alpha(mTextColor.getDefaultColor(), 150));
+ }
+
+ // onclick
+ itemViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if(mChipsInput != null)
+ mChipsInput.addChip(chip);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mFilteredList.size();
+ }
+
+ private ChipInterface getItem(int position) {
+ return mFilteredList.get(position);
+ }
+
+ @Override
+ public Filter getFilter() {
+ if(mFilter == null)
+ mFilter = new ChipFilter(this, mChipList);
+ return mFilter;
+ }
+
+ private class ChipFilter extends Filter {
+
+ private FilterableAdapter adapter;
+ private List originalList;
+ private List filteredList;
+
+ public ChipFilter(FilterableAdapter adapter, List originalList) {
+ super();
+ this.adapter = adapter;
+ this.originalList = originalList;
+ this.filteredList = new ArrayList<>();
+ }
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ filteredList.clear();
+ FilterResults results = new FilterResults();
+ if (constraint.length() == 0) {
+ filteredList.addAll(originalList);
+ } else {
+ final String filterPattern = constraint.toString().toLowerCase().trim();
+ for (ChipInterface chip : originalList) {
+ if (chip.getLabel().toLowerCase().contains(filterPattern)) {
+ filteredList.add(chip);
+ }
+ else if(chip.getInfo() != null && chip.getInfo().toLowerCase().replaceAll("\\s", "").contains(filterPattern)) {
+ filteredList.add(chip);
+ }
+ }
+ }
+
+ results.values = filteredList;
+ results.count = filteredList.size();
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ mFilteredList.clear();
+ mFilteredList.addAll((ArrayList) results.values);
+ notifyDataSetChanged();
+ }
+ }
+
+ private void removeChip(ChipInterface chip) {
+ int position = mFilteredList.indexOf(chip);
+ if (position >= 0)
+ mFilteredList.remove(position);
+
+ position = mChipList.indexOf(chip);
+ if(position >= 0)
+ mChipList.remove(position);
+
+ notifyDataSetChanged();
+ }
+
+ private void addChip(ChipInterface chip) {
+ if(contains(chip)) {
+ mChipList.add(chip);
+ mFilteredList.add(chip);
+ // sort original list
+ Collections.sort(mChipList, new Comparator() {
+ @Override
+ public int compare(ChipInterface o1, ChipInterface o2) {
+ Collator collator = Collator.getInstance(Locale.getDefault());
+ collator.setStrength(Collator.PRIMARY);
+ return collator.compare(o1.getLabel(), o2.getLabel());
+ }
+ });
+ // sort filtered list
+ Collections.sort(mFilteredList, new Comparator() {
+ @Override
+ public int compare(ChipInterface o1, ChipInterface o2) {
+ Collator collator = Collator.getInstance(Locale.getDefault());
+ collator.setStrength(Collator.PRIMARY);
+ return collator.compare(o1.getLabel(), o2.getLabel());
+ }
+ });
+
+ notifyDataSetChanged();
+ }
+ }
+
+ private boolean contains(ChipInterface chip) {
+ for(ChipInterface item: mOriginalList) {
+ if(item.equals(chip))
+ return true;
+ }
+ return false;
+ }
+
+
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/model/Chip.java b/library/src/main/java/com/pchmn/materialchips/model/Chip.java
new file mode 100644
index 00000000..892e9366
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/model/Chip.java
@@ -0,0 +1,78 @@
+package com.pchmn.materialchips.model;
+
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public class Chip implements ChipInterface {
+
+ private Object id;
+ private Uri avatarUri;
+ private Drawable avatarDrawable;
+ private String label;
+ private String info;
+
+ public Chip(@NonNull Object id, @Nullable Uri avatarUri, @NonNull String label, @Nullable String info) {
+ this.id = id;
+ this.avatarUri = avatarUri;
+ this.label = label;
+ this.info = info;
+ }
+
+ public Chip(@NonNull Object id, @Nullable Drawable avatarDrawable, @NonNull String label, @Nullable String info) {
+ this.id = id;
+ this.avatarDrawable = avatarDrawable;
+ this.label = label;
+ this.info = info;
+ }
+
+ public Chip(@Nullable Uri avatarUri, @NonNull String label, @Nullable String info) {
+ this.avatarUri = avatarUri;
+ this.label = label;
+ this.info = info;
+ }
+
+ public Chip(@Nullable Drawable avatarDrawable, @NonNull String label, @Nullable String info) {
+ this.avatarDrawable = avatarDrawable;
+ this.label = label;
+ this.info = info;
+ }
+
+ public Chip(@NonNull Object id, @NonNull String label, @Nullable String info) {
+ this.id = id;
+ this.label = label;
+ this.info = info;
+ }
+
+ public Chip(@NonNull String label, @Nullable String info) {
+ this.label = label;
+ this.info = info;
+ }
+
+ @Override
+ public Object getId() {
+ return id;
+ }
+
+ @Override
+ public Uri getAvatarUri() {
+ return avatarUri;
+ }
+
+ @Override
+ public Drawable getAvatarDrawable() {
+ return avatarDrawable;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+ @Override
+ public String getInfo() {
+ return info;
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/model/ChipInterface.java b/library/src/main/java/com/pchmn/materialchips/model/ChipInterface.java
new file mode 100644
index 00000000..e942bfb7
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/model/ChipInterface.java
@@ -0,0 +1,14 @@
+package com.pchmn.materialchips.model;
+
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+public interface ChipInterface {
+
+ Object getId();
+ Uri getAvatarUri();
+ Drawable getAvatarDrawable();
+ String getLabel();
+ String getInfo();
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/util/ColorUtil.java b/library/src/main/java/com/pchmn/materialchips/util/ColorUtil.java
new file mode 100644
index 00000000..c1b118c1
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/util/ColorUtil.java
@@ -0,0 +1,38 @@
+package com.pchmn.materialchips.util;
+
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.util.TypedValue;
+
+import com.pchmn.materialchips.R;
+
+public class ColorUtil {
+
+ public static int lighter(int color, float factor) {
+ int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255);
+ int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255);
+ int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255);
+ return Color.argb(Color.alpha(color), red, green, blue);
+ }
+
+ public static int lighter(ColorStateList color, float factor) {
+ return lighter(color.getDefaultColor(), factor);
+ }
+
+ public static int alpha(int color, int alpha) {
+ return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ public static boolean isColorDark(int color){
+ double darkness = 1 - (0.2126*Color.red(color) + 0.7152*Color.green(color) + 0.0722*Color.blue(color))/255;
+ return darkness >= 0.5;
+ }
+
+ public static int getThemeAccentColor (final Context context) {
+ final TypedValue value = new TypedValue ();
+ context.getTheme ().resolveAttribute (R.attr.colorAccent, value, true);
+ return value.data;
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java b/library/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java
new file mode 100644
index 00000000..3d0c736d
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java
@@ -0,0 +1,214 @@
+package com.pchmn.materialchips.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.text.TextPaint;
+import android.util.Log;
+
+import com.pchmn.materialchips.R;
+
+/**
+ * Used to create a {@link Bitmap} that contains a letter used in the English
+ * alphabet or digit, if there is no letter or digit available, a default image
+ * is shown instead
+ */
+public class LetterTileProvider {
+
+ /** The number of available tile colors (see R.array.letter_tile_colors) */
+ private static final int NUM_OF_TILE_COLORS = 8;
+
+ /** The {@link TextPaint} used to draw the letter onto the tile */
+ private final TextPaint mPaint = new TextPaint();
+ /** The bounds that enclose the letter */
+ private final Rect mBounds = new Rect();
+ /** The {@link Canvas} to draw on */
+ private final Canvas mCanvas = new Canvas();
+ /** The first char of the name being displayed */
+ private final char[] mFirstChar = new char[1];
+
+ /** The background colors of the tile */
+ private final TypedArray mColors;
+ /** The font size used to display the letter */
+ private final int mTileLetterFontSize;
+ /** The default image to display */
+ private final Bitmap mDefaultBitmap;
+
+ /** Width */
+ private final int mWidth;
+ /** Height */
+ private final int mHeight;
+
+ /**
+ * Constructor for LetterTileProvider
+ *
+ * @param context The {@link Context} to use
+ */
+ public LetterTileProvider(Context context) {
+ final Resources res = context.getResources();
+
+ mPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
+ mPaint.setColor(Color.WHITE);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ mPaint.setAntiAlias(true);
+
+ mColors = res.obtainTypedArray(R.array.letter_tile_colors);
+ mTileLetterFontSize = res.getDimensionPixelSize(R.dimen.tile_letter_font_size);
+
+ //mDefaultBitmap = BitmapFactory.decodeResource(res, android.R.drawable.);
+ mDefaultBitmap = drawableToBitmap(ContextCompat.getDrawable(context, R.drawable.ic_person_white_24dp));
+ mWidth = res.getDimensionPixelSize(R.dimen.letter_tile_size);
+ mHeight = res.getDimensionPixelSize(R.dimen.letter_tile_size);
+ }
+
+ /**
+ * @param displayName The name used to create the letter for the tile
+ * @return A {@link Bitmap} that contains a letter used in the English
+ * alphabet or digit, if there is no letter or digit available, a
+ * default image is shown instead
+ */
+ public Bitmap getLetterTile(String displayName) {
+ if(displayName.length() == 0)
+ return null;
+
+ final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
+ final char firstChar = displayName.charAt(0);
+
+ final Canvas c = mCanvas;
+ c.setBitmap(bitmap);
+ c.drawColor(pickColor(displayName));
+
+ if (isLetterOrDigit(firstChar)) {
+ mFirstChar[0] = Character.toUpperCase(firstChar);
+ mPaint.setTextSize(mTileLetterFontSize);
+ mPaint.getTextBounds(mFirstChar, 0, 1, mBounds);
+ c.drawText(mFirstChar, 0, 1, mWidth / 2, mHeight / 2
+ + (mBounds.bottom - mBounds.top) / 2, mPaint);
+ } else {
+ // (32 - 24) / 2 = 4
+ c.drawBitmap(mDefaultBitmap, ViewUtil.dpToPx(4), ViewUtil.dpToPx(4), null);
+ }
+ return bitmap;
+ }
+
+ /**
+ * @param displayName The name used to create the letter for the tile
+ * @return A circular {@link Bitmap} that contains a letter used in the English
+ * alphabet or digit, if there is no letter or digit available, a
+ * default image is shown instead
+ */
+ public Bitmap getCircularLetterTile(String displayName) {
+ final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
+ final char firstChar = displayName.charAt(0);
+
+ final Canvas c = mCanvas;
+ c.setBitmap(bitmap);
+ c.drawColor(pickColor(displayName));
+
+ if (isLetterOrDigit(firstChar)) {
+ mFirstChar[0] = Character.toUpperCase(firstChar);
+ mPaint.setTextSize(mTileLetterFontSize);
+ mPaint.getTextBounds(mFirstChar, 0, 1, mBounds);
+ c.drawText(mFirstChar, 0, 1, mWidth / 2, mHeight / 2
+ + (mBounds.bottom - mBounds.top) / 2, mPaint);
+ } else {
+ // (32 - 24) / 2 = 4
+ c.drawBitmap(mDefaultBitmap, ViewUtil.dpToPx(4), ViewUtil.dpToPx(4), null);
+ }
+ return getCircularBitmap(bitmap);
+ }
+
+ /**
+ * @param c The char to check
+ * @return True if c is in the English alphabet or is a digit,
+ * false otherwise
+ */
+ private static boolean isLetterOrDigit(char c) {
+ //return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9';
+ return Character.isLetterOrDigit(c);
+ }
+
+ /**
+ * @param key The key used to generate the tile color
+ * @return A new or previously chosen color for key used as the
+ * tile background color
+ */
+ private int pickColor(String key) {
+ // String.hashCode() is not supposed to change across java versions, so
+ // this should guarantee the same key always maps to the same color
+ final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS;
+ try {
+ return mColors.getColor(color, Color.BLACK);
+ } finally {
+ // bug with recycler view
+ //mColors.recycle();
+ }
+ }
+
+ private Bitmap getCircularBitmap(Bitmap bitmap) {
+ Bitmap output;
+
+ if (bitmap.getWidth() > bitmap.getHeight()) {
+ output = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+ } else {
+ output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getWidth(), Bitmap.Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(output);
+
+ final int color = 0xff424242;
+ final Paint paint = new Paint();
+ final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+
+ float r = 0;
+
+ if (bitmap.getWidth() > bitmap.getHeight()) {
+ r = bitmap.getHeight() / 2;
+ } else {
+ r = bitmap.getWidth() / 2;
+ }
+
+ paint.setAntiAlias(true);
+ canvas.drawARGB(0, 0, 0, 0);
+ paint.setColor(color);
+ canvas.drawCircle(r, r, r, paint);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+ canvas.drawBitmap(bitmap, rect, rect, paint);
+ return output;
+ }
+
+ public static Bitmap drawableToBitmap (Drawable drawable) {
+ Bitmap bitmap = null;
+
+ if (drawable instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if(bitmapDrawable.getBitmap() != null) {
+ return bitmapDrawable.getBitmap();
+ }
+ }
+
+ if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
+ } else {
+ bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
+
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java b/library/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java
new file mode 100644
index 00000000..92435080
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java
@@ -0,0 +1,173 @@
+package com.pchmn.materialchips.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SearchEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import com.pchmn.materialchips.views.ChipsInputEditText;
+import com.pchmn.materialchips.views.DetailedChipView;
+
+public class MyWindowCallback implements Window.Callback {
+
+ private Window.Callback mLocalCallback;
+ private Activity mActivity;
+
+ public MyWindowCallback(Window.Callback localCallback, Activity activity) {
+ mLocalCallback = localCallback;
+ mActivity = activity;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent keyEvent) {
+ return mLocalCallback.dispatchKeyEvent(keyEvent);
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent keyEvent) {
+ return mLocalCallback.dispatchKeyShortcutEvent(keyEvent);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent motionEvent) {
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+ View v = mActivity.getCurrentFocus();
+ if(v instanceof DetailedChipView) {
+ Rect outRect = new Rect();
+ v.getGlobalVisibleRect(outRect);
+ if (!outRect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
+ ((DetailedChipView) v).fadeOut();
+ }
+ }
+ if (v instanceof ChipsInputEditText) {
+ Rect outRect = new Rect();
+ v.getGlobalVisibleRect(outRect);
+ if (!outRect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY())
+ && !((ChipsInputEditText) v).isFilterableListVisible()) {
+ InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+ }
+ }
+ return mLocalCallback.dispatchTouchEvent(motionEvent);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent motionEvent) {
+ return mLocalCallback.dispatchTrackballEvent(motionEvent);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) {
+ return mLocalCallback.dispatchGenericMotionEvent(motionEvent);
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
+ return mLocalCallback.dispatchPopulateAccessibilityEvent(accessibilityEvent);
+ }
+
+ @Nullable
+ @Override
+ public View onCreatePanelView(int i) {
+ return mLocalCallback.onCreatePanelView(i);
+ }
+
+ @Override
+ public boolean onCreatePanelMenu(int i, Menu menu) {
+ return mLocalCallback.onCreatePanelMenu(i, menu);
+ }
+
+ @Override
+ public boolean onPreparePanel(int i, View view, Menu menu) {
+ return mLocalCallback.onPreparePanel(i, view, menu);
+ }
+
+ @Override
+ public boolean onMenuOpened(int i, Menu menu) {
+ return mLocalCallback.onMenuOpened(i, menu);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int i, MenuItem menuItem) {
+ return mLocalCallback.onMenuItemSelected(i, menuItem);
+ }
+
+ @Override
+ public void onWindowAttributesChanged(WindowManager.LayoutParams layoutParams) {
+ mLocalCallback.onWindowAttributesChanged(layoutParams);
+ }
+
+ @Override
+ public void onContentChanged() {
+ mLocalCallback.onContentChanged();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean b) {
+ mLocalCallback.onWindowFocusChanged(b);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ mLocalCallback.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mLocalCallback.onDetachedFromWindow();
+ }
+
+ @Override
+ public void onPanelClosed(int i, Menu menu) {
+ mLocalCallback.onPanelClosed(i, menu);
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ return mLocalCallback.onSearchRequested();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ @Override
+ public boolean onSearchRequested(SearchEvent searchEvent) {
+ return mLocalCallback.onSearchRequested(searchEvent);
+ }
+
+ @Nullable
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+ return mLocalCallback.onWindowStartingActionMode(callback);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ @Nullable
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int i) {
+ return mLocalCallback.onWindowStartingActionMode(callback, i);
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode actionMode) {
+ mLocalCallback.onActionModeStarted(actionMode);
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode actionMode) {
+ mLocalCallback.onActionModeFinished(actionMode);
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/util/ViewUtil.java b/library/src/main/java/com/pchmn/materialchips/util/ViewUtil.java
new file mode 100644
index 00000000..1cae87da
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/util/ViewUtil.java
@@ -0,0 +1,81 @@
+package com.pchmn.materialchips.util;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+public class ViewUtil {
+
+ private static int windowWidthPortrait = 0;
+ private static int windowWidthLandscape = 0;
+
+ public static int dpToPx(int dp) {
+ return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
+ }
+
+ public static int pxToDp(int px) {
+ return (int) (px / Resources.getSystem().getDisplayMetrics().density);
+ }
+
+ public static int getWindowWidth(Context context) {
+ if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
+ return getWindowWidthPortrait(context);
+ }
+ else {
+ return getWindowWidthLandscape(context);
+ }
+ }
+
+ private static int getWindowWidthPortrait(Context context) {
+ if(windowWidthPortrait == 0) {
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ windowWidthPortrait = metrics.widthPixels;
+ }
+
+ return windowWidthPortrait;
+ }
+
+ private static int getWindowWidthLandscape(Context context) {
+ if(windowWidthLandscape == 0) {
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ windowWidthLandscape = metrics.widthPixels;
+ }
+
+ return windowWidthLandscape;
+ }
+
+ public static int getNavBarHeight(Context context) {
+ int result = 0;
+ boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
+ boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
+
+ if(!hasMenuKey && !hasBackKey) {
+ //The device has a navigation bar
+ Resources resources = context.getResources();
+
+ int orientation = context.getResources().getConfiguration().orientation;
+ int resourceId;
+ if (isTablet(context)){
+ resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android");
+ } else {
+ resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_width", "dimen", "android");
+ }
+
+ if (resourceId > 0) {
+ return context.getResources().getDimensionPixelSize(resourceId);
+ }
+ }
+ return result;
+ }
+
+
+ private static boolean isTablet(Context context) {
+ return (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ >= Configuration.SCREENLAYOUT_SIZE_LARGE;
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java b/library/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java
new file mode 100644
index 00000000..570d68ed
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java
@@ -0,0 +1,31 @@
+package com.pchmn.materialchips.views;
+
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+
+public class ChipsInputEditText extends android.support.v7.widget.AppCompatEditText {
+
+ private FilterableListView filterableListView;
+
+ public ChipsInputEditText(Context context) {
+ super(context);
+ }
+
+ public ChipsInputEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public boolean isFilterableListVisible() {
+ return filterableListView.getVisibility() == VISIBLE;
+ }
+
+ public FilterableListView getFilterableListView() {
+ return filterableListView;
+ }
+
+ public void setFilterableListView(FilterableListView filterableListView) {
+ this.filterableListView = filterableListView;
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java b/library/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java
new file mode 100644
index 00000000..766a2e92
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java
@@ -0,0 +1,273 @@
+package com.pchmn.materialchips.views;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.Uri;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.widget.ImageButton;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.pchmn.materialchips.R;
+import com.pchmn.materialchips.R2;
+import com.pchmn.materialchips.model.Chip;
+import com.pchmn.materialchips.model.ChipInterface;
+import com.pchmn.materialchips.util.ColorUtil;
+import com.pchmn.materialchips.util.LetterTileProvider;
+import com.pchmn.materialchips.util.MyWindowCallback;
+import com.pchmn.materialchips.util.ViewUtil;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import de.hdodenhof.circleimageview.CircleImageView;
+
+
+public class DetailedChipView extends RelativeLayout {
+
+ private static final String TAG = DetailedChipView.class.toString();
+ // context
+ private Context mContext;
+ // xml elements
+ @BindView(R2.id.content) RelativeLayout mContentLayout;
+ @BindView(R2.id.avatar_icon) CircleImageView mAvatarIconImageView;
+ @BindView(R2.id.name) TextView mNameTextView;
+ @BindView(R2.id.info) TextView mInfoTextView;
+ @BindView(R2.id.delete_button) ImageButton mDeleteButton;
+ // letter tile provider
+ private static LetterTileProvider mLetterTileProvider;
+ // attributes
+ private ColorStateList mBackgroundColor;
+
+ public DetailedChipView(Context context) {
+ super(context);
+ mContext = context;
+ init(null);
+ }
+
+ public DetailedChipView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ init(attrs);
+ }
+
+ /**
+ * Inflate the view according to attributes
+ *
+ * @param attrs the attributes
+ */
+ private void init(AttributeSet attrs) {
+ // inflate layout
+ View rootView = inflate(getContext(), R.layout.detailed_chip_view, this);
+ // butter knife
+ ButterKnife.bind(this, rootView);
+ // letter tile provider
+ mLetterTileProvider = new LetterTileProvider(mContext);
+
+ // hide on first
+ setVisibility(GONE);
+ // hide on touch outside
+ hideOnTouchOutside();
+ }
+
+ /**
+ * Hide the view on touch outside of it
+ */
+ private void hideOnTouchOutside() {
+ // set focusable
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ setClickable(true);
+ }
+
+ /**
+ * Fade in
+ */
+ public void fadeIn() {
+ AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
+ anim.setDuration(200);
+ startAnimation(anim);
+ setVisibility(VISIBLE);
+ // focus on the view
+ requestFocus();
+ }
+
+ /**
+ * Fade out
+ */
+ public void fadeOut() {
+ AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
+ anim.setDuration(200);
+ startAnimation(anim);
+ setVisibility(GONE);
+ // fix onclick issue
+ clearFocus();
+ setClickable(false);
+ }
+
+ public void setAvatarIcon(Drawable icon) {
+ mAvatarIconImageView.setImageDrawable(icon);
+ }
+
+ public void setAvatarIcon(Bitmap icon) {
+ mAvatarIconImageView.setImageBitmap(icon);
+ }
+
+ public void setAvatarIcon(Uri icon) {
+ mAvatarIconImageView.setImageURI(icon);
+ }
+
+ public void setName(String name) {
+ mNameTextView.setText(name);
+ }
+
+ public void setInfo(String info) {
+ if(info != null) {
+ mInfoTextView.setVisibility(VISIBLE);
+ mInfoTextView.setText(info);
+ }
+ else {
+ mInfoTextView.setVisibility(GONE);
+ }
+ }
+
+ public void setTextColor(ColorStateList color) {
+ mNameTextView.setTextColor(color);
+ mInfoTextView.setTextColor(ColorUtil.alpha(color.getDefaultColor(), 150));
+ }
+
+ public void setBackGroundcolor(ColorStateList color) {
+ mBackgroundColor = color;
+ mContentLayout.getBackground().setColorFilter(color.getDefaultColor(), PorterDuff.Mode.SRC_ATOP);
+ }
+
+ public int getBackgroundColor() {
+ return mBackgroundColor == null ? ContextCompat.getColor(mContext, R.color.colorAccent) : mBackgroundColor.getDefaultColor();
+ }
+
+ public void setDeleteIconColor(ColorStateList color) {
+ mDeleteButton.getDrawable().mutate().setColorFilter(color.getDefaultColor(), PorterDuff.Mode.SRC_ATOP);
+ }
+
+ public void setOnDeleteClicked(OnClickListener onClickListener) {
+ mDeleteButton.setOnClickListener(onClickListener);
+ }
+
+ public void alignLeft() {
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mContentLayout.getLayoutParams();
+ params.leftMargin = 0;
+ mContentLayout.setLayoutParams(params);
+ }
+
+ public void alignRight() {
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mContentLayout.getLayoutParams();
+ params.rightMargin = 0;
+ mContentLayout.setLayoutParams(params);
+ }
+
+ public static class Builder {
+ private Context context;
+ private Uri avatarUri;
+ private Drawable avatarDrawable;
+ private String name;
+ private String info;
+ private ColorStateList textColor;
+ private ColorStateList backgroundColor;
+ private ColorStateList deleteIconColor;
+
+ public Builder(Context context) {
+ this.context = context;
+ }
+
+ public Builder avatar(Uri avatarUri) {
+ this.avatarUri = avatarUri;
+ return this;
+ }
+
+ public Builder avatar(Drawable avatarDrawable) {
+ this.avatarDrawable = avatarDrawable;
+ return this;
+ }
+
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder info(String info) {
+ this.info = info;
+ return this;
+ }
+
+ public Builder chip(ChipInterface chip) {
+ this.avatarUri = chip.getAvatarUri();
+ this.avatarDrawable = chip.getAvatarDrawable();
+ this.name = chip.getLabel();
+ this.info = chip.getInfo();
+ return this;
+ }
+
+ public Builder textColor(ColorStateList textColor) {
+ this.textColor = textColor;
+ return this;
+ }
+
+ public Builder backgroundColor(ColorStateList backgroundColor) {
+ this.backgroundColor = backgroundColor;
+ return this;
+ }
+
+ public Builder deleteIconColor(ColorStateList deleteIconColor) {
+ this.deleteIconColor = deleteIconColor;
+ return this;
+ }
+
+ public DetailedChipView build() {
+ return DetailedChipView.newInstance(this);
+ }
+ }
+
+ private static DetailedChipView newInstance(Builder builder) {
+ DetailedChipView detailedChipView = new DetailedChipView(builder.context);
+ // avatar
+ if(builder.avatarUri != null)
+ detailedChipView.setAvatarIcon(builder.avatarUri);
+ else if(builder.avatarDrawable != null)
+ detailedChipView.setAvatarIcon(builder.avatarDrawable);
+ else
+ detailedChipView.setAvatarIcon(mLetterTileProvider.getLetterTile(builder.name));
+
+ // background color
+ if(builder.backgroundColor != null)
+ detailedChipView.setBackGroundcolor(builder.backgroundColor);
+
+ // text color
+ if(builder.textColor != null)
+ detailedChipView.setTextColor(builder.textColor);
+ else if(ColorUtil.isColorDark(detailedChipView.getBackgroundColor()))
+ detailedChipView.setTextColor(ColorStateList.valueOf(Color.WHITE));
+ else
+ detailedChipView.setTextColor(ColorStateList.valueOf(Color.BLACK));
+
+ // delete icon color
+ if(builder.deleteIconColor != null)
+ detailedChipView.setDeleteIconColor(builder.deleteIconColor);
+ else if(ColorUtil.isColorDark(detailedChipView.getBackgroundColor()))
+ detailedChipView.setDeleteIconColor(ColorStateList.valueOf(Color.WHITE));
+ else
+ detailedChipView.setDeleteIconColor(ColorStateList.valueOf(Color.BLACK));
+
+ detailedChipView.setName(builder.name);
+ detailedChipView.setInfo(builder.info);
+ return detailedChipView;
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/views/FilterableListView.java b/library/src/main/java/com/pchmn/materialchips/views/FilterableListView.java
new file mode 100644
index 00000000..8e94cd52
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/views/FilterableListView.java
@@ -0,0 +1,158 @@
+package com.pchmn.materialchips.views;
+
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AlphaAnimation;
+import android.widget.Filter;
+import android.widget.RelativeLayout;
+
+import com.pchmn.materialchips.ChipsInput;
+import com.pchmn.materialchips.R;
+import com.pchmn.materialchips.R2;
+import com.pchmn.materialchips.adapter.FilterableAdapter;
+import com.pchmn.materialchips.model.ChipInterface;
+import com.pchmn.materialchips.util.ViewUtil;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class FilterableListView extends RelativeLayout {
+
+ private static final String TAG = FilterableListView.class.toString();
+ private Context mContext;
+ // list
+ @BindView(R2.id.recycler_view) RecyclerView mRecyclerView;
+ private FilterableAdapter mAdapter;
+ private List extends ChipInterface> mFilterableList;
+ // others
+ private ChipsInput mChipsInput;
+
+ public FilterableListView(Context context) {
+ super(context);
+ mContext = context;
+ init();
+ }
+
+ private void init() {
+ // inflate layout
+ View view = inflate(getContext(), R.layout.list_filterable_view, this);
+ // butter knife
+ ButterKnife.bind(this, view);
+
+ // recycler
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));
+
+ // hide on first
+ setVisibility(GONE);
+ }
+
+ public void build(List extends ChipInterface> filterableList, ChipsInput chipsInput, ColorStateList backgroundColor, ColorStateList textColor) {
+ mFilterableList = filterableList;
+ mChipsInput = chipsInput;
+
+ // adapter
+ mAdapter = new FilterableAdapter(mContext, mRecyclerView, filterableList, chipsInput, backgroundColor, textColor);
+ mRecyclerView.setAdapter(mAdapter);
+ if(backgroundColor != null)
+ mRecyclerView.getBackground().setColorFilter(backgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP);
+
+ // listen to change in the tree
+ mChipsInput.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+
+ @Override
+ public void onGlobalLayout() {
+
+ // position
+ ViewGroup rootView = (ViewGroup) mChipsInput.getRootView();
+
+ // size
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
+ ViewUtil.getWindowWidth(mContext),
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+
+ if(mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
+ layoutParams.bottomMargin = ViewUtil.getNavBarHeight(mContext);
+ }
+
+
+ // add view
+ rootView.addView(FilterableListView.this, layoutParams);
+
+ // remove the listener:
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ mChipsInput.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ } else {
+ mChipsInput.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ }
+
+ });
+ }
+
+ public void filterList(CharSequence text) {
+ mAdapter.getFilter().filter(text, new Filter.FilterListener() {
+ @Override
+ public void onFilterComplete(int count) {
+ // show if there are results
+ if(mAdapter.getItemCount() > 0)
+ fadeIn();
+ else
+ fadeOut();
+ }
+ });
+ }
+
+ /**
+ * Fade in
+ */
+ public void fadeIn() {
+ if(getVisibility() == VISIBLE)
+ return;
+
+ // get visible window (keyboard shown)
+ final View rootView = getRootView();
+ Rect r = new Rect();
+ rootView.getWindowVisibleDisplayFrame(r);
+
+ int[] coord = new int[2];
+ mChipsInput.getLocationInWindow(coord);
+ ViewGroup.MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
+ layoutParams.topMargin = coord[1] + mChipsInput.getHeight();
+ // height of the keyboard
+ layoutParams.bottomMargin = rootView.getHeight() - r.bottom;
+ setLayoutParams(layoutParams);
+
+ AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
+ anim.setDuration(200);
+ startAnimation(anim);
+ setVisibility(VISIBLE);
+ }
+
+ /**
+ * Fade out
+ */
+ public void fadeOut() {
+ if(getVisibility() == GONE)
+ return;
+
+ AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
+ anim.setDuration(200);
+ startAnimation(anim);
+ setVisibility(GONE);
+ }
+}
diff --git a/library/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java b/library/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java
new file mode 100644
index 00000000..db212ff6
--- /dev/null
+++ b/library/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java
@@ -0,0 +1,49 @@
+package com.pchmn.materialchips.views;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v4.widget.NestedScrollView;
+import android.util.AttributeSet;
+
+import com.pchmn.materialchips.R;
+import com.pchmn.materialchips.util.ViewUtil;
+
+public class ScrollViewMaxHeight extends NestedScrollView {
+
+ private int mMaxHeight;
+ private int mWidthMeasureSpec;
+
+ public ScrollViewMaxHeight(Context context) {
+ super(context);
+ }
+
+ public ScrollViewMaxHeight(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.ScrollViewMaxHeight,
+ 0, 0);
+
+ try {
+ mMaxHeight = a.getDimensionPixelSize(R.styleable.ScrollViewMaxHeight_maxHeight, ViewUtil.dpToPx(300));
+ }
+ finally {
+ a.recycle();
+ }
+ }
+
+ public void setMaxHeight(int height) {
+ mMaxHeight = height;
+ int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST);
+ measure(mWidthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mWidthMeasureSpec = widthMeasureSpec;
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+}
diff --git a/library/src/main/res/drawable-v21/ripple_chip_view.xml b/library/src/main/res/drawable-v21/ripple_chip_view.xml
new file mode 100644
index 00000000..aea2c3a4
--- /dev/null
+++ b/library/src/main/res/drawable-v21/ripple_chip_view.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable/avatar.png b/library/src/main/res/drawable/avatar.png
new file mode 100644
index 00000000..337f6ead
Binary files /dev/null and b/library/src/main/res/drawable/avatar.png differ
diff --git a/library/src/main/res/drawable/bg_chip_view.xml b/library/src/main/res/drawable/bg_chip_view.xml
new file mode 100644
index 00000000..58b375ec
--- /dev/null
+++ b/library/src/main/res/drawable/bg_chip_view.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable/bg_chip_view_opened.xml b/library/src/main/res/drawable/bg_chip_view_opened.xml
new file mode 100644
index 00000000..69dbc5bd
--- /dev/null
+++ b/library/src/main/res/drawable/bg_chip_view_opened.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/drawable/ic_cancel_grey_24dp.xml b/library/src/main/res/drawable/ic_cancel_grey_24dp.xml
new file mode 100644
index 00000000..f9b26632
--- /dev/null
+++ b/library/src/main/res/drawable/ic_cancel_grey_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/library/src/main/res/drawable/ic_cancel_white_24dp.xml b/library/src/main/res/drawable/ic_cancel_white_24dp.xml
new file mode 100644
index 00000000..e6545bf8
--- /dev/null
+++ b/library/src/main/res/drawable/ic_cancel_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/library/src/main/res/drawable/ic_person_outline_white_24dp.xml b/library/src/main/res/drawable/ic_person_outline_white_24dp.xml
new file mode 100644
index 00000000..69453b4e
--- /dev/null
+++ b/library/src/main/res/drawable/ic_person_outline_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/library/src/main/res/drawable/ic_person_white_24dp.xml b/library/src/main/res/drawable/ic_person_white_24dp.xml
new file mode 100644
index 00000000..22ca1566
--- /dev/null
+++ b/library/src/main/res/drawable/ic_person_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/library/src/main/res/drawable/ripple_chip_view.xml b/library/src/main/res/drawable/ripple_chip_view.xml
new file mode 100644
index 00000000..58b375ec
--- /dev/null
+++ b/library/src/main/res/drawable/ripple_chip_view.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/chip_view.xml b/library/src/main/res/layout/chip_view.xml
new file mode 100644
index 00000000..ce7310b2
--- /dev/null
+++ b/library/src/main/res/layout/chip_view.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/chips_input.xml b/library/src/main/res/layout/chips_input.xml
new file mode 100644
index 00000000..f39d6fc7
--- /dev/null
+++ b/library/src/main/res/layout/chips_input.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/detailed_chip_view.xml b/library/src/main/res/layout/detailed_chip_view.xml
new file mode 100644
index 00000000..3ceed2cc
--- /dev/null
+++ b/library/src/main/res/layout/detailed_chip_view.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/item_list_filterable.xml b/library/src/main/res/layout/item_list_filterable.xml
new file mode 100644
index 00000000..cbe46c5a
--- /dev/null
+++ b/library/src/main/res/layout/item_list_filterable.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/layout/list_filterable_view.xml b/library/src/main/res/layout/list_filterable_view.xml
new file mode 100644
index 00000000..3dc22897
--- /dev/null
+++ b/library/src/main/res/layout/list_filterable_view.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..871a27ac
--- /dev/null
+++ b/library/src/main/res/values/attrs.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/values/colors.xml b/library/src/main/res/values/colors.xml
new file mode 100644
index 00000000..9f540beb
--- /dev/null
+++ b/library/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+
+ #E0E0E0
+ #009688
+ #ababab
+ #b9ffffff
+ ?attr/colorAccent
+
+
\ No newline at end of file
diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml
new file mode 100644
index 00000000..1a143039
--- /dev/null
+++ b/library/src/main/res/values/strings.xml
@@ -0,0 +1,22 @@
+
+ MaterialChipsInput
+
+
+
+
+ #f16364
+ #f58559
+ #f9a43e
+ #e4c62e
+ #67bf74
+ #59a2be
+ #2093cd
+ #ad62a7
+
+
+
+ 17sp
+
+ 32dp
+
+
diff --git a/library/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java b/library/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java
new file mode 100644
index 00000000..b68d9790
--- /dev/null
+++ b/library/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.pchmn.materialchips;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/sample/.gitignore b/sample/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 00000000..1882ae03
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,43 @@
+apply plugin: 'com.android.application'
+apply plugin: 'me.tatarka.retrolambda'
+apply plugin: 'com.jakewharton.butterknife'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+ defaultConfig {
+ applicationId "com.pchmn.sample.materialchipsinput"
+ minSdkVersion 15
+ targetSdkVersion 25
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ compile project(path: ':library')
+
+ // butter knife
+ compile 'com.android.support:appcompat-v7:25.3.0'
+ compile 'com.android.support.constraint:constraint-layout:1.0.2'
+ compile 'io.reactivex.rxjava2:rxjava:2.0.8'
+ compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.3@aar'
+ compile 'com.jakewharton:butterknife:8.5.1'
+ testCompile 'junit:junit:4.12'
+ annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
+}
diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro
new file mode 100644
index 00000000..0ae68584
--- /dev/null
+++ b/sample/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/couleurwhatever/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/sample/src/androidTest/java/com/pchmn/sample/materialchipsinput/ExampleInstrumentedTest.java b/sample/src/androidTest/java/com/pchmn/sample/materialchipsinput/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..400aed59
--- /dev/null
+++ b/sample/src/androidTest/java/com/pchmn/sample/materialchipsinput/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.pchmn.sample.materialchipsinput;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.pchmn.sample.materialchipsinput", appContext.getPackageName());
+ }
+}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..15326c11
--- /dev/null
+++ b/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/java/com/pchmn/sample/materialchipsinput/ChipExamplesActivity.java b/sample/src/main/java/com/pchmn/sample/materialchipsinput/ChipExamplesActivity.java
new file mode 100644
index 00000000..8ba18c73
--- /dev/null
+++ b/sample/src/main/java/com/pchmn/sample/materialchipsinput/ChipExamplesActivity.java
@@ -0,0 +1,81 @@
+package com.pchmn.sample.materialchipsinput;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Toast;
+
+import com.pchmn.materialchips.ChipView;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ChipExamplesActivity extends AppCompatActivity {
+
+ private static final String TAG = ChipExamplesActivity.class.toString();
+ @BindView(R.id.chip1) ChipView mChip1;
+ @BindView(R.id.chip2) ChipView mChip2;
+ @BindView(R.id.chip3) ChipView mChip3;
+ @BindView(R.id.chip4) ChipView mChip4;
+ @BindView(R.id.chip5) ChipView mChip5;
+ @BindView(R.id.chip6) ChipView mChip6;
+ @BindView(R.id.chip7) ChipView mChip7;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_chip_examples);
+ // butter knife
+ ButterKnife.bind(this);
+
+ // chip 1
+ mChip1.setOnChipClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip1.getLabel() + ": clicked", Toast.LENGTH_SHORT).show();
+ });
+ mChip1.setOnDeleteClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip1.getLabel() + ": delete clicked", Toast.LENGTH_SHORT).show();
+ });
+
+ // chip 2
+ mChip2.setOnChipClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip2.getLabel() + ": clicked", Toast.LENGTH_SHORT).show();
+ });
+
+ // chip 3
+ mChip3.setOnChipClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip3.getLabel() + ": clicked", Toast.LENGTH_SHORT).show();
+ });
+ mChip3.setOnDeleteClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip3.getLabel() + ": delete clicked", Toast.LENGTH_SHORT).show();
+ });
+
+ // chip 4
+ mChip4.setOnChipClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip4.getLabel() + ": clicked", Toast.LENGTH_SHORT).show();
+ });
+ mChip4.setOnDeleteClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip4.getLabel() + ": delete clicked", Toast.LENGTH_SHORT).show();
+ });
+
+ // chip 5
+ mChip5.setOnChipClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip5.getLabel() + ": clicked", Toast.LENGTH_SHORT).show();
+ });
+
+ // chip 6
+ mChip6.setOnChipClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip6.getLabel() + ": clicked", Toast.LENGTH_SHORT).show();
+ });
+ mChip6.setOnDeleteClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip6.getLabel() + ": delete clicked", Toast.LENGTH_SHORT).show();
+ });
+
+ // chip 7
+ mChip7.setOnChipClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip7.getLabel() + ": clicked", Toast.LENGTH_SHORT).show();
+ });
+ mChip7.setOnDeleteClicked(view -> {
+ Toast.makeText(ChipExamplesActivity.this, mChip7.getLabel() + ": delete clicked", Toast.LENGTH_SHORT).show();
+ });
+ }
+}
diff --git a/sample/src/main/java/com/pchmn/sample/materialchipsinput/ContactChip.java b/sample/src/main/java/com/pchmn/sample/materialchipsinput/ContactChip.java
new file mode 100644
index 00000000..2706b54d
--- /dev/null
+++ b/sample/src/main/java/com/pchmn/sample/materialchipsinput/ContactChip.java
@@ -0,0 +1,47 @@
+package com.pchmn.sample.materialchipsinput;
+
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import com.pchmn.materialchips.model.ChipInterface;
+
+public class ContactChip implements ChipInterface {
+
+ private String id;
+ private Uri avatarUri;
+ private String name;
+ private String phoneNumber;
+
+ public ContactChip(String id, Uri avatarUri, String name, String phoneNumber) {
+ this.id = id;
+ this.avatarUri = avatarUri;
+ this.name = name;
+ this.phoneNumber = phoneNumber;
+ }
+
+ @Override
+ public Object getId() {
+ return id;
+ }
+
+ @Override
+ public Uri getAvatarUri() {
+ return avatarUri;
+ }
+
+ @Override
+ public Drawable getAvatarDrawable() {
+ return null;
+ }
+
+ @Override
+ public String getLabel() {
+ return name;
+ }
+
+ @Override
+ public String getInfo() {
+ return phoneNumber;
+ }
+}
diff --git a/sample/src/main/java/com/pchmn/sample/materialchipsinput/ContactListActivity.java b/sample/src/main/java/com/pchmn/sample/materialchipsinput/ContactListActivity.java
new file mode 100644
index 00000000..ebdb1b41
--- /dev/null
+++ b/sample/src/main/java/com/pchmn/sample/materialchipsinput/ContactListActivity.java
@@ -0,0 +1,123 @@
+package com.pchmn.sample.materialchipsinput;
+
+import android.Manifest;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.pchmn.materialchips.ChipsInput;
+import com.pchmn.materialchips.model.Chip;
+import com.pchmn.materialchips.model.ChipInterface;
+import com.tbruyelle.rxpermissions2.RxPermissions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ContactListActivity extends AppCompatActivity {
+
+ private static final String TAG = ContactListActivity.class.toString();
+ @BindView(R.id.chips_input) ChipsInput mChipsInput;
+ @BindView(R.id.validate) Button mValidateButton;
+ @BindView(R.id.chip_list) TextView mChipListText;
+ private List mContactList = new ArrayList<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_contact_list);
+ // butter knife
+ ButterKnife.bind(this);
+
+ // get contact list
+ new RxPermissions(this)
+ .request(Manifest.permission.READ_CONTACTS)
+ .subscribe(granted -> {
+ if(granted && mContactList.size() == 0)
+ getContactList();
+ });
+
+ // chips listener
+ mChipsInput.addChipsListener(new ChipsInput.ChipsListener() {
+ @Override
+ public void onChipAdded(ChipInterface chip, int newSize) {
+ Log.e(TAG, "chip added, " + newSize);
+ }
+
+ @Override
+ public void onChipRemoved(ChipInterface chip, int newSize) {
+ Log.e(TAG, "chip removed, " + newSize);
+ }
+
+ @Override
+ public void onTextChanged(CharSequence text) {
+ Log.e(TAG, "text changed: " + text.toString());
+ }
+ });
+
+ // show selected chips
+ mValidateButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String listString = "";
+ for(ContactChip chip: (List) mChipsInput.getSelectedChipList()) {
+ listString += chip.getLabel() + " (" + (chip.getInfo() != null ? chip.getInfo(): "") + ")" + ", ";
+ }
+
+ mChipListText.setText(listString);
+ }
+ });
+ }
+
+ /**
+ * Get the contacts of the user and add each contact in the mContactList
+ * And finally pass the mContactList to the mChipsInput
+ */
+ private void getContactList() {
+ Cursor phones = this.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null,null,null, null);
+
+ // loop over all contacts
+ if(phones != null) {
+ while (phones.moveToNext()) {
+ // get contact info
+ String phoneNumber = null;
+ String id = phones.getString(phones.getColumnIndex(ContactsContract.Contacts._ID));
+ String name = phones.getString(phones.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
+ String avatarUriString = phones.getString(phones.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI));
+ Uri avatarUri = null;
+ if(avatarUriString != null)
+ avatarUri = Uri.parse(avatarUriString);
+
+ // get phone number
+ if (Integer.parseInt(phones.getString(phones.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
+ Cursor pCur = this.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
+ null,
+ ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[] { id }, null);
+
+ while (pCur != null && pCur.moveToNext()) {
+ phoneNumber = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
+ }
+
+ pCur.close();
+
+ }
+
+ ContactChip contactChip = new ContactChip(id, avatarUri, name, phoneNumber);
+ // add contact to the list
+ mContactList.add(contactChip);
+ }
+ phones.close();
+ }
+
+ // pass contact list to chips input
+ mChipsInput.setFilterableList(mContactList);
+ }
+}
diff --git a/sample/src/main/java/com/pchmn/sample/materialchipsinput/MainActivity.java b/sample/src/main/java/com/pchmn/sample/materialchipsinput/MainActivity.java
new file mode 100644
index 00000000..664a4cf4
--- /dev/null
+++ b/sample/src/main/java/com/pchmn/sample/materialchipsinput/MainActivity.java
@@ -0,0 +1,32 @@
+package com.pchmn.sample.materialchipsinput;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.widget.Button;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class MainActivity extends AppCompatActivity {
+
+ private static final String TAG = MainActivity.class.toString();
+ @BindView(R.id.contacts_button) Button mContactListButton;
+ @BindView(R.id.custom_chips_button) Button mCustomChipsButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ // butter knife
+ ButterKnife.bind(this);
+
+ mContactListButton.setOnClickListener(view -> {
+ startActivity(new Intent(MainActivity.this, ContactListActivity.class));
+ });
+
+ mCustomChipsButton.setOnClickListener(view -> {
+ startActivity(new Intent(MainActivity.this, ChipExamplesActivity.class));
+ });
+ }
+}
diff --git a/sample/src/main/res/drawable/francis.png b/sample/src/main/res/drawable/francis.png
new file mode 100644
index 00000000..71d3bb31
Binary files /dev/null and b/sample/src/main/res/drawable/francis.png differ
diff --git a/sample/src/main/res/drawable/ic_check_circle_black_24dp.xml b/sample/src/main/res/drawable/ic_check_circle_black_24dp.xml
new file mode 100644
index 00000000..1241edab
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_check_circle_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/sample/src/main/res/drawable/paul.png b/sample/src/main/res/drawable/paul.png
new file mode 100644
index 00000000..d3ddc559
Binary files /dev/null and b/sample/src/main/res/drawable/paul.png differ
diff --git a/sample/src/main/res/drawable/teresa.png b/sample/src/main/res/drawable/teresa.png
new file mode 100644
index 00000000..50736364
Binary files /dev/null and b/sample/src/main/res/drawable/teresa.png differ
diff --git a/sample/src/main/res/layout/activity_chip_examples.xml b/sample/src/main/res/layout/activity_chip_examples.xml
new file mode 100644
index 00000000..68442c51
--- /dev/null
+++ b/sample/src/main/res/layout/activity_chip_examples.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/activity_contact_list.xml b/sample/src/main/res/layout/activity_contact_list.xml
new file mode 100644
index 00000000..496a510a
--- /dev/null
+++ b/sample/src/main/res/layout/activity_contact_list.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..d05e5891
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..cde69bcc
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9a078e3e
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..c133a0cb
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..efc028a6
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..bfa42f0e
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..3af2608a
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..324e72cd
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9bec2e62
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..aee44e13
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..34947cd6
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
new file mode 100644
index 00000000..3ab3e9cb
--- /dev/null
+++ b/sample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 00000000..d5f937eb
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ MaterialChipsInput
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 00000000..5885930d
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/sample/src/test/java/com/pchmn/sample/materialchipsinput/ExampleUnitTest.java b/sample/src/test/java/com/pchmn/sample/materialchipsinput/ExampleUnitTest.java
new file mode 100644
index 00000000..d0402c86
--- /dev/null
+++ b/sample/src/test/java/com/pchmn/sample/materialchipsinput/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.pchmn.sample.materialchipsinput;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..52baf7e6
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':sample', ':library'