Skip to content

Instantly share code, notes, and snippets.

@alexzaitsev
Last active August 17, 2021 08:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexzaitsev/081d94be6038d2b4c699 to your computer and use it in GitHub Desktop.
Save alexzaitsev/081d94be6038d2b4c699 to your computer and use it in GitHub Desktop.
Android popup error for TextView without focus
protected ErrorPopupHelper errorHelper = new ErrorPopupHelper();
mEditTextEmail.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (!hasFocus) {
if (!ValidatorUtil.isValidEmail(getEmail())) {
errorHelper.setError(mEditTextEmail, R.string.email_validation_error);
}
}
}
});
/**
* As TextView (and EditText as a subclass)
* can show error if it is in focus only
* (many thanks to person who added this if condition to Android source code) -
* we have to create own popup window and style it with custom styles
* (internal styles are unavailable for developers).
* This is simple implementation of popup window.
*/
public class ErrorPopup extends PopupWindow {
private final TextView mView;
private boolean mAbove = false;
public ErrorPopup(TextView v, int width, int height) {
super(v, width, height);
mView = v;
mView.setBackgroundResource(R.color.text_gray); // TODO provide actual resource
}
public void fixDirection(boolean above) {
mAbove = above;
mView.setBackgroundResource(above ? R.color.text_gray : R.color.text_gray); // TODO provide actual resources
}
@Override
public void update(int x, int y, int w, int h, boolean force) {
super.update(x, y, w, h, force);
boolean above = isAboveAnchor();
if (above != mAbove) {
fixDirection(above);
}
}
}
/**
* As TextView (and EditText as a subclass)
* can show error if it is in focus only
* (many thanks to person who added this if condition to Android source code) -
* we have to create own popup window and style it with custom styles
* (internal styles are unavailable for developers).
* This is helper to show / hide popup errors and drawable icons for TextView.
*/
public class ErrorPopupHelper {
private ErrorPopup mErrorPopup;
private TextView lastTextView;
/**
* Hide error if user starts to write in this view
*/
private TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (mErrorPopup != null && mErrorPopup.isShowing()) {
cancel();
}
}
@Override
public void afterTextChanged(Editable editable) {
}
};
public void setError(TextView mTextView, int stringResId) {
setError(mTextView, mTextView.getContext().getString(stringResId));
}
public void setError(TextView mTextView, CharSequence error) {
cancel();
CharSequence mError = TextUtils.stringOrSpannedString(error);
if (mError != null) {
showError(mTextView, error.toString());
}
mTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
mError == null ? 0 : android.R.drawable.stat_notify_error, 0);
mTextView.addTextChangedListener(textWatcher);
lastTextView = mTextView;
}
public void cancel() {
if (lastTextView != null && lastTextView.getWindowToken() != null) {
hideError();
lastTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
lastTextView.removeTextChangedListener(textWatcher);
lastTextView = null;
}
}
private void showError(TextView mTextView, String error) {
if (mTextView.getWindowToken() == null) {
return;
}
if (mErrorPopup == null) {
LayoutInflater inflater = LayoutInflater.from(mTextView.getContext());
final TextView err = (TextView) inflater.inflate(R.layout.view_error_hint, null);
final float scale = mTextView.getResources().getDisplayMetrics().density;
mErrorPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
mErrorPopup.setFocusable(false);
// The user is entering text, so the input method is needed. We
// don't want the popup to be displayed on top of it.
mErrorPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
TextView tv = (TextView) mErrorPopup.getContentView();
chooseSize(mErrorPopup, error, tv);
tv.setText(error);
mErrorPopup.showAsDropDown(mTextView, getErrorX(mTextView), 0);
mErrorPopup.fixDirection(mErrorPopup.isAboveAnchor());
}
private void hideError() {
if (mErrorPopup != null) {
if (mErrorPopup.isShowing()) {
mErrorPopup.dismiss();
}
mErrorPopup = null;
}
}
private int getErrorX(TextView mTextView) {
return mTextView.getWidth() - mErrorPopup.getWidth() - mTextView.getPaddingRight();
}
private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
int wid = tv.getPaddingLeft() + tv.getPaddingRight();
int ht = tv.getPaddingTop() + tv.getPaddingBottom();
int defaultWidthInPixels = tv.getResources().getDimensionPixelSize(
R.dimen.textview_error_popup_default_width);
Layout l = new StaticLayout(text, tv.getPaint(), defaultWidthInPixels,
Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
float max = 0;
for (int i = 0; i < l.getLineCount(); i++) {
max = Math.max(max, l.getLineWidth(i));
}
/*
* Now set the popup size to be big enough for the text plus the border capped
* to DEFAULT_MAX_POPUP_WIDTH
*/
pop.setWidth(wid + (int) Math.ceil(max));
pop.setHeight(ht + l.getHeight());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment