Last active
August 17, 2021 08:36
-
-
Save alexzaitsev/081d94be6038d2b4c699 to your computer and use it in GitHub Desktop.
Android popup error for TextView without focus
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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