Skip to content

Instantly share code, notes, and snippets.

@sbelloz
Last active February 19, 2020 12:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sbelloz/5193779a4c0db216c7e491113005aa36 to your computer and use it in GitHub Desktop.
Save sbelloz/5193779a4c0db216c7e491113005aa36 to your computer and use it in GitHub Desktop.
Suggestion search view collapsable
<resources>
<declare-styleable name="SuggestionSearchView">
<attr name="ssv_enableBack" format="boolean"/>
<attr name="ssv_searchHint" format="string"/>
<attr name="ssv_searchTextColor" format="color"/>
<attr name="ssv_searchTextHintColor" format="color"/>
<attr name="ssv_searchOptionsIcon" format="reference"/>
<attr name="ssv_clearTextOnDismiss" format="boolean"/>
<attr name="ssv_waitKeyboardForAnimation" format="boolean"/>
<attr name="ssv_delayAnimationIn" format="integer"/>
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="search_bar_bg">#cccccc</color>
</resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<dimen name="suggestion_view_height">54dp</dimen>
<dimen name="dimen4">4dp</dimen>
<dimen name="dimen8">8dp</dimen>
<dimen name="dimen10">10dp</dimen>
<dimen name="dimen12">12dp</dimen>
<dimen name="dimen16">16dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/search_suggestions_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent">
<androidx.cardview.widget.CardView
android:id="@+id/suggestions_list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
app:cardUseCompatPadding="true">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/suggestions_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:isScrollContainer="false"
tools:visibility="visible" />
<!--android:overScrollMode="never"-->
</androidx.cardview.widget.CardView>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:orientation="vertical"
tools:parentTag="android.widget.LinearLayout">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/search_bar_view"
android:layout_width="match_parent"
android:layout_height="@dimen/suggestion_view_height"
app:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="@dimen/dimen8"
android:layout_marginStart="@dimen/dimen8"
android:layout_marginBottom="@dimen/dimen8"
android:visibility="gone"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_action_arrow_back"
tools:ignore="ContentDescription" />
<it.immobiliare.android.widget.SearchInputView
android:id="@+id/search_edit_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignWithParentIfMissing="true"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/btn_back"
android:layout_toStartOf="@+id/btn_clear"
android:layout_marginStart="@dimen/dimen8"
android:background="@color/transparent"
android:imeOptions="flagNoFullscreen|flagNoExtractUi|actionSearch"
android:inputType="textNoSuggestions"
android:textColor="@color/grey_xlight"
android:textSize="@dimen/text_medium"
android:ellipsize="end"
tools:text="Search..."/>
<ImageView
android:id="@+id/btn_clear"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_toStartOf="@+id/btn_options_separator_view"
android:layout_alignWithParentIfMissing="true"
android:layout_centerVertical="true"
android:layout_margin="@dimen/dimen8"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/dimen8"
android:visibility="gone"
app:srcCompat="@drawable/ic_erase"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<ImageView
android:id="@+id/btn_options_separator_view"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="@+id/btn_options"
android:layout_toStartOf="@id/btn_options"
android:background="@color/search_bar_bg" />
<ImageView
android:id="@+id/btn_options"
android:layout_width="@dimen/dimen_32"
android:layout_height="@dimen/dimen_32"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_margin="@dimen/dimen8"
tools:ignore="ContentDescription"
tools:srcCompat="@drawable/ic_posizione" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
<View
android:id="@+id/divider_view"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:layout_marginBottom="5dp"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:background="@color/search_bar_bg"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
<include layout="@layout/suggestion_list_view" />
</merge>
/**
* @author Simone Bellotti
*/
public class SuggestionSearchView extends LinearLayout {
private static final String TAG = "SuggestionSearchView";
private static final double COS_45 = Math.cos(Math.toRadians(45));
private static final boolean DEFAULT_ENABLED_BACK = true;
private static final boolean DEFAULT_WAIT_KEYBOARD_FOR_COLLAPSE = true;
private static final boolean DEFAULT_CLEAR_TEXT_ON_DISMISS = true;
private static final int DEFAULT_DELAY_ANIMATION_IN = -1;
private static final FastOutSlowInInterpolator ANIM_INTERPOLATOR =
new FastOutSlowInInterpolator();
private static final int ANIM_DURATION = 200;
//The CardView's top or bottom height used for its shadow
private static final int CARD_VIEW_TOP_BOTTOM_SHADOW_HEIGHT = 3;
//The CardView's (default) corner radius height
private static final int CARD_VIEW_CORNERS_HEIGHT = 2;
private static final int CARD_VIEW_CORNERS_AND_TOP_BOTTOM_SHADOW_HEIGHT =
CARD_VIEW_TOP_BOTTOM_SHADOW_HEIGHT + CARD_VIEW_CORNERS_HEIGHT;
private boolean isAdjustPanMode;
public interface OnFocusChangeListener {
void onFocus();
void onFocusCleared();
}
public interface OnQueryTextChangeListener {
void onQueryChanged(CharSequence text);
}
public interface OnSuggestionsAnimationListener {
void onSuggestionsIn();
void onSuggestionsOut();
}
public abstract static class SuggestionsAnimationAdapter
implements OnSuggestionsAnimationListener {
@Override
public void onSuggestionsIn() {
}
@Override
public void onSuggestionsOut() {
}
}
@BindView(R2.id.btn_back)
ImageView btnBack;
@BindView(R2.id.search_edit_view)
SearchInputView searchView;
@BindView(R2.id.suggestions_list)
RecyclerView suggestionsList;
@BindView(R2.id.search_suggestions_container)
FrameLayout suggestionsContainer;
@BindView(R2.id.suggestions_list_container)
CardView suggestionListContainer;
@BindView(R2.id.search_bar_view)
CardView searchBarView;
@BindView(R2.id.btn_clear)
ImageView btnClear;
@BindView(R2.id.divider_view)
View dividerView;
@BindView(R2.id.btn_options_separator_view)
ImageView separatorView;
@BindView(R2.id.btn_options)
ImageView btnOptions;
@BindDimen(R2.dimen.dimen16)
int dimen16;
private TextWatcher textWatcher;
KeyListener keyListener;
ValueAnimator valueAnimator;
View activityRootView;
@Nullable
OnFocusChangeListener onFocusChangeListener;
@Nullable
TextView.OnEditorActionListener onEditorActionListener;
OnClickListener onBackClickListener;
OnClickListener onBtnClearClickListener;
OnQueryTextChangeListener onQueryTextChangeListener;
OnSuggestionsAnimationListener onListAnimationListener;
RecyclerView.AdapterDataObserver adapterObserver;
boolean isAnimating;
boolean isFocused;
boolean isOptionsIconEnabled;
boolean isInitialLayout = true;
boolean skipTextChangeEvent;
boolean waitKeyboardForCollapse = DEFAULT_WAIT_KEYBOARD_FOR_COLLAPSE;
boolean clearTextOnDismiss = DEFAULT_CLEAR_TEXT_ON_DISMISS;
int delayAnimationIn = DEFAULT_DELAY_ANIMATION_IN;
@Px
int endListHeight;
@Px
int startListHeight;
@Px
int bottomInsets;
public SuggestionSearchView(Context context) {
super(context);
init(null);
}
public SuggestionSearchView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public SuggestionSearchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public SuggestionSearchView(
Context context,
AttributeSet attrs, int defStyleAttr,
int defStyleRes
) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init(@Nullable AttributeSet attrs) {
inflate(getContext(), R.layout.suggestion_search_layout, this);
ButterKnife.bind(this);
setOrientation(VERTICAL);
setClipToPadding(false);
setBackgroundColor(ContextCompat.getColor(getContext(), R.color.transparent));
setFocusable(true);
setFocusableInTouchMode(true);
if (attrs != null) {
applyXmlAttributes(attrs);
}
initSearchInputView();
initSuggestionsList();
initClearBtn();
initSuggestionsContainer();
initSuggestionsListContainer();
initDividerView();
isAdjustPanMode = (((AppCompatActivity) getContext()).getWindow()
.getAttributes().softInputMode & WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) != 0;
adapterObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
if (isFocused) {
toggleDividerView(getItemCount() > 0);
}
}
};
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH)
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
bottomInsets = insets.getSystemWindowInsetBottom();
return insets.consumeSystemWindowInsets();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
onQueryTextChangeListener = null;
onFocusChangeListener = null;
onListAnimationListener = null;
removePendingAnimation();
removePendingDataObserver();
}
private void initDividerView() {
int padding = (int) Math.ceil(calculateHorizontalPadding(suggestionListContainer));
MarginLayoutParams layoutParams = (MarginLayoutParams) dividerView.getLayoutParams();
layoutParams.setMarginStart(padding);
layoutParams.setMarginEnd(padding);
}
private void applyXmlAttributes(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SuggestionSearchView);
try {
initBackButton(
a.getBoolean(R.styleable.SuggestionSearchView_ssv_enableBack, DEFAULT_ENABLED_BACK)
);
setHint(a.getString(R.styleable.SuggestionSearchView_ssv_searchHint));
setTextHintColor(
a.getColor(R.styleable.SuggestionSearchView_ssv_searchTextHintColor, Color.LTGRAY)
);
setSearchTextColor(
a.getColor(R.styleable.SuggestionSearchView_ssv_searchTextColor, Color.BLACK)
);
waitKeyboardForCollapse = a.getBoolean(
R.styleable.SuggestionSearchView_ssv_waitKeyboardForAnimation,
DEFAULT_WAIT_KEYBOARD_FOR_COLLAPSE
);
clearTextOnDismiss = a.getBoolean(
R.styleable.SuggestionSearchView_ssv_clearTextOnDismiss,
DEFAULT_CLEAR_TEXT_ON_DISMISS
);
delayAnimationIn = a.getInt(
R.styleable.SuggestionSearchView_ssv_delayAnimationIn,
DEFAULT_DELAY_ANIMATION_IN
);
int optionIconResId =
a.getResourceId(R.styleable.SuggestionSearchView_ssv_searchOptionsIcon, 0);
isOptionsIconEnabled = optionIconResId > 0;
if (isOptionsIconEnabled) {
setOptionBtnIcon(optionIconResId);
}
toggleOptionsButtonVisibility(isOptionsIconEnabled);
} finally {
a.recycle();
}
}
public boolean areSuggestionsVisible() {
return suggestionListContainer.getVisibility() == VISIBLE;
}
public CharSequence getText() {
return searchView.getText();
}
public void setOptionBtnIcon(@DrawableRes int resId) {
btnOptions.setImageResource(resId);
}
public void setOnOptionBtnClickListener(OnClickListener onClickListener) {
btnOptions.setOnClickListener(onClickListener);
}
public void setOnButtonClearClickListener(OnClickListener onBtnClearClickListener) {
this.onBtnClearClickListener = onBtnClearClickListener;
}
@Override
public boolean isFocused() {
return isFocused;
}
public int getSearchBarHeight() {
return searchBarView.getHeight();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus && isFocused) {
setFocusInternally(false);
}
}
public void setTextHintColor(@ColorInt int color) {
searchView.setHintTextColor(color);
}
public void setSearchTextColor(@ColorInt int color) {
searchView.setTextColor(color);
}
public void setHint(@Nullable String searchHint) {
String hintText = searchHint != null
? searchHint
: getResources().getString(R.string.abc_search_hint);
searchView.setHint(hintText);
}
public void setHint(@StringRes int resId) {
searchView.setHint(resId);
}
public void setText(CharSequence text) {
searchView.setText(text);
searchView.setSelection(searchView.getText().length());
}
public void setTextIgnoringWatcher(@Nullable CharSequence text) {
searchView.setText(null);
searchView.removeTextChangedListener(textWatcher);
if (!TextUtils.isEmpty(text)) {
searchView.setText(text);
searchView.setSelection(searchView.getText().length());
toggleCancelButtonVisibility(text.length() > 0);
}
searchView.addTextChangedListener(textWatcher);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
searchView.setEnabled(enabled);
}
public void setWaitKeyboardForCollapse(boolean waitKeyboardForCollapse) {
this.waitKeyboardForCollapse = waitKeyboardForCollapse;
}
public void setAdapter(final RecyclerView.Adapter adapter) {
suggestionsList.setAdapter(adapter);
if (adapter != null) {
adapter.registerAdapterDataObserver(adapterObserver);
}
}
public void toggleDividerView(boolean enabled) {
dividerView.setVisibility(enabled ? VISIBLE : GONE);
}
public void setOnFocusChangeListener(@Nullable OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public void setOnBackButtonClickListener(OnClickListener onClickListener) {
this.onBackClickListener = onClickListener;
}
public void setOnQueryTextChangeListener(OnQueryTextChangeListener onQueryTextChangeListener) {
this.onQueryTextChangeListener = onQueryTextChangeListener;
}
public void setImeOptions(int imeOptions) {
searchView.setImeOptions(imeOptions);
}
public void setOnEditorActionListener(TextView.OnEditorActionListener onEditorActionListener) {
this.onEditorActionListener = onEditorActionListener;
}
public void setOnListAnimationListener(OnSuggestionsAnimationListener onListAnimationListener) {
this.onListAnimationListener = onListAnimationListener;
}
public boolean isExpanded() {
return suggestionListContainer.getVisibility() == VISIBLE;
}
void computeAnimationIn() {
endListHeight = 0;
startListHeight = suggestionListContainer.getHeight();
ViewGroup.LayoutParams layoutParams = suggestionListContainer.getLayoutParams();
layoutParams.height = 0;
suggestionListContainer.setLayoutParams(layoutParams);
suggestionsContainer.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (waitKeyboardForCollapse
&& (isWindowAdjustPanMode()
? getKeyboardHeight() <= 0
: suggestionsContainer.getHeight() == startListHeight)
) {
return;
}
ViewTreeObserver vto = suggestionsContainer.getViewTreeObserver();
if (vto.isAlive()) {
vto.removeOnGlobalLayoutListener(this);
}
// Confronta l'altezza iniziale con l'altezza del container
// (resizato dalla tastiera se possibile)
if (startListHeight > suggestionsContainer.getHeight()) {
// animazione solo fino a fine container
endListHeight = suggestionsContainer.getHeight();
} else {
// l'altezza iniziale non ha superato il container
if (isWindowAdjustPanMode()) {
// se siamo in ADJUST_PAN, calcoliamo la size della tastiera
int keyboardHeight = getKeyboardHeight();
MarginLayoutParams marginLayoutParams =
(MarginLayoutParams) getLayoutParams();
marginLayoutParams.bottomMargin = keyboardHeight;
setLayoutParams(marginLayoutParams);
endListHeight = startListHeight > keyboardHeight
? startListHeight - keyboardHeight
: startListHeight;
} else {
endListHeight = startListHeight;
}
}
animateListContainer();
}
});
}
void setFocusInternally(boolean isFocused) {
this.isFocused = isFocused;
if (isFocused) {
searchView.requestFocus();
searchView.setLongClickable(true);
searchView.setEllipsize(null);
searchView.setKeyListener(keyListener);
Utils.showKeyboard(searchView);
if (onFocusChangeListener != null) {
onFocusChangeListener.onFocus();
}
if (delayAnimationIn > 0) {
getHandler().postDelayed(this::computeAnimationIn, delayAnimationIn);
} else {
computeAnimationIn();
}
} else {
searchView.clearFocus();
searchView.setLongClickable(false);
searchView.setEllipsize(TextUtils.TruncateAt.END);
searchView.setKeyListener(null);
Utils.hideKeyboard(searchView);
getHandler().removeCallbacksAndMessages(null);
if (clearTextOnDismiss) {
clearText();
}
if (onFocusChangeListener != null) {
onFocusChangeListener.onFocusCleared();
}
animateListContainer();
}
}
private void initBackButton(boolean btnBackVisible) {
btnBack.setVisibility(btnBackVisible ? VISIBLE : GONE);
if (btnBackVisible) {
btnBack.setOnClickListener(v -> {
if (isFocused) {
setFocusInternally(false);
}
if (onBackClickListener != null) {
onBackClickListener.onClick(v);
}
});
} else {
MarginLayoutParams layoutParams = (MarginLayoutParams) searchView.getLayoutParams();
layoutParams.setMarginStart(dimen16);
}
}
public void clearText() {
searchView.setText(null);
}
void animate(int fromValue, int toValue) {
final boolean animateIn = toValue > fromValue;
valueAnimator = ValueAnimator.ofInt(fromValue, toValue);
valueAnimator.setInterpolator(ANIM_INTERPOLATOR);
valueAnimator.addUpdateListener(animation -> {
setListHeight((int) animation.getAnimatedValue());
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
isAnimating = true;
if (animateIn) {
suggestionListContainer.getLayoutParams().height = 0;
suggestionListContainer.setVisibility(VISIBLE);
}
toggleDividerView(animateIn);
}
@Override
public void onAnimationEnd(Animator animation) {
isAnimating = false;
if (animateIn) {
if (onListAnimationListener != null) {
onListAnimationListener.onSuggestionsIn();
}
if (waitKeyboardForCollapse) {
// aggiungi un ritardo in modo che non glitcha
// se ancora non è apparsa la tastiera
new Handler().postDelayed(
() -> setListHeight(ViewGroup.LayoutParams.WRAP_CONTENT),
100
);
} else {
setListHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
}
} else {
suggestionListContainer.setVisibility(INVISIBLE);
suggestionListContainer.getLayoutParams().height =
ViewGroup.LayoutParams.WRAP_CONTENT;
if (onListAnimationListener != null) {
onListAnimationListener.onSuggestionsOut();
}
if (isWindowAdjustPanMode()) {
MarginLayoutParams marginLayoutParams =
(MarginLayoutParams) getLayoutParams();
marginLayoutParams.bottomMargin = 0;
setLayoutParams(marginLayoutParams);
}
}
}
});
valueAnimator.setDuration(ANIM_DURATION).start();
}
AppCompatActivity getActivity() {
return ((AppCompatActivity) getContext());
}
int getKeyboardHeight() {
Rect visibleRect = new Rect();
if (activityRootView == null) {
activityRootView = getActivityRoot(getActivity());
}
activityRootView.getWindowVisibleDisplayFrame(visibleRect);
return activityRootView.getRootView().getHeight() - visibleRect.bottom - bottomInsets;
}
View getActivityRoot(@NonNull Activity activity) {
return ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
}
boolean isWindowAdjustPanMode() {
return isAdjustPanMode;
}
void setListHeight(int height) {
// if (height == ViewGroup.LayoutParams.WRAP_CONTENT && isWindowAdjustPanMode()) {
// return;
// }
suggestionListContainer.getLayoutParams().height = height;
suggestionListContainer.requestLayout();
}
private void removePendingAnimation() {
if (valueAnimator != null && valueAnimator.isRunning()) {
Logger.d(TAG, "Removing pending list animation");
valueAnimator.cancel();
}
}
private void removePendingDataObserver() {
if (hasAdapter()) {
suggestionsList.getAdapter().unregisterAdapterDataObserver(adapterObserver);
}
}
void animateListContainer() {
// if (isAnimating) {
// return;
// }
if (isOptionsIconEnabled) {
toggleOptionsButtonVisibility(!isFocused);
}
int fromValue;
int toValue;
if (isExpanded()) {
// animateOut
// int listHeight = suggestionListContainer.getHeight();
// from = listHeight < endListHeight ? listHeight : endListHeight;
fromValue = suggestionListContainer.getHeight();
toValue = 0;
} else {
// animateIn
fromValue = 0;
toValue = endListHeight;
}
animate(fromValue, toValue);
}
boolean hasAdapter() {
return suggestionsList.getAdapter() != null;
}
int getItemCount() {
return suggestionsList.getAdapter() != null
? suggestionsList.getAdapter().getItemCount()
: 0;
}
private float calculateVerticalPadding(CardView cardView) {
float maxCardElevation = cardView.getMaxCardElevation();
float cornerRadius = cardView.getRadius();
boolean addPaddingForCorners = cardView.getPreventCornerOverlap();
if (addPaddingForCorners) {
return (float) (maxCardElevation * 1.5f + (1 - COS_45) * cornerRadius);
} else {
return maxCardElevation * 1.5f;
}
}
private float calculateHorizontalPadding(CardView cardView) {
float maxCardElevation = cardView.getMaxCardElevation();
float cornerRadius = cardView.getRadius();
boolean addPaddingForCorners = cardView.getPreventCornerOverlap();
if (addPaddingForCorners) {
return (float) (maxCardElevation + (1 - COS_45) * cornerRadius);
} else {
return maxCardElevation;
}
}
private void initSuggestionsContainer() {
float cardViewBottomPadding =
Utils.pxFromDp(getContext(), CARD_VIEW_CORNERS_AND_TOP_BOTTOM_SHADOW_HEIGHT);
// float cardViewBottomPadding =
// (float) Math.ceil(calculateVerticalPadding(suggestionListContainer));
// move up the suggestions section enough to cover the search bar
// card's bottom left and right corners
suggestionsContainer.setTranslationY(-cardViewBottomPadding);
}
private void initSuggestionsListContainer() {
float cardViewBottomPadding =
Utils.pxFromDp(getContext(), CARD_VIEW_CORNERS_AND_TOP_BOTTOM_SHADOW_HEIGHT);
// float cardViewBottomPadding =
// (float) Math.ceil(calculateVerticalPadding(suggestionListContainer));
suggestionListContainer.setTranslationY(-cardViewBottomPadding);
}
private void initSuggestionsList() {
suggestionsList.setLayoutManager(new LinearLayoutManager(getContext()));
}
private void initClearBtn() {
btnClear.setOnClickListener(v -> {
clearText();
if (!isFocused) {
setFocusInternally(true);
}
if (onBtnClearClickListener != null) {
onBtnClearClickListener.onClick(v);
}
});
}
private void initSearchInputView() {
keyListener = searchView.getKeyListener();
searchView.setOnFocusChangeListener((v, hasFocus) -> {
if ((hasFocus != isFocused) && isEnabled()) {
setFocusInternally(hasFocus);
}
});
searchView.setOnEditorActionListener((v, actionId, event) -> {
boolean isEnterEvent = event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER;
boolean isActionDone = actionId == EditorInfo.IME_ACTION_DONE;
if (isActionDone || isEnterEvent) {
setFocusInternally(false);
}
return onEditorActionListener != null
&& onEditorActionListener.onEditorAction(v, actionId, event);
});
searchView.setOnKeyboardDismissedListener(() -> setFocusInternally(false));
textWatcher = new TextWatcherAdapter() {
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
if (skipTextChangeEvent) {
skipTextChangeEvent = false;
} else {
boolean enableCancelBtn = !TextUtils.isEmpty(text);
toggleCancelButtonVisibility(enableCancelBtn);
if (onQueryTextChangeListener != null) {
onQueryTextChangeListener.onQueryChanged(text);
}
}
}
};
searchView.addTextChangedListener(textWatcher);
}
void toggleOptionsButtonVisibility(boolean visible) {
int visibility = visible ? VISIBLE : GONE;
btnOptions.setVisibility(visibility);
separatorView.setVisibility(visibility);
}
void toggleCancelButtonVisibility(boolean visible) {
btnClear.setVisibility(visible ? VISIBLE : GONE);
}
@Nullable
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
return new SavedState(superState);
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
skipTextChangeEvent = true;
}
private static class SavedState extends BaseSavedState {
boolean isFocused;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel in) {
super(in);
isFocused = (in.readInt() != 0);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(isFocused ? 1 : 0);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
/**
* @author Simone Bellotti
*/
public class TextWatcherAdapter implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence text, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable text) {
}
}
public class Utils {
public static void showKeyboard(@Nullable EditText editText) {
if (editText != null) {
editText.requestFocus();
InputMethodManager imm =
(InputMethodManager) editText.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
// imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
public static void hideKeyboard(@Nullable EditText editText) {
if (editText != null) {
editText.clearFocus();
InputMethodManager imm =
(InputMethodManager) editText.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
}
public static float pxFromDp(Context ctx, float dp) {
return dp * ctx.getResources().getDisplayMetrics().density;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment