package com.pchmn.materialchips; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; 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.Filter.FilterListener; import android.widget.RelativeLayout; import android.widget.TextView; import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager; import com.pchmn.materialchips.RecyclerItemClickListener.OnItemClickListener; import com.pchmn.materialchips.adapter.ChipsAdapter; import com.pchmn.materialchips.adapter.FilterableAdapter; import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; import com.pchmn.materialchips.util.ActivityUtil; import com.pchmn.materialchips.util.ClickOutsideCallback; import com.pchmn.materialchips.util.ViewUtil; import com.pchmn.materialchips.views.ChipsInputEditText; import com.pchmn.materialchips.views.DropdownListView; import com.pchmn.materialchips.views.ScrollViewMaxHeight; public abstract class ChipsInput extends ScrollViewMaxHeight { private Context mContext; // attributes private String mHint; private ColorStateList mHintColor; private ColorStateList mTextColor; private int mMaxRows = 2; private boolean mShowChipDetailed = true; private List> mChipsListenerList = new ArrayList<>(); private ChipValidator mChipValidator; private ChipsAdapter chipsAdapter; private RecyclerView chipsRecyclerView; private ChipsInputEditText chipsInputEditText; private ChipDropdownAdapter filterableAdapter; private ViewGroup filterableListLayout; private DropdownListView mDropdownListView; 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); chipsRecyclerView = 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); mShowChipDetailed = a.getBoolean(R.styleable.ChipsInput_showChipDetailed, true); // chip detailed text color } finally { a.recycle(); } } ChipsLayoutManager chipsLayoutManager = ChipsLayoutManager.newBuilder(mContext) .setOrientation(ChipsLayoutManager.HORIZONTAL) .build(); chipsRecyclerView.setLayoutManager(chipsLayoutManager); chipsRecyclerView.setNestedScrollingEnabled(false); setupClickOutsideCallback(); } public void setChipsAdapter(ChipsAdapter chipsAdapter) { this.chipsAdapter = chipsAdapter; chipsRecyclerView.setAdapter(chipsAdapter); } private void setupClickOutsideCallback() { 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 originalWindowCallback = (activity).getWindow().getCallback(); activity.getWindow().setCallback(new ClickOutsideCallback(originalWindowCallback, activity)); } private void initEditText() { chipsInputEditText = new ChipsInputEditText(mContext); if (mHintColor != null) chipsInputEditText.setHintTextColor(mHintColor); if (mTextColor != null) chipsInputEditText.setTextColor(mTextColor); chipsInputEditText.setLayoutParams(new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); chipsInputEditText.setHint(mHint); chipsInputEditText.setBackgroundResource(android.R.color.transparent); // prevent fullscreen on landscape chipsInputEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); chipsInputEditText.setPrivateImeOptions("nm"); // no suggestion chipsInputEditText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); // handle back space chipsInputEditText.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 (chipsInputEditText.getText().toString().length() == 0) chipsAdapter.removeLastChip(); } return false; } }); chipsInputEditText.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(chipsInputEditText.getText().toString()); } return false; } }); // text changed chipsInputEditText.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 addChip(T chip) { chipsAdapter.addChip(chip); } public ChipsInputEditText getEditText() { return chipsInputEditText; } public void addChipsListener(ChipsListener chipsListener) { mChipsListenerList.add(chipsListener); } public void onChipAdded(T chip, int size) { filterableAdapter.hideItem(chip); for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onChipAdded(chip, size); } } public void onChipRemoved(T chip, int size) { filterableAdapter.unhideItem(chip); for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onChipRemoved(chip, size); } } public void onTextChanged(CharSequence text) { for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onTextChanged(text); } mDropdownListView.getRecyclerView().scrollToPosition(0); // show filterable list if (mDropdownListView != null) { if (text.length() > 0) { filterDropdownList(text); } else { mDropdownListView.fadeOut(); } } } public void filterDropdownList(CharSequence text) { filterableAdapter.getFilter().filter(text, new FilterListener() { @Override public void onFilterComplete(int count) { // show if there are results if (filterableAdapter.getItemCount() > 0) mDropdownListView.fadeIn(); else mDropdownListView.fadeOut(); } }); } public void onActionDone(CharSequence text) { for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onActionDone(text); } mDropdownListView.getRecyclerView().scrollToPosition(0); } public List getSelectedChipList() { return chipsAdapter.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 ChipsInput setShowChipDetailed(boolean mShowChipDetailed) { this.mShowChipDetailed = mShowChipDetailed; return this; } public boolean isShowChipDetailed() { return mShowChipDetailed; } public void setFilterableListLayout(ViewGroup layout) { this.filterableListLayout = layout; } public RecyclerView getChipsRecyclerView() { return chipsRecyclerView; } public abstract static class ChipDropdownAdapter extends FilterableAdapter { public ChipDropdownAdapter(List itemList) { super(itemList); } } public void setChipDropdownAdapter(final ChipDropdownAdapter filterableAdapter) { this.filterableAdapter = filterableAdapter; if (filterableListLayout != null) { mDropdownListView = new DropdownListView(mContext, filterableListLayout); } else { mDropdownListView = new DropdownListView(mContext, this); } mDropdownListView.build(filterableAdapter); chipsAdapter.setFilterableListView(mDropdownListView); mDropdownListView.getRecyclerView().addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new OnItemClickListener() { @Override public void onItemClick(View view, int position) { T item = filterableAdapter.getItem(position); chipsAdapter.addChip(item); } })); } public ChipValidator getChipValidator() { return mChipValidator; } public void setChipValidator(ChipValidator mChipValidator) { this.mChipValidator = mChipValidator; } public interface ChipsListener { void onChipAdded(T chip, int newSize); void onChipRemoved(T chip, int newSize); void onTextChanged(CharSequence text); void onActionDone(CharSequence text); } public static abstract class SimpleChipsListener implements ChipsListener { public void onChipAdded(T chip, int newSize) { } public void onChipRemoved(T chip, int newSize) { } public void onTextChanged(CharSequence text) { } public void onActionDone(CharSequence text) { } } public interface ChipValidator { boolean areEquals(T chip1, T chip2); } }