include MaterialChipsInput as subdir lib

This commit is contained in:
Vincent Breitmoser 2018-07-02 20:06:47 +02:00
parent c98e835936
commit 053cbdf43e
40 changed files with 2963 additions and 2 deletions

View File

@ -28,7 +28,6 @@ dependencies {
// UI
compile 'org.sufficientlysecure:html-textview:3.1'
compile 'com.github.sikeeoh:MaterialChipsInput:1.1.1'
compile 'com.jpardogo.materialtabstrip:library:1.1.1'
compile 'com.getbase:floatingactionbutton:1.10.1'
compile 'com.nispok:snackbar:2.11.0'
@ -61,6 +60,7 @@ dependencies {
implementation project(':extern:minidns')
implementation project(':KeybaseLib')
implementation project(':safeslinger-exchange')
implementation project(':extern:MaterialChipsInput')
implementation "android.arch.work:work-runtime:1.0.0-alpha02"

View File

@ -46,7 +46,6 @@
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:hint="@string/label_to"
app:chip_hasAvatarIcon="false"
app:maxRows="2"
app:chip_detailed_backgroundColor="@color/colorChipViewBackground"
/>

8
extern/MaterialChipsInput/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild

36
extern/MaterialChipsInput/build.gradle vendored Normal file
View File

@ -0,0 +1,36 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
buildToolsVersion "27.0.3"
defaultConfig {
minSdkVersion 15
targetSdkVersion 27
versionCode 114
versionName "1.1.4"
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:27.1.0'
testCompile 'junit:junit:4.12'
// recycler
compile 'com.android.support:recyclerview-v7:27.1.0'
compile 'com.beloo.widget:ChipsLayoutManager:0.3.7@aar'
}

View File

@ -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

View File

@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@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());
}
}

View File

@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pchmn.materialchips">
</manifest>

View File

@ -0,0 +1,399 @@
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.support.annotation.ColorInt;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
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.ChipInterface;
import com.pchmn.materialchips.util.LetterTileProvider;
import com.pchmn.materialchips.util.ViewUtil;
public class ChipView extends RelativeLayout {
private static final String TAG = ChipView.class.toString();
// context
private Context mContext;
// xml elements
private LinearLayout mContentLayout;
private TextView mLabelTextView;
private ImageButton mDeleteButton;
// attributes
private static final int NONE = -1;
private String mLabel;
private ColorStateList mLabelColor;
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);
mContentLayout = (LinearLayout) rootView.findViewById(R.id.content);
mLabelTextView = (TextView) rootView.findViewById(R.id.label);
mDeleteButton = (ImageButton) rootView.findViewById(R.id.delete_button);
// 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);
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);
// delete button
setDeletable(mDeletable);
// background color
if (mBackgroundColor != null)
setChipBackgroundColor(mBackgroundColor);
}
public void inflate(ChipInterface chip) {
mChip = chip;
// label
mLabel = mChip.getLabel();
// inflate
inflateWithAttributes();
}
/**
* Get label
*
* @return the label
*/
public String getLabel() {
return mLabel;
}
/**
* Set label
*
* @param label the label to set
*/
public void setLabel(String label) {
mLabel = label;
mLabelTextView.setText(label);
}
/**
* Set label color
*
* @param color the color to set
*/
public void setLabelColor(ColorStateList color) {
mLabelColor = color;
mLabelTextView.setTextColor(color);
}
/**
* Set label color
*
* @param color the color to set
*/
public void setLabelColor(@ColorInt int color) {
mLabelColor = ColorStateList.valueOf(color);
mLabelTextView.setTextColor(color);
}
// /**
// * Show or hide avatar icon
// *
// * @param hasAvatarIcon true to show, false to hide
// */
// public void setHasAvatarIcon(boolean hasAvatarIcon) {
// mHasAvatarIcon = hasAvatarIcon;
//
// if(!mHasAvatarIcon) {
// // 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 icon
// if(mAvatarIconUri != null)
// mAvatarIconImageView.setImageURI(mAvatarIconUri);
// else if(mAvatarIconDrawable != null)
// mAvatarIconImageView.setImageDrawable(mAvatarIconDrawable);
// else
// mAvatarIconImageView.setImageBitmap(mLetterTileProvider.getLetterTile(getLabel()));
// }
// }
// /**
// * Set avatar icon
// *
// * @param avatarIcon the icon to set
// */
// public void setAvatarIcon(Drawable avatarIcon) {
// mAvatarIconDrawable = avatarIcon;
// mHasAvatarIcon = true;
// inflateWithAttributes();
// }
// /**
// * Set avatar icon
// *
// * @param avatarUri the uri of the icon to set
// */
// public void setAvatarIcon(Uri avatarUri) {
// mAvatarIconUri = avatarUri;
// mHasAvatarIcon = true;
// inflateWithAttributes();
// }
/**
* Show or hide delte button
*
* @param deletable true to show, false to hide
*/
public void setDeletable(boolean deletable) {
mDeletable = deletable;
if (!mDeletable) {
// hide delete icon
mDeleteButton.setVisibility(GONE);
// adjust padding
mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, ViewUtil.dpToPx(12), 0);
} else {
// show icon
mDeleteButton.setVisibility(VISIBLE);
// adjust padding
mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0);
// set icon
if (mDeleteIcon != null)
mDeleteButton.setImageDrawable(mDeleteIcon);
if (mDeleteIconColor != null)
mDeleteButton.getDrawable().mutate().setColorFilter(mDeleteIconColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP);
}
}
/**
* Set delete icon color
*
* @param color the color to set
*/
public void setDeleteIconColor(ColorStateList color) {
mDeleteIconColor = color;
mDeletable = true;
inflateWithAttributes();
}
/**
* Set delete icon color
*
* @param color the color to set
*/
public void setDeleteIconColor(@ColorInt int color) {
mDeleteIconColor = ColorStateList.valueOf(color);
mDeletable = true;
inflateWithAttributes();
}
/**
* Set delete icon
*
* @param deleteIcon the icon to set
*/
public void setDeleteIcon(Drawable deleteIcon) {
mDeleteIcon = deleteIcon;
mDeletable = true;
inflateWithAttributes();
}
/**
* Set background color
*
* @param color the color to set
*/
public void setChipBackgroundColor(ColorStateList color) {
mBackgroundColor = color;
setChipBackgroundColor(color.getDefaultColor());
}
/**
* Set background color
*
* @param color the color to set
*/
public void setChipBackgroundColor(@ColorInt int color) {
mBackgroundColor = ColorStateList.valueOf(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 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 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();
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.mDeletable = builder.deletable;
chipView.mDeleteIcon = builder.deleteIcon;
chipView.mDeleteIconColor = builder.deleteIconColor;
chipView.mBackgroundColor = builder.backgroundColor;
chipView.mChip = builder.chip;
chipView.inflateWithAttributes();
return chipView;
}
}

View File

@ -0,0 +1,427 @@
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.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.RelativeLayout;
import android.widget.TextView;
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.ActivityUtil;
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;
public class ChipsInput extends ScrollViewMaxHeight {
private static final String TAG = ChipsInput.class.toString();
// context
private Context mContext;
// xml element
private 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 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<ChipsListener> mChipsListenerList = new ArrayList<>();
private ChipsListener mChipsListener;
// chip list
private List<? extends ChipInterface> mFilterableChipList;
private FilterableListView mFilterableListView;
// chip validator
private ChipValidator mChipValidator;
private ViewGroup filterableListLayout;
private ChipsInputEditText mEditText;
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 filterableListLayout
View rootView = inflate(getContext(), R.layout.chips_input, this);
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.chips_recycler);
initEditText();
// 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 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, mEditText, 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
Activity activity = ActivityUtil.scanForActivity(mContext);
if (activity == null)
throw new ClassCastException("android.view.Context cannot be cast to android.app.Activity");
android.view.Window.Callback mCallBack = (activity).getWindow().getCallback();
activity.getWindow().setCallback(new MyWindowCallback(mCallBack, activity));
}
private void initEditText() {
mEditText = new ChipsInputEditText(mContext);
if (mHintColor != null)
mEditText.setHintTextColor(mHintColor);
if (mTextColor != null)
mEditText.setTextColor(mTextColor);
mEditText.setLayoutParams(new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mEditText.setHint(mHint);
mEditText.setBackgroundResource(android.R.color.transparent);
// prevent fullscreen on landscape
mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE);
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 (mEditText.getText().toString().length() == 0)
mChipsAdapter.removeLastChip();
}
return false;
}
});
mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((actionId == EditorInfo.IME_ACTION_DONE) || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
ChipsInput.this.onActionDone(mEditText.getText().toString());
}
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) {
ChipsInput.this.onTextChanged(s);
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
public void addChips(List<ChipInterface> chipList) {
mChipsAdapter.addChipsProgrammatically(chipList);
}
public void addChip(ChipInterface chip) {
mChipsAdapter.addChip(chip);
}
public void addChip(Object id, String label, String info) {
Chip chip = new Chip(id, label, info);
mChipsAdapter.addChip(chip);
}
public void addChip(String label, String info) {
Chip 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)
.deletable(mChipDeletable)
.deleteIcon(mChipDeleteIcon)
.deleteIconColor(mChipDeleteIconColor)
.backgroundColor(mChipBackgroundColor)
.build();
chipView.setPadding(padding, padding, padding, padding);
return chipView;
}
public ChipsInputEditText getEditText() {
return mChipsAdapter.getmEditText();
}
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 void onActionDone(CharSequence text) {
if (mChipsListener != null) {
for (ChipsListener chipsListener : mChipsListenerList) {
chipsListener.onActionDone(text);
}
}
}
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 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 setFilterableListLayout(ViewGroup layout) {
this.filterableListLayout = layout;
}
public void setFilterableList(List<? extends ChipInterface> list) {
mFilterableChipList = list;
if (filterableListLayout != null) {
mFilterableListView = new FilterableListView(mContext, filterableListLayout);
} else {
mFilterableListView = new FilterableListView(mContext);
}
mFilterableListView.build(mFilterableChipList, this, mFilterableListBackgroundColor, mFilterableListTextColor);
mChipsAdapter.setFilterableListView(mFilterableListView);
}
public List<? extends ChipInterface> getFilterableList() {
return mFilterableChipList;
}
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);
void onActionDone(CharSequence text);
}
public interface ChipValidator {
boolean areEquals(ChipInterface chip1, ChipInterface chip2);
}
}

View File

@ -0,0 +1,384 @@
package com.pchmn.materialchips.adapter;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
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.util.ViewUtil;
import com.pchmn.materialchips.views.ChipsInputEditText;
import com.pchmn.materialchips.views.DetailedChipView;
import com.pchmn.materialchips.views.FilterableListView;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ChipsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_EDIT_TEXT = 0;
private static final int TYPE_ITEM = 1;
private Context mContext;
private ChipsInput mChipsInput;
private List<ChipInterface> 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();
}
public ChipsAdapter(Context mContext, ChipsInput chipsInput, ChipsInputEditText mEditText, RecyclerView mRecyclerView) {
this(mContext, chipsInput, mRecyclerView);
this.mEditText = mEditText;
}
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 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 addChipsProgrammatically(List<ChipInterface> chipList) {
if (chipList != null) {
if (chipList.size() > 0) {
int chipsBeforeAdding = getItemCount();
for (ChipInterface chip : chipList) {
mChipList.add(chip);
mChipsInput.onChipAdded(chip, getItemCount());
}
// hide hint
mEditText.setHint(null);
// reset text
mEditText.setText(null);
notifyItemRangeChanged(chipsBeforeAdding, chipList.size());
}
}
}
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());
mChipsInput.onChipRemoved(chip, mChipList.size());
// 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<ChipInterface> 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<ChipInterface> 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<ChipInterface> 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 void removeLastChip() {
if (mChipList.size() > 0) {
removeChip(mChipList.get(mChipList.size() - 1));
}
}
public List<ChipInterface> getChipList() {
return mChipList;
}
private boolean listContains(List<ChipInterface> contactList, ChipInterface chip) {
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;
}
public ChipsInputEditText getmEditText() {
return mEditText;
}
}

View File

@ -0,0 +1,253 @@
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.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 java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import static android.view.View.GONE;
public class FilterableAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Filterable {
private static final String TAG = FilterableAdapter.class.toString();
// context
private Context mContext;
// list
private List<ChipInterface> mOriginalList = new ArrayList<>();
private List<ChipInterface> mChipList = new ArrayList<>();
private List<ChipInterface> mFilteredList = new ArrayList<>();
private ChipFilter mFilter;
private ChipsInput mChipsInput;
private LetterTileProvider mLetterTileProvider;
private ColorStateList mBackgroundColor;
private ColorStateList mTextColor;
// recycler
private RecyclerView mRecyclerView;
// sort
private Comparator<ChipInterface> mComparator;
private Collator mCollator;
public FilterableAdapter(Context context,
RecyclerView recyclerView,
List<? extends ChipInterface> chipList,
ChipsInput chipsInput,
ColorStateList backgroundColor,
ColorStateList textColor) {
mContext = context;
mRecyclerView = recyclerView;
mCollator = Collator.getInstance(Locale.getDefault());
mCollator.setStrength(Collator.PRIMARY);
mComparator = new Comparator<ChipInterface>() {
@Override
public int compare(ChipInterface o1, ChipInterface o2) {
return mCollator.compare(o1.getLabel(), o2.getLabel());
}
};
// remove chips that do not have label
Iterator<? extends ChipInterface> iterator = chipList.iterator();
while (iterator.hasNext()) {
if (iterator.next().getLabel() == null)
iterator.remove();
}
sortList(chipList);
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);
}
@Override
public void onActionDone(CharSequence text) {
mRecyclerView.scrollToPosition(0);
}
});
}
private class ItemViewHolder extends RecyclerView.ViewHolder {
private TextView mLabel;
private TextView mInfo;
ItemViewHolder(View view) {
super(view);
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);
// 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<ChipInterface> originalList;
private List<ChipInterface> filteredList;
public ChipFilter(FilterableAdapter adapter, List<ChipInterface> 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<ChipInterface>) 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
sortList(mChipList);
// sort filtered list
sortList(mFilteredList);
notifyDataSetChanged();
}
}
private boolean contains(ChipInterface chip) {
for (ChipInterface item : mOriginalList) {
if (item.equals(chip))
return true;
}
return false;
}
private void sortList(List<? extends ChipInterface> list) {
Collections.sort(list, mComparator);
}
}

View File

@ -0,0 +1,39 @@
package com.pchmn.materialchips.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class Chip implements ChipInterface {
private Object id;
private String label;
private String 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 String getLabel() {
return label;
}
@Override
public String getInfo() {
return info;
}
}

View File

@ -0,0 +1,12 @@
package com.pchmn.materialchips.model;
import android.graphics.drawable.Drawable;
import android.net.Uri;
public interface ChipInterface {
Object getId();
String getLabel();
String getInfo();
}

View File

@ -0,0 +1,20 @@
package com.pchmn.materialchips.util;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
public class ActivityUtil {
public static Activity scanForActivity(Context context) {
if (context == null)
return null;
else if (context instanceof Activity)
return (Activity)context;
else if (context instanceof ContextWrapper)
return scanForActivity(((ContextWrapper)context).getBaseContext());
return null;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,221 @@
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 <code>LetterTileProvider</code>
*
* @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) {
// workaround
if(displayName == null || 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) {
// workaround
if(displayName == null || displayName.length() == 0)
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 <code>c</code> 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 <code>key</code> 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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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 != null && filterableListView.getVisibility() == VISIBLE;
}
public FilterableListView getFilterableListView() {
return filterableListView;
}
public void setFilterableListView(FilterableListView filterableListView) {
this.filterableListView = filterableListView;
}
}

View File

@ -0,0 +1,233 @@
package com.pchmn.materialchips.views;
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.Drawable;
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.LinearLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.pchmn.materialchips.R;
import com.pchmn.materialchips.model.ChipInterface;
import com.pchmn.materialchips.util.ColorUtil;
import com.pchmn.materialchips.util.LetterTileProvider;
public class DetailedChipView extends LinearLayout {
private static final String TAG = DetailedChipView.class.toString();
// context
private Context mContext;
// xml elements
private LinearLayout mContentLayout;
private TextView mNameTextView;
private TextView mInfoTextView;
private 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);
mContentLayout = (LinearLayout) rootView.findViewById(R.id.content);
mNameTextView = (TextView) rootView.findViewById(R.id.name);
mInfoTextView = (TextView) rootView.findViewById(R.id.info);
mDeleteButton = (ImageButton) rootView.findViewById(R.id.delete_button);
// 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 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() {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentLayout.getLayoutParams();
params.leftMargin = 0;
mContentLayout.setLayoutParams(params);
}
public void alignRight() {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentLayout.getLayoutParams();
params.rightMargin = 0;
mContentLayout.setLayoutParams(params);
}
public static class Builder {
private Context context;
private String name;
private String info;
private ColorStateList textColor;
private ColorStateList backgroundColor;
private ColorStateList deleteIconColor;
public Builder(Context context) {
this.context = context;
}
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.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
// 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;
}
}

View File

@ -0,0 +1,156 @@
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.FrameLayout;
import android.widget.RelativeLayout;
import com.pchmn.materialchips.ChipsInput;
import com.pchmn.materialchips.R;
import com.pchmn.materialchips.adapter.FilterableAdapter;
import com.pchmn.materialchips.model.ChipInterface;
import com.pchmn.materialchips.util.ViewUtil;
import java.util.List;
public class FilterableListView extends RelativeLayout {
private static final String TAG = FilterableListView.class.toString();
private FrameLayout frameLayout;
private Context mContext;
// list
private RecyclerView mRecyclerView;
private FilterableAdapter mAdapter;
private List<? extends ChipInterface> mFilterableList;
// others
private ChipsInput mChipsInput;
private ViewGroup rootView;
public FilterableListView(Context context) {
this(context, null);
}
public FilterableListView(Context context, ViewGroup layout) {
super(context);
this.mContext = context;
this.rootView = layout;
init();
}
private void init() {
// inflate layout
View view = inflate(getContext(), R.layout.list_filterable_view, this);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_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
if(rootView == null){
rootView = (ViewGroup) mChipsInput.getRootView();
}
// size
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
ViewUtil.getWindowWidth(mContext),
ViewGroup.LayoutParams.WRAP_CONTENT);
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);
}
//If this child view is already added to the parent rootView, then remove it first
ViewGroup parent = (ViewGroup) FilterableListView.this.getParent();
if (parent != null) {
parent.removeView(FilterableListView.this);
}
// 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;
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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/white_transparency" />
<corners android:radius="16dp" />
</shape>
</item>
<item android:drawable="@drawable/bg_chip_view" />
</ripple>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:shape="rectangle">
<solid android:color="@color/colorChipViewBackground"/>
<corners android:radius="16dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:shape="rectangle">
<solid android:color="@color/colorAccent"/>
<corners android:radius="2dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#A6A6A6"
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#fff"
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#fff"
android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#aaffffff"
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:shape="rectangle">
<solid android:color="@color/colorChipViewBackground"/>
<corners android:radius="16dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_chip_view">
<!-- content -->
<LinearLayout
android:id="@+id/content"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:clickable="true"
android:background="@drawable/ripple_chip_view">
<!-- label -->
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="8dp"
android:textSize="14sp"
android:text="Paulcito"/>
<!-- remove button -->
<ImageButton
android:id="@+id/delete_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_cancel_grey_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<com.pchmn.materialchips.views.ScrollViewMaxHeight xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:maxHeight="150dp" >
<android.support.v7.widget.RecyclerView
android:id="@+id/chips_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp" />
</com.pchmn.materialchips.views.ScrollViewMaxHeight>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true">
<!-- content -->
<LinearLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginBottom="12dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="12dp"
android:background="@drawable/bg_chip_view_opened"
android:clickable="true"
android:elevation="6dp"
android:orientation="horizontal"
tools:targetApi="lollipop">
<!-- info -->
<LinearLayout
android:id="@+id/info_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingTop="16dp">
<!-- name -->
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:scrollHorizontally="true"
android:text="sdfdsfdsfdgs"
android:textSize="16sp" />
<!-- info -->
<TextView
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="1"
android:scrollHorizontally="true"
android:text="paul@gmail.com"
android:textSize="14sp" />
</LinearLayout>
<!-- delete button -->
<ImageButton
android:id="@+id/delete_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginRight="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_cancel_white_24dp" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:background="@android:color/white"
android:foreground="?attr/selectableItemBackground">
<!-- info -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:layout_marginLeft="18dp"
android:layout_marginRight="16dp">
<!-- name -->
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@android:color/black"
android:text="Two line item"/>
<TextView
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="2dp"
android:textSize="13sp"
android:text="secondary text"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:background="@android:color/white" />
</RelativeLayout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ChipView">
<attr name="label" format="string" />
<attr name="labelColor" format="color" />
<attr name="deletable" format="boolean" />
<attr name="deleteIcon" format="reference" />
<attr name="deleteIconColor" format="color" />
<attr name="backgroundColor" format="color" />
</declare-styleable>
<declare-styleable name="ChipsInput">
<attr name="hint" format="string" />
<attr name="hintColor" format="color" />
<attr name="textColor" format="color" />
<attr name="maxRows" format="integer" />
<attr name="chip_labelColor" format="color" />
<attr name="chip_deletable" format="boolean" />
<attr name="chip_deleteIcon" format="reference" />
<attr name="chip_deleteIconColor" format="color" />
<attr name="chip_backgroundColor" format="color" />
<attr name="showChipDetailed" format="boolean" />
<attr name="chip_detailed_textColor" format="color" />
<attr name="chip_detailed_backgroundColor" format="color" />
<attr name="chip_detailed_deleteIconColor" format="color" />
<attr name="filterable_list_backgroundColor" format="color" />
<attr name="filterable_list_textColor" format="color" />
</declare-styleable>
<declare-styleable name="ScrollViewMaxHeight">
<attr name="maxHeight" format="dimension" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorChipViewBackground">#E0E0E0</color>
<color name="chips_opened_bg">#009688</color>
<color name="colorCloseChip">#ababab</color>
<color name="white_transparency">#b9ffffff</color>
<color name="colorAccent">?attr/colorAccent</color>
</resources>

View File

@ -0,0 +1,20 @@
<resources>
<!-- LetterTileProvider -->
<!-- All of the possible tile background colors -->
<array name="letter_tile_colors">
<item>#f16364</item>
<item>#f58559</item>
<item>#f9a43e</item>
<item>#e4c62e</item>
<item>#67bf74</item>
<item>#59a2be</item>
<item>#2093cd</item>
<item>#ad62a7</item>
</array>
<!-- The default letter tile text size -->
<dimen name="tile_letter_font_size">17sp</dimen>
<!-- The deafult tile size -->
<dimen name="letter_tile_size">32dp</dimen>
<!-- /LetterTileProvider -->
</resources>

View File

@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

@ -4,6 +4,7 @@ include ':extern:bouncycastle:core'
include ':extern:bouncycastle:pg'
include ':extern:bouncycastle:prov'
include ':extern:minidns'
include ':extern:MaterialChipsInput:library'
// Workaround for Android Gradle Plugin 2.0, as described in http://stackoverflow.com/a/36544850
//include ':libkeychain'