I have a TextView in which every word is a ClickableSpan (actually a custom subclass of ClickableSpan). When a word is touched, it should be shown in bold font style. If I set textIsSelectable(false) on the TextView, it works just fine. The word is immediately bolded. But if text is selectable, then it does not work. BUT - if I touch a word and then turn the screen off and back on, when the screen display comes back on the word is bolded. I have tried everything I can think of to force a redraw (invalidate the TextView, force call Activity's onRestart(), refreshDrawableState() on the TextView, etc). What am I missing?
Here is my subclass of ClickableSpan:
public class WordSpan extends ClickableSpan
{
int id;
private boolean marking = false;
TextPaint tp;
Typeface font;
int color = Color.BLACK;
public WordSpan(int id, Typeface font, boolean marked) {
this.id = id;
marking = marked;
this.font = font;
}
#Override
public void updateDrawState(TextPaint ds) {
ds.setColor(color);
ds.setUnderlineText(false);
if (marking)
ds.setTypeface(Typeface.create(font,Typeface.BOLD));
tp = ds;
}
#Override
public void onClick(View v) {
// Empty here -- overriden in activity
}
public void setMarking(boolean m) {
marking = m;
updateDrawState(tp);
}
public void setColor(int col) {
color = col;
}
}
Here is the WordSpan instantiation code in my Activity:
... looping through words
curSpan = new WordSpan(index,myFont,index==selectedWordId) {
#Override
public void onClick(View view) {
handleWordClick(index,this);
setMarking(true);
tvText.invalidate();
}
};
... continue loop code
And here is my custom MovementMethod:
public static MovementMethod createMovementMethod ( Context context ) {
final GestureDetector detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapUp ( MotionEvent e ) {
return true;
}
#Override
public boolean onSingleTapConfirmed ( MotionEvent e ) {
return false;
}
#Override
public boolean onDown ( MotionEvent e ) {
return false;
}
#Override
public boolean onDoubleTap ( MotionEvent e ) {
return false;
}
#Override
public void onShowPress ( MotionEvent e ) {
return;
}
});
return new ScrollingMovementMethod() {
#Override
public boolean canSelectArbitrarily () {
return true;
}
#Override
public void initialize(TextView widget, Spannable text) {
Selection.setSelection(text, text.length());
}
#Override
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
if (view.getLayout() == null) {
// This shouldn't be null, but do something sensible if it is.
Selection.setSelection(text, text.length());
}
} else {
Selection.setSelection(text, text.length());
}
}
#Override
public boolean onTouchEvent ( TextView widget, Spannable buffer, MotionEvent event ) {
// check if event is a single tab
boolean isClickEvent = detector.onTouchEvent(event);
// detect span that was clicked
if (isClickEvent) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
WordSpan[] link = buffer.getSpans(off, off, WordSpan.class);
if (link.length != 0) {
// execute click only for first clickable span
// can be a for each loop to execute every one
if (event.getAction() == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
return true;
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
return false;
}
}
else {
}
}
// let scroll movement handle the touch
return super.onTouchEvent(widget, buffer, event);
}
};
}
Your spans are somehow becoming immutable when the text is set as selectable (TextView#setTextIsSelectable(true)). Here is a good write-up on Understanding Spans that explains mutability of spans. I also think that this post has some good explanations
I am not sure how your spans are getting to be immutable. Maybe they are mutable but just not showing somehow? It is unclear. Maybe someone has an explanation for this behavior. But, for now, here is a fix:
When you rotate your device or turn it off and back on, the spans are recreated or just reapplied. That is why you see the change. The fix is to not try to change the spans when clicked, but to reapply it with the font bolded. That way the change will take effect. You will not even need to call invalidate(). Keep track of the bolded span so it can be unbolded later when another span is clicked.
Here is the result:
Here is main activity. (Please forgive all the hard-coding, but this is just a sample.)
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private WordSpan mBoldedSpan;
#Override
protected void onCreate(Bundle savedInstanceState) {
Typeface myFont = Typeface.DEFAULT;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textView);
mTextView.setTextIsSelectable(true);
mTextView.setMovementMethod(createMovementMethod(this));
SpannableString ss = new SpannableString("Hello world! ");
int[][] spanStartEnd = new int[][]{{0, 5}, {6, 12}};
for (int i = 0; i < spanStartEnd.length; i++) {
WordSpan wordSpan = new WordSpan(i, myFont, false) {
#Override
public void onClick(View view) {
// handleWordClick(index, this); // Not sure what this does.
Spannable ss = (Spannable) mTextView.getText();
if (mBoldedSpan != null) {
reapplySpan(ss, mBoldedSpan, false);
}
reapplySpan(ss, this, true);
mBoldedSpan = this;
}
private void reapplySpan(Spannable spannable, WordSpan span, boolean isBold) {
int spanStart = spannable.getSpanStart(span);
int spanEnd = spannable.getSpanEnd(span);
span.setMarking(isBold);
spannable.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
};
ss.setSpan(wordSpan, spanStartEnd[i][0], spanStartEnd[i][1],
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
mTextView.setText(ss, TextView.BufferType.SPANNABLE);
}
// All the other code follows without modification.
}
activity_main.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.04000002"
tools:text="Hello World!" />
</android.support.constraint.ConstraintLayout>
Here is version that uses a StyleSpan. The results are the same.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private StyleSpan mBoldedSpan;
#Override
protected void onCreate(Bundle savedInstanceState) {
Typeface myFont = Typeface.DEFAULT;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textView);
mTextView.setTextIsSelectable(true);
mTextView.setMovementMethod(createMovementMethod(this));
mBoldedSpan = new StyleSpan(android.graphics.Typeface.BOLD);
SpannableString ss = new SpannableString("Hello world!");
int[][] spanStartEnd = new int[][]{{0, 5}, {6, 12}};
for (int i = 0; i < spanStartEnd.length; i++) {
WordSpan wordSpan = new WordSpan(i, myFont, false) {
#Override
public void onClick(View view) {
// handleWordClick(index, this); // Not sure what this does.
Spannable ss = (Spannable) mTextView.getText();
ss.setSpan(mBoldedSpan, ss.getSpanStart(this), ss.getSpanEnd(this),
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
};
ss.setSpan(wordSpan, spanStartEnd[i][0], spanStartEnd[i][1],
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
mTextView.setText(ss, TextView.BufferType.SPANNABLE);
}
// All the other code follows without modification.
}
I wrote a little STT-functionality, with a floating button that is pulsating after being clicked on to notify that the app is listening. This works quite well so far with the one annoying behavior that my floating button does not return to its original size in some cases.
The animation increases and decreases the size of the button, and I guess it gets stuck in the increased state, hence the randomness of this behavior. I just can't figure out how to catch that and set the size to the original one.
Action Listener of my Button:
private View.OnTouchListener setVoiceButtonOnClick()
{
return new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if(!voiceButton.isInitialized())
voiceButton.initAnimationValues();
voiceButton.setPressed(true);
listen();
}
return true;
}
};
}
My Button extends FloatingActionButton, and does the following:
public class FloatingVoiceButton extends FloatingActionButton
{
public static final float DEFAULT_ANIMATION_FACTOR = 1.2f;
private boolean isInitialized = false;
private int originalHeight;
private int originalWidth;
private boolean isAnimationRunning;
private ObjectAnimator animator;
public FloatingVoiceButton(Context context)
{
super(context);
}
public void initAnimationValues()
{
isInitialized = true;
isAnimationRunning = false;
originalHeight = getMeasuredHeight();
originalWidth = getMeasuredWidth();
animator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", DEFAULT_ANIMATION_FACTOR),
PropertyValuesHolder.ofFloat("scaleY", DEFAULT_ANIMATION_FACTOR));
animator.setDuration(200);
animator.setRepeatCount(ObjectAnimator.INFINITE);
animator.setRepeatMode(ObjectAnimator.REVERSE);
}
public boolean isInitialized()
{
return isInitialized;
}
public void resetButtonSize()
{
setMeasuredDimension(originalWidth, originalHeight);
}
public boolean isAnimationRunning()
{
return isAnimationRunning;
}
public void animate(boolean doAnimation)
{
isAnimationRunning = doAnimation;
if(doAnimation)
animator.start();
else
{
animator.end();
setPressed(false);
resetButtonSize();
//destroyDrawingCache(); tried these without success
//postInvalidate();
}
}
}
Finally I am controlling the button the start and end of the animation with my RecognitionListener:
public class InputVoiceRecognitionListener implements RecognitionListener
{
private EditText targetEditText;
private String originalContent;
private final String DELIMITER = "\n\n";
private FloatingVoiceButton button;
public InputVoiceRecognitionListener(EditText editText, FloatingVoiceButton button)
{
targetEditText = editText;
originalContent = editText.getText().toString();
this.button = button;
}
#Override
public void onReadyForSpeech(Bundle params)
{
button.animate(true);
}
#Override
public void onBeginningOfSpeech()
{
originalContent = targetEditText.getText().toString();
}
#Override
public void onRmsChanged(float rmsdB)
{}
#Override
public void onBufferReceived(byte[] buffer)
{}
#Override
public void onEndOfSpeech()
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onError(int error)
{
if(button.isAnimationRunning())
button.animate(false);
}
#Override
public void onResults(Bundle results)
{
setRecognizedText(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onPartialResults(Bundle partialResults)
{
setRecognizedText(partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
}
#Override
public void onEvent(int eventType, Bundle params)
{
}
private void setRecognizedText(ArrayList<String> matches)
{
String result = "";
if(matches != null)
result = matches.get(0);
if((originalContent.trim()).length() > 0)
{
if(!originalContent.endsWith("\n\n"))
result = originalContent + DELIMITER + result;
else result = originalContent + result;
}
targetEditText.setText(result);
targetEditText.setSelection(result.length());
}
}
EDIT
This did it for me:
resettingAnimator = ObjectAnimator.ofPropertyValuesHolder(
this,
PropertyValuesHolder.ofFloat("scaleX", 1.0f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f));
resettingAnimator.setDuration(0);
resettingAnimator.setRepeatCount(1);
and calling resettingAnimator.start(); when I finish my main animation.
Simple solution to this problem is that you define another animation after stopping your repeating one.
I just can't figure out how to catch that and set the size to the original one.
You, that is View, does know what is the "original" size, its the size of the scale factor 1f. So after stopping repeating animation just make another animations to set scale to 1f
PropertyValuesHolder.ofFloat("scaleX", 1f)
PropertyValuesHolder.ofFloat("scaleY", 1f))
This animation will run always, but will not be visible if your button is already at "normal" size.
With this in mind I would recommend that you use some other flag than isAnimationRunning(), either by some state (ex. selected) of your Fab, or some manually set arbitrary boolean.
I want to disable press of other keys on press of some keys in my custom keyboard and i also want to disable touches on outside my custom keyboard because touching outside and holding some keys and pressing other keys automatically inserting characters in edittext.i want to stop that.
My CustomKeyBoardClass here
public class CustomKeyboard extends View {
int touchPosition;
int id;
/**
* A link to the KeyboardView that is used to render this CustomKeyboard.
*/
private KeyboardView mKeyboardView;
/**
* A link to the activity that hosts the {#link #mKeyboardView}.
*/
private AppCompatActivity mHostActivity;
private Keyboard mKeyBoard;
private View mInflater;
Boolean longpressded=false;
View custom = LayoutInflater.from(getContext())
.inflate(R.layout.kbpreview, new FrameLayout(getContext()));
PopupWindow popup = new PopupWindow(this);
/**
* The key (code) handler.
*/
private KeyboardView.OnKeyboardActionListener mOnKeyboardActionListener = new KeyboardView.OnKeyboardActionListener() {
public final static int CodeDelete = -5; // Keyboard.KEYCODE_DELETE
public final static int CodeCancel = -3; // Keyboard.KEYCODE_CANCEL
public final static int CodePrev = 55000;
public final static int CodeAllLeft = 55001;
public final static int CodeLeft = 55002;
public final static int CodeRight = 55003;
public final static int CodeAllRight = 55004;
public final static int CodeNext = 55005;
public final static int CodeClear = 55006;
public final static int CodeDone = 55007;
public final static int CodeCaps = -1;
public final static int QwertNumberKeypad = 1100;
public final static int QwertNumericKeypad = 1101;
public final static int CodeBackSpace = -4;
private boolean caps = false;
#Override
public void onKey(final int primaryCode, int[] keyCodes) {
// NOTE We can say '<Key android:codes="49,50" ... >' in the xml
// file; all codes come in keyCodes, the first in this list in
// primaryCode
// Get the EditText and its Editable
View focusCurrent = mHostActivity.getWindow().getCurrentFocus();
if (focusCurrent == null
|| focusCurrent.getClass() != AppCompatEditText.class)
return;
final AppCompatEditText edittext = (AppCompatEditText) focusCurrent;
edittext.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
String searchString = s.toString();
int textLength = searchString.length();
// edittext.setSelection(textLength);
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void afterTextChanged(Editable s) {
}
});
// System.out.println("print edit text id==="+edittext.getId());
// if(edittext.getId()==R.id.ed_hack){
//
// // invisible the hack============fix it later
// System.out.println("id matched");
// //edittext.setVisibility(View.INVISIBLE);
//
// // make the layout visible==========
//
// mHostActivity.findViewById(R.id.ll_enter_id).setVisibility(View.VISIBLE);
//
// // Show the input field and focus on keypad
// touched================
// ((EditText)mHostActivity.findViewById(R.id.et_enter_id_landing)).requestFocus();
//
//
// }
Editable editables = edittext.getText();
int start = edittext.getSelectionStart();
int end = edittext.getSelectionEnd();
// Apply the key to the edittext
if (primaryCode == CodeCancel) {
hideCustomKeyboard();
} else if (primaryCode == CodeDelete) {
if (editables != null && start > 0) {
editables.delete(start - 1, start);
edittext.setSelection(start - 1);
}
}
/*
* else if( primaryCode==CodeBackSpace ) { if( editable!=null && end
* >0 ) editable.delete(end - 1, end); }
*/
else if (primaryCode == CodeClear) {
if (editables != null)
editables.clear();
} else if (primaryCode == CodeLeft) {
if (start > 0)
edittext.setSelection(start - 1);
} else if (primaryCode == CodeRight) {
if (start < edittext.length())
edittext.setSelection(start + 1);
} else if (primaryCode == CodeAllLeft) {
edittext.setSelection(0);
} else if (primaryCode == CodeAllRight) {
edittext.setSelection(edittext.length());
} else if (primaryCode == CodePrev) {
View focusNew = edittext.focusSearch(View.FOCUS_BACKWARD);
if (focusNew != null)
focusNew.requestFocus();
} else if (primaryCode == CodeNext) {
View focusNew = edittext.focusSearch(View.FOCUS_FORWARD);
if (focusNew != null)
focusNew.requestFocus();
} else if (primaryCode == CodeDone) {
hideCustomKeyboard();
}
// for capslock
else if (primaryCode == CodeCaps) {
caps = !caps;
mKeyBoard.setShifted(caps);
mKeyboardView.invalidateAllKeys();
// edittext.setInputType(InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
//
// edittext.setFilters(new InputFilter[]{new
// InputFilter.AllCaps()});
} else { // insert character
if (primaryCode != QwertNumericKeypad && primaryCode != QwertNumberKeypad ) {
char code = (char) primaryCode;
if (Character.isLetter(code) && caps) {
code = Character.toUpperCase(code);
}
editables.insert(start, Character.toString((char) code));
}
}
Log.d("Gajanand", "onKey: at first" + caps);
if (primaryCode == QwertNumberKeypad) {
mKeyboardView.setKeyboard(new Keyboard(mHostActivity,
R.xml.qwerty));
mKeyboardView.invalidateAllKeys();
Log.d("Gajanand", "onKey:in number keypad " + caps);
}
if (primaryCode == QwertNumericKeypad) {
mKeyboardView.setKeyboard(new Keyboard(mHostActivity,
R.xml.qwerty_numbers));
mKeyboardView.invalidateAllKeys();
Log.d("Gajanand", "onKey:in numeric keypad " + caps);
}
popup.setContentView(custom);
mKeyboardView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mKeyboardView.closing(); // Close popup keyboard if it's showing
}
return false;
}
});
}
#Override
public void onPress(int arg0) {
Log.d("Gajanand", "onPress:GK ");
}
#Override
public void onRelease(int primaryCode) {
Log.d("Gajanand", "onRelease: GK");
}
#Override
public void onText(CharSequence text) {
}
#Override
public void swipeDown() {
}
#Override
public void swipeLeft() {
}
#Override
public void swipeRight() {
}
#Override
public void swipeUp() {
}
};
/**
* Create a custom keyboard, that uses the KeyboardView (with resource id
* <var>viewid</var>) of the <var>host</var> activity, and load the keyboard
* layout from xml file <var>layoutid</var> (see {#link Keyboard} for
* description). Note that the <var>host</var> activity must have a
* <var>KeyboardView</var> in its layout (typically aligned with the bottom
* of the activity). Note that the keyboard layout xml file may include key
* codes for navigation; see the constants in this class for their values.
* Note that to enable EditText's to use this custom keyboard, call the
* {#link #registerEditText(int)}.
*
* #param host The hosting activity.
* #param viewid The id of the KeyboardView.
* #param layoutid The id of the xml file containing the keyboard layout.
*/
public CustomKeyboard(AppCompatActivity host, int viewid, int layoutid) {
super(host);
mHostActivity = host;
mKeyboardView = (KeyboardView) mHostActivity.findViewById(viewid);
mKeyBoard = new Keyboard(mHostActivity, layoutid);
mKeyboardView.setKeyboard(mKeyBoard);
mKeyboardView.setPreviewEnabled(false); // NOTE Do not show the preview
// balloons
mKeyboardView.setOnKeyboardActionListener(mOnKeyboardActionListener);
// Hide the standard keyboard initially
mHostActivity.getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
// #Override
// protected void onSelectionChanged(int selStart, int selEnd) {
// super.onSelectionChanged(selStart, selEnd);
//
// Log.d("Gajanand", "onSelectionChanged: yes changed");
// }
public CustomKeyboard(AppCompatActivity host, View inflater, int viewid, int layoutid) {
super(host);
mHostActivity = host;
mInflater = inflater;
mKeyboardView = (KeyboardView) mInflater.findViewById(viewid);
mKeyBoard = new Keyboard(mHostActivity, layoutid);
mKeyboardView.setKeyboard(mKeyBoard);
mKeyboardView.setPreviewEnabled(true); // NOTE Do not show the preview
// balloons
mKeyboardView.setOnKeyboardActionListener(mOnKeyboardActionListener);
// Hide the standard keyboard initially
mHostActivity.getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
/**
* Returns whether the CustomKeyboard is visible.
*/
public boolean isCustomKeyboardVisible() {
return mKeyboardView.getVisibility() == View.VISIBLE;
}
/**
* Make the CustomKeyboard visible, and hide the system keyboard for view v.
*/
public void showCustomKeyboard(View v) {
mKeyboardView.setVisibility(View.VISIBLE);
mKeyboardView.setEnabled(true);
if (v != null)
((InputMethodManager) mHostActivity
.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
/**
* Make the CustomKeyboard invisible.
*/
public void hideCustomKeyboard() {
mKeyboardView.setVisibility(View.GONE);
mKeyboardView.setEnabled(false);
sendStatusToActivity("hide");
}
private void sendStatusToActivity(String msg) {
Intent intent = new Intent("KEYPAD");
intent.putExtra("Status", msg);
LocalBroadcastManager.getInstance(mHostActivity).sendBroadcast(intent);
// stopservice();
}
/**
* Register <var>EditText<var> with resource id <var>resid</var> (on the
* hosting activity) for using this custom keyboard.
*
* #param resid The resource id of the EditText that registers to the custom
* keyboard.
*/
public void registerEditText(int resid) {
// Find the EditText 'resid'
AppCompatEditText edittext;
/*View focusCurrent = mHostActivity.getWindow().getCurrentFocus();
if (focusCurrent == null
|| focusCurrent.getClass() != AppCompatEditText.class)
return;
final AppCompatEditText edittext = (AppCompatEditText) focusCurrent;*/
edittext = (AppCompatEditText) mHostActivity.findViewById(resid);
if (edittext == null) {
edittext = (AppCompatEditText) mInflater.findViewById(resid);
}
// Make the custom keyboard appear
edittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
// NOTE By setting the on focus listener, we can show the custom
// keyboard when the edit box gets focus, but also hide it when the
// edit box loses focus
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus)
showCustomKeyboard(v);
else
hideCustomKeyboard();
}
});
edittext.setOnClickListener(new View.OnClickListener() {
// NOTE By setting the on click listener, we can show the custom
// keyboard again, by tapping on an edit box that already had focus
// (but that had the keyboard hidden).
#Override
public void onClick(View v) {
Log.d("kishan", "onClick: keypad");
showCustomKeyboard(v);
}
});
// Disable standard keyboard hard way
// NOTE There is also an easy way:
// 'edittext.setInputType(InputType.TYPE_NULL)' (but you will not have a
// cursor, and no 'edittext.setCursorVisible(true)' doesn't work )
edittext.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("kishan", "onTouch: keypad");
showCustomKeyboard(v);
AppCompatEditText edittext = (AppCompatEditText) v;
float x = event.getX();
float y = event.getY();
touchPosition = edittext.getOffsetForPosition(x, y);
if (touchPosition > 0) {
edittext.setSelection(touchPosition);
}
int inType = edittext.getInputType(); // Backup the input type
edittext.setInputType(InputType.TYPE_NULL); // Disable standard
// keyboard
edittext.onTouchEvent(event); // Call native handler
edittext.setInputType(inType); // Restore input type
return true; // Consume touch event
}
});
// Disable spell check (hex strings look like words to Android)
edittext.setInputType(edittext.getInputType()
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
public void registerAutoCompleteEditText(int resid) {
// Find the EditText 'resid'
System.out.println("hello ");
AutoCompleteTextView autoCompleteTextView;
autoCompleteTextView = (AutoCompleteTextView) mHostActivity.findViewById(resid);
if (autoCompleteTextView == null) {
autoCompleteTextView = (AutoCompleteTextView) mInflater.findViewById(resid);
}
System.out.println("hello 1");
// Make the custom keyboard appear
autoCompleteTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
// NOTE By setting the on focus listener, we can show the custom
// keyboard when the edit box gets focus, but also hide it when the
// edit box loses focus
#Override
public void onFocusChange(View v, boolean hasFocus) {
System.out.println("hello 2");
if (hasFocus)
showCustomKeyboard(v);
else
hideCustomKeyboard();
}
});
autoCompleteTextView.setOnClickListener(new View.OnClickListener() {
// NOTE By setting the on click listener, we can show the custom
// keyboard again, by tapping on an edit box that already had focus
// (but that had the keyboard hidden).
#Override
public void onClick(View v) {
showCustomKeyboard(v);
}
});
// Disable standard keyboard hard way
// NOTE There is also an easy way:
// 'edittext.setInputType(InputType.TYPE_NULL)' (but you will not have a
// cursor, and no 'edittext.setCursorVisible(true)' doesn't work )
autoCompleteTextView.setOnTouchListener(new View.OnTouchListener() {
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
#Override
public boolean onTouch(View v, MotionEvent event) {
AutoCompleteTextView edittext = (AutoCompleteTextView) v;
float x = event.getX();
float y = event.getY();
touchPosition = edittext.getOffsetForPosition(x, y);
if (touchPosition > 0) {
edittext.setSelection(touchPosition);
}
int inType = edittext.getInputType(); // Backup the input type
edittext.setInputType(InputType.TYPE_NULL); // Disable standard
// keyboard
edittext.onTouchEvent(event); // Call native handler
edittext.setInputType(inType); // Restore input type
return true; // Consume touch event
}
});
// Disable spell check (hex strings look like words to Android)
System.out.println("hello 5");
autoCompleteTextView.setInputType(autoCompleteTextView.getInputType()
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
}
Android Studio 2.3.1
I am trying to create some text that is not web or html but just some normal text that I want to look like a web link that will be clickable when clicked.
The text is this: Contains 3 reviews
And I want to make it look like a clickable web link.
private void setupTextViewAsLinkClickable() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mTvReviews.setMovementMethod(LinkMovementMethod.getInstance());
mTvReviews.setText(Html.fromHtml("Contains 3 reviews", Html.FROM_HTML_MODE_LEGACY));
}
else {
mTvReviews.setMovementMethod(LinkMovementMethod.getInstance());
mTvReviews.setText(Html.fromHtml("Contains 3 reviews"));
}
}
I have also tried this as well for my xml:
<TextView
android:id="#+id/tvReviews"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-condensed"
android:autoLink="all"
android:linksClickable="true"
android:clickable="true"
android:textSize="#dimen/runtime_textsize"
android:text="Contains 3 reviews" />
try with this code, its working code in my project.
SpannableString ss = new SpannableString("Android is a Software stack");
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
startActivity(new Intent(MyActivity.this, NextActivity.class));
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
};
ss.setSpan(clickableSpan, 22, 27, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView textView = (TextView) findViewById(R.id.hello);
textView.setText(ss);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);
How to set the part of the text view is clickable
You can use this class to make a textview look like web url and even clickable
public final class LinkUtils {
public static final Pattern URL_PATTERN =
Pattern.compile("((https?|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\#&=+\\$,%#]+))");
public interface OnClickListener {
void onLinkClicked(final String link);
void onClicked();
}
static class SensibleUrlSpan extends URLSpan {
/**
* Pattern to match.
*/
private Pattern mPattern;
public SensibleUrlSpan(String url, Pattern pattern) {
super(url);
mPattern = pattern;
}
public boolean onClickSpan(View widget) {
boolean matched = mPattern.matcher(getURL()).matches();
if (matched) {
super.onClick(widget);
}
return matched;
}
}
static class SensibleLinkMovementMethod extends LinkMovementMethod {
private boolean mLinkClicked;
private String mClickedLink;
#Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP) {
mLinkClicked = false;
mClickedLink = null;
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
SensibleUrlSpan span = (SensibleUrlSpan) link[0];
mLinkClicked = span.onClickSpan(widget);
mClickedLink = span.getURL();
return mLinkClicked;
}
}
super.onTouchEvent(widget, buffer, event);
return false;
}
public boolean isLinkClicked() {
return mLinkClicked;
}
public String getClickedLink() {
return mClickedLink;
}
}
public static void autoLink(final TextView view, final OnClickListener listener) {
autoLink(view, listener, null);
}
public static void autoLink(final TextView view, final OnClickListener listener,
final String patternStr) {
String text = view.getText().toString();
if (TextUtils.isEmpty(text)) {
return;
}
Spannable spannable = new SpannableString(text);
Pattern pattern;
if (TextUtils.isEmpty(patternStr)) {
pattern = URL_PATTERN;
} else {
pattern = Pattern.compile(patternStr);
}
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
SensibleUrlSpan urlSpan = new SensibleUrlSpan(matcher.group(1), pattern);
spannable.setSpan(urlSpan, matcher.start(1), matcher.end(1),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
view.setText(spannable, TextView.BufferType.SPANNABLE);
final SensibleLinkMovementMethod method = new SensibleLinkMovementMethod();
view.setMovementMethod(method);
if (listener != null) {
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (method.isLinkClicked()) {
listener.onLinkClicked(method.getClickedLink());
} else {
listener.onClicked();
}
}
});
}
}
}
And in activity call
String testStr = "Text 。http://www.yahoo.com , Text ";
textView1.setTextSize(20);
textView1.setText(testStr);
LinkUtils.autoLink(textView1, new LinkUtils.OnClickListener() {
#Override
public void onLinkClicked(final String link) {
Log.i("Log", "Log"+link);
}
#Override
public void onClicked() {
Log.i("Log", "Log");
}
});
I have an Activity where there are 5 EditTexts. When the user clicks on the first EditText, the soft keyboard opens to enter some value in it. I want to set some other View's visibility to Gone when the soft keyboard opens and also when the user clicks on the first EditText and also when the soft keyboard closes from the same EditText on the back button press. Then I want to set some other View's visibility to visible.
Is there any listener or callback or any hack for when the soft keyboard opens from a click on the first EditText in Android?
Piece of cake with the awesome
KeyboardVisibilityEvent library
KeyboardVisibilityEvent.setEventListener(
getActivity(),
new KeyboardVisibilityEventListener() {
#Override
public void onVisibilityChanged(boolean isOpen) {
// Ah... at last. do your thing :)
}
});
Credits for Yasuhiro SHIMIZU
This only works when android:windowSoftInputMode of your activity is set to adjustResize in the manifest. You can use a layout listener to see if the root layout of your activity is resized by the keyboard.
I use something like the following base class for my activities:
public class BaseActivity extends Activity {
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int heightDiff = rootLayout.getRootView().getHeight() - rootLayout.getHeight();
int contentViewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(BaseActivity.this);
if(heightDiff <= contentViewTop){
onHideKeyboard();
Intent intent = new Intent("KeyboardWillHide");
broadcastManager.sendBroadcast(intent);
} else {
int keyboardHeight = heightDiff - contentViewTop;
onShowKeyboard(keyboardHeight);
Intent intent = new Intent("KeyboardWillShow");
intent.putExtra("KeyboardHeight", keyboardHeight);
broadcastManager.sendBroadcast(intent);
}
}
};
private boolean keyboardListenersAttached = false;
private ViewGroup rootLayout;
protected void onShowKeyboard(int keyboardHeight) {}
protected void onHideKeyboard() {}
protected void attachKeyboardListeners() {
if (keyboardListenersAttached) {
return;
}
rootLayout = (ViewGroup) findViewById(R.id.rootLayout);
rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);
keyboardListenersAttached = true;
}
#Override
protected void onDestroy() {
super.onDestroy();
if (keyboardListenersAttached) {
rootLayout.getViewTreeObserver().removeGlobalOnLayoutListener(keyboardLayoutListener);
}
}
}
The following example activity uses this to hide a view when the keyboard is shown and show it again when the keyboard is hidden.
The xml layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
>
<!-- omitted for brevity -->
</ScrollView>
<LinearLayout android:id="#+id/bottomContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<!-- omitted for brevity -->
</LinearLayout>
</LinearLayout>
And the activity:
public class TestActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_activity);
attachKeyboardListeners();
}
#Override
protected void onShowKeyboard(int keyboardHeight) {
// do things when keyboard is shown
bottomContainer.setVisibility(View.GONE);
}
#Override
protected void onHideKeyboard() {
// do things when keyboard is hidden
bottomContainer.setVisibility(View.VISIBLE);
}
}
As Vikram pointed out in the comments, detecting whether the softkeyboard is shown or has disappeared is only possible with some ugly hacks.
Maybe it is enough to set a focus listener on the edittext:
yourEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
//got focus
} else {
//lost focus
}
}
});
For Activity:
final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
activityRootView.getWindowVisibleDisplayFrame(r);
int heightDiff = view.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 100) {
//enter your code here
}else{
//enter code for hid
}
}
});
For Fragment:
view = inflater.inflate(R.layout.live_chat_fragment, null);
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
view.getWindowVisibleDisplayFrame(r);
int heightDiff = view.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 500) { // if more than 100 pixels, its probably a keyboard...
}
}
});
Jaap's answer won't work for AppCompatActivity. Instead get the height of the Status Bar and Navigation bar etc and compare to your app's window size.
Like so:
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// navigation bar height
int navigationBarHeight = 0;
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navigationBarHeight = getResources().getDimensionPixelSize(resourceId);
}
// status bar height
int statusBarHeight = 0;
resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
// display window size for the app layout
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
// screen height - (user app height + status + nav) ..... if non-zero, then there is a soft keyboard
int keyboardHeight = rootLayout.getRootView().getHeight() - (statusBarHeight + navigationBarHeight + rect.height());
if (keyboardHeight <= 0) {
onHideKeyboard();
} else {
onShowKeyboard(keyboardHeight);
}
}
};
You can try it:
private void initKeyBoardListener() {
// Минимальное значение клавиатуры.
// Threshold for minimal keyboard height.
final int MIN_KEYBOARD_HEIGHT_PX = 150;
// Окно верхнего уровня view.
// Top-level window decor view.
final View decorView = getWindow().getDecorView();
// Регистрируем глобальный слушатель. Register global layout listener.
decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
// Видимый прямоугольник внутри окна.
// Retrieve visible rectangle inside window.
private final Rect windowVisibleDisplayFrame = new Rect();
private int lastVisibleDecorViewHeight;
#Override
public void onGlobalLayout() {
decorView.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame);
final int visibleDecorViewHeight = windowVisibleDisplayFrame.height();
if (lastVisibleDecorViewHeight != 0) {
if (lastVisibleDecorViewHeight > visibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX) {
Log.d("Pasha", "SHOW");
} else if (lastVisibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX < visibleDecorViewHeight) {
Log.d("Pasha", "HIDE");
}
}
// Сохраняем текущую высоту view до следующего вызова.
// Save current decor view height for the next call.
lastVisibleDecorViewHeight = visibleDecorViewHeight;
}
});
}
I am late but I just found a very convenient dependency out there. Using it you can check the visibility of the keyboard as well as make the keyboard "Hide" and Show Whenever you want with a single Line of Code.
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC2'
And then you simply use this code segment to check the keyboard visibility.
KeyboardVisibilityEvent.setEventListener(this, new KeyboardVisibilityEventListener() {
#Override
public void onVisibilityChanged(boolean isOpen) {
if (isOpen)
Toast.makeText(MainActivity.this, "keyboard opened",Toast.LENGTH_SHORT).show();
else
Toast.makeText(MainActivity.this, "keyboard hidden", Toast.LENGTH_SHORT).show();
}
});
Then if you want to Hide/Show keyboard at any point of time then you can just write one of these single lines to achieve it.
UIUtil.showKeyboard(this,edittext_to_be_focused);
UIUtil.hideKeyboard(this);
The below code is working for me,
mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (mainLayout != null) {
int heightDiff = mainLayout.getRootView().getHeight() - mainLayout.getHeight();
if (heightDiff > dpToPx(getActivity(), 200)) {
//keyboard is open
} else {
//keyboard is hide
}
}
}
});
For use in Kotlin inside fragment, which is a common use case it is very easy with KeyboardVisibilityEvent library.
In build.gradle:
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC2'
In Fragment:
activity?.let {
KeyboardVisibilityEvent.setEventListener(it,object: KeyboardVisibilityEventListener {
override fun onVisibilityChanged(isOpen: Boolean) {
if (isOpen) Toast.makeText(context,"Keyboard is opened",Toast.LENGTH_SHORT).show()
else Toast.makeText(context,"Keyboard is closed",Toast.LENGTH_SHORT).show()
}
})
}
Source and credits
You can use my Rx extension function (Kotlin).
/**
* #return [Observable] to subscribe of keyboard visibility changes.
*/
fun AppCompatActivity.keyboardVisibilityChanges(): Observable<Boolean> {
// flag indicates whether keyboard is open
var isKeyboardOpen = false
val notifier: BehaviorSubject<Boolean> = BehaviorSubject.create()
// approximate keyboard height
val approximateKeyboardHeight = dip(100)
// device screen height
val screenHeight: Int = getScreenHeight()
val visibleDisplayFrame = Rect()
val viewTreeObserver = window.decorView.viewTreeObserver
val onDrawListener = ViewTreeObserver.OnDrawListener {
window.decorView.getWindowVisibleDisplayFrame(visibleDisplayFrame)
val keyboardHeight = screenHeight - (visibleDisplayFrame.bottom - visibleDisplayFrame.top)
val keyboardOpen = keyboardHeight >= approximateKeyboardHeight
val hasChanged = isKeyboardOpen xor keyboardOpen
if (hasChanged) {
isKeyboardOpen = keyboardOpen
notifier.onNext(keyboardOpen)
}
}
val lifeCycleObserver = object : GenericLifecycleObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event?) {
if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
viewTreeObserver.removeOnDrawListener(onDrawListener)
source.lifecycle.removeObserver(this)
notifier.onComplete()
}
}
}
viewTreeObserver.addOnDrawListener(onDrawListener)
lifecycle.addObserver(lifeCycleObserver)
return notifier
.doOnDispose {
viewTreeObserver.removeOnDrawListener(onDrawListener)
lifecycle.removeObserver(lifeCycleObserver)
}
.onTerminateDetach()
.hide()
}
Example:
(context as AppCompatActivity)
.keyboardVisibilityChanges()
.subscribeBy { isKeyboardOpen ->
// your logic
}
in kotlin you can use this code in your activity
window.decorView.viewTreeObserver.addOnGlobalLayoutListener{
val r = Rect()
window.decorView.getWindowVisibleDisplayFrame(r)
val height =window.decorView.height
if(height - r.bottom>height*0.1399){
//keyboard is open
}else{
//keyboard is close
}
If you can, try to extend EditText and override 'onKeyPreIme' method.
#Override
public void setOnEditorActionListener(final OnEditorActionListener listener) {
mEditorListener = listener; //keep it for later usage
super.setOnEditorActionListener(listener);
}
#Override
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (mEditorListener != null) {
//you can define and use custom listener,
//OR define custom R.id.<imeId>
//OR check event.keyCode in listener impl
//* I used editor action because of ButterKnife #
mEditorListener.onEditorAction(this, android.R.id.closeButton, event);
}
}
return super.onKeyPreIme(keyCode, event);
}
How can you extend it:
Implement onFocus listening and declare 'onKeyboardShown'
declare 'onKeyboardHidden'
I think, that recalculating of screen height is not 100% successfully as mentioned before.
To be clear, overriding of 'onKeyPreIme' is not called on 'hide soft keyboard programatically' methods, BUT if you are doing it anywhere, you should do 'onKeyboardHidden' logic there and do not create a comprehensive solutions.
This will work without any need to change your activity's android:windowSoftInputMode
step 1: extend EditText class and override these two:
#Override
public void setOnEditorActionListener(final OnEditorActionListener listener) {
mEditorListener = listener;
super.setOnEditorActionListener(listener);
}
#Override
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (mEditorListener != null) {
mEditorListener.onEditorAction(this, android.R.id.closeButton, event);
}
}
return super.onKeyPreIme(keyCode, event);
}
step 2: create these two in your activity:
private void initKeyboard() {
final AppEditText editText = findViewById(R.id.some_id);
editText.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
setKeyboard(hasFocus);
}
});
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event == null || event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
editText.clearFocus();
}
return false;
}
});
}
public void setKeyboard(boolean isShowing) {
// do something
}
*** remember in order to make clearFocus work, you have to make parent or first child in the parent hierarchy focusable.
setFocusableInTouchMode(true);
setFocusable(true);
Check my Kotlin extension View.keyboardVisibilityChanges():
fun View.keyboardVisibilityChanges(): Flow<Boolean>{
return onPreDrawFlow()
.map { isKeyboardVisible() }
.distinctUntilChanged()
}
fun View.onPreDrawFlow(): Flow<Unit> {
return callbackFlow {
val onPreDrawListener = ViewTreeObserver.OnPreDrawListener {
trySendBlocking(Unit)
true
}
viewTreeObserver.addOnPreDrawListener(onPreDrawListener)
awaitClose {
viewTreeObserver.removeOnPreDrawListener(onPreDrawListener)
}
}
}
fun View.isKeyboardVisible(): Boolean = ViewCompat.getRootWindowInsets(this)
?.isVisible(Type.ime())
?: false
Insets are the only one official and proper answer. The simple inset listener works like a charm. Here is the code:
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
insets
}
Attention! This solution works only in case if you set edge-to-edge mode:
WindowCompat.setDecorFitsSystemWindows(window, false)
Please check official documentation about checking keyboard software visibility and proper implementation of edge-to-edge mode:
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mainactivity);
attachKeyboardListeners();
....
yourEditText1.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
yourEditText2.setVisibility(View.GONE);
yourEditText3.setVisibility(View.GONE);
yourEditText4.setVisibility(View.GONE);
yourEditText5.setVisibility(View.GONE);
} else {
yourEditText2.setVisibility(View.VISIBLE);
yourEditText3.setVisibility(View.VISIBLE);
yourEditText4.setVisibility(View.VISIBLE);
yourEditText5.setVisibility(VISIBLE);
}
}
});
}
}
Use this class,
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
public class SoftKeyboard implements View.OnFocusChangeListener
{
private static final int CLEAR_FOCUS = 0;
private ViewGroup layout;
private int layoutBottom;
private InputMethodManager im;
private int[] coords;
private boolean isKeyboardShow;
private SoftKeyboardChangesThread softKeyboardThread;
private List<EditText> editTextList;
private View tempView; // reference to a focused EditText
public SoftKeyboard(ViewGroup layout, InputMethodManager im)
{
this.layout = layout;
keyboardHideByDefault();
initEditTexts(layout);
this.im = im;
this.coords = new int[2];
this.isKeyboardShow = false;
this.softKeyboardThread = new SoftKeyboardChangesThread();
this.softKeyboardThread.start();
}
public void openSoftKeyboard()
{
if(!isKeyboardShow)
{
layoutBottom = getLayoutCoordinates();
im.toggleSoftInput(0, InputMethodManager.SHOW_IMPLICIT);
softKeyboardThread.keyboardOpened();
isKeyboardShow = true;
}
}
public void closeSoftKeyboard()
{
if(isKeyboardShow)
{
im.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
isKeyboardShow = false;
}
}
public void setSoftKeyboardCallback(SoftKeyboardChanged mCallback)
{
softKeyboardThread.setCallback(mCallback);
}
public void unRegisterSoftKeyboardCallback()
{
softKeyboardThread.stopThread();
}
public interface SoftKeyboardChanged
{
public void onSoftKeyboardHide();
public void onSoftKeyboardShow();
}
private int getLayoutCoordinates()
{
layout.getLocationOnScreen(coords);
return coords[1] + layout.getHeight();
}
private void keyboardHideByDefault()
{
layout.setFocusable(true);
layout.setFocusableInTouchMode(true);
}
/*
* InitEditTexts now handles EditTexts in nested views
* Thanks to Francesco Verheye (verheye.francesco#gmail.com)
*/
private void initEditTexts(ViewGroup viewgroup)
{
if(editTextList == null)
editTextList = new ArrayList<EditText>();
int childCount = viewgroup.getChildCount();
for(int i=0; i<= childCount-1;i++)
{
View v = viewgroup.getChildAt(i);
if(v instanceof ViewGroup)
{
initEditTexts((ViewGroup) v);
}
if(v instanceof EditText)
{
EditText editText = (EditText) v;
editText.setOnFocusChangeListener(this);
editText.setCursorVisible(true);
editTextList.add(editText);
}
}
}
/*
* OnFocusChange does update tempView correctly now when keyboard is still shown
* Thanks to Israel Dominguez (dominguez.israel#gmail.com)
*/
#Override
public void onFocusChange(View v, boolean hasFocus)
{
if(hasFocus)
{
tempView = v;
if(!isKeyboardShow)
{
layoutBottom = getLayoutCoordinates();
softKeyboardThread.keyboardOpened();
isKeyboardShow = true;
}
}
}
// This handler will clear focus of selected EditText
private final Handler mHandler = new Handler()
{
#Override
public void handleMessage(Message m)
{
switch(m.what)
{
case CLEAR_FOCUS:
if(tempView != null)
{
tempView.clearFocus();
tempView = null;
}
break;
}
}
};
private class SoftKeyboardChangesThread extends Thread
{
private AtomicBoolean started;
private SoftKeyboardChanged mCallback;
public SoftKeyboardChangesThread()
{
started = new AtomicBoolean(true);
}
public void setCallback(SoftKeyboardChanged mCallback)
{
this.mCallback = mCallback;
}
#Override
public void run()
{
while(started.get())
{
// Wait until keyboard is requested to open
synchronized(this)
{
try
{
wait();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
int currentBottomLocation = getLayoutCoordinates();
// There is some lag between open soft-keyboard function and when it really appears.
while(currentBottomLocation == layoutBottom && started.get())
{
currentBottomLocation = getLayoutCoordinates();
}
if(started.get())
mCallback.onSoftKeyboardShow();
// When keyboard is opened from EditText, initial bottom location is greater than layoutBottom
// and at some moment equals layoutBottom.
// That broke the previous logic, so I added this new loop to handle this.
while(currentBottomLocation >= layoutBottom && started.get())
{
currentBottomLocation = getLayoutCoordinates();
}
// Now Keyboard is shown, keep checking layout dimensions until keyboard is gone
while(currentBottomLocation != layoutBottom && started.get())
{
synchronized(this)
{
try
{
wait(500);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
currentBottomLocation = getLayoutCoordinates();
}
if(started.get())
mCallback.onSoftKeyboardHide();
// if keyboard has been opened clicking and EditText.
if(isKeyboardShow && started.get())
isKeyboardShow = false;
// if an EditText is focused, remove its focus (on UI thread)
if(started.get())
mHandler.obtainMessage(CLEAR_FOCUS).sendToTarget();
}
}
public void keyboardOpened()
{
synchronized(this)
{
notify();
}
}
public void stopThread()
{
synchronized(this)
{
started.set(false);
notify();
}
}
}
}
In Android Manifest, android:windowSoftInputMode="adjustResize" is necessary.
/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use the layout root
InputMethodManager im = (InputMethodManager)getSystemService(Service.INPUT_METHOD_SERVICE);
/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged() {
#Override
public void onSoftKeyboardHide() {
// Code here
}
#Override
public void onSoftKeyboardShow() {
// Code here
}
});
/*
Open or close the soft keyboard easily
*/
softKeyboard.openSoftKeyboard();
softKeyboard.closeSoftKeyboard();
/* Prevent memory leaks:*/
#Override
public void onDestroy() {
super.onDestroy();
softKeyboard.unRegisterSoftKeyboardCallback();
}
P.S - Completely taken from here.
For the case of adjustResize and FragmentActivity accepted solution from #Jaap doesn't work for me.
Here is my solution:
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
private int contentDiff;
private int rootHeight;
#Override
public void onGlobalLayout() {
View contentView = getWindow().findViewById(Window.ID_ANDROID_CONTENT);
if (rootHeight != mDrawerLayout.getRootView().getHeight()) {
rootHeight = mDrawerLayout.getRootView().getHeight();
contentDiff = rootHeight - contentView.getHeight();
return;
}
int newContentDiff = rootHeight - contentView.getHeight();
if (contentDiff != newContentDiff) {
if (contentDiff < newContentDiff) {
onShowKeyboard(newContentDiff - contentDiff);
} else {
onHideKeyboard();
}
contentDiff = newContentDiff;
}
}
};
A different approach would be to check when the user stopped typing...
When a TextEdit is in focus (user is/was typing) you could hide the views (focus listener)
and use a Handler + Runnable and a text change listener to close the keyboard (regardless of its visibility) and show the views after some delay.
The main thing to look out for would be the delay you use, which would depend on the content of these TextEdits.
Handler timeoutHandler = new Handler();
Runnable typingRunnable = new Runnable() {
public void run() {
// current TextEdit
View view = getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// reset focus
view.clearFocus();
// close keyboard (whether its open or not)
imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
// SET VIEWS VISIBLE
}
};
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
// SET VIEWS GONE
// reset handler
timeoutHandler.removeCallbacks(typingRunnable);
timeoutHandler.postDelayed(typingRunnable, TYPING_TIMEOUT);
}
}
});
editText.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) {
// Reset Handler...
timeoutHandler.removeCallbacks(typingRunnable);
}
#Override
public void afterTextChanged(Editable s) {
// Reset Handler Cont.
if (editText.getText().toString().trim().length() > 0) {
timeoutHandler.postDelayed(typingRunnable, TYPING_TIMEOUT);
}
}
});
This code works great nice
use this class for root view:
public class KeyboardConstraintLayout extends ConstraintLayout {
private KeyboardListener keyboardListener;
private EditText targetEditText;
private int minKeyboardHeight;
private boolean isShow;
public KeyboardConstraintLayout(Context context) {
super(context);
minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height); //128dp
}
public KeyboardConstraintLayout(Context context, AttributeSet attrs) {
super(context, attrs);
minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height); // 128dp
}
public KeyboardConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height); // 128dp
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!isInEditMode()) {
Activity activity = (Activity) getContext();
#SuppressLint("DrawAllocation")
Rect rect = new Rect();
getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rect.top;
int keyboardHeight = activity.getWindowManager().getDefaultDisplay().getHeight() - (rect.bottom - rect.top) - statusBarHeight;
if (keyboardListener != null && targetEditText != null && targetEditText.isFocused()) {
if (keyboardHeight > minKeyboardHeight) {
if (!isShow) {
isShow = true;
keyboardListener.onKeyboardVisibility(true);
}
}else {
if (isShow) {
isShow = false;
keyboardListener.onKeyboardVisibility(false);
}
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public boolean isShowKeyboard() {
return isShow;
}
public void setKeyboardListener(EditText targetEditText, KeyboardListener keyboardListener) {
this.targetEditText = targetEditText;
this.keyboardListener = keyboardListener;
}
public interface KeyboardListener {
void onKeyboardVisibility (boolean isVisible);
}
}
and set keyboard listener in activity or fragment:
rootLayout.setKeyboardListener(targetEditText, new KeyboardConstraintLayout.KeyboardListener() {
#Override
public void onKeyboardVisibility(boolean isVisible) {
}
});
This is not working as desired...
... have seen many use size calculations to check ...
I wanted to determine if it was open or not and I found isAcceptingText()
so this really does not answer the question as it does not address opening or closing rather more like is open or closed so it is related code that may help others in various scenarios...
in an activity
if (((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).isAcceptingText()) {
Log.d(TAG,"Software Keyboard was shown");
} else {
Log.d(TAG,"Software Keyboard was not shown");
}
in a fragment
if (((InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).isAcceptingText()) {
Log.d(TAG,"Software Keyboard was shown");
} else {
Log.d(TAG,"Software Keyboard was not shown");
}
You can handle keyboard visibility by overriding two methods in your Activity: onKeyUp() and onKeyDown() more information in this link: https://developer.android.com/training/keyboard-input/commands
Found an accurate way of telling whether or not a keyboard when using the 'adjustResize' Soft input mode (Kotlin code)
Define a couple of activity scope variables
private var activityHeight = 0
private var keyboardOpen = false
Write the following code in onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
/* Grab initial screen value */
this#ActivityMain.window.decorView.doOnNextLayout {
val displayFrame : Rect = Rect()
this#ActivityMain.window.decorView.getWindowVisibleDisplayFrame(displayFrame)
activityHeight = displayFrame.height()
}
/* Check for keyboard open/close */
this#ActivityMain.window.decorView.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
val drawFrame : Rect = Rect()
this#ActivityMain.window.decorView.getWindowVisibleDisplayFrame(drawFrame)
val currentSize = drawFrame.height()
keyboardOpen = currentSize < activityHeight
Log.v("keyboard1","$keyboardOpen $currentSize - $activityHeight")
}
}
You now have a boolean which accurately tracks whether or not the keyboard is open, do what you will
Unfortunately I do not have a sufficiently high reputation to comment on Jaap van Hengstum's answer. But I read a few comments of people, having the problem that contentViewTop is always 0 and that onShowKeyboard(...) is always called.
I had the same issue and figured out the problem I had. I used an AppCompatActivity instead of a 'normal' Activity. In this case Window.ID_ANDROID_CONTENT refers to an ContentFrameLayout and not to the FrameLayout with the right top-value. In my case it was fine to use the 'normal' Activity, if you have to use another activity-type (I just tested the AppCompatActivity, maybe it's also an issue with other acitivy-types like the FragmentActivity), you have to access the FrameLayout, which is an ancestor of the ContentFrameLayout.
when keyboard show
rootLayout.getHeight() < rootLayout.getRootView().getHeight() - getStatusBarHeight()
is true,else hide
private boolean isKeyboardShown = false;
private int prevContentHeight = 0;
private ViewGroup contentLayout;
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener =
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int contentHeight = contentLayout.getHeight();
int rootViewHeight = contentLayout.getRootView().getHeight();
if (contentHeight > 0) {
if (!isKeyboardShown) {
if (contentHeight < prevContentHeight) {
isKeyboardShown = true;
onShowKeyboard(rootViewHeight - contentHeight);
}
} else {
if (contentHeight > prevContentHeight) {
isKeyboardShown = false;
onHideKeyboard();
}
}
prevContentHeight = contentHeight;
}
}
};
I've modified the Jaap's accepted answer a bit. But in my case, there are few assumptions such as android:windowSoftInputMode=adjustResize and the keyboard does not show up at the beginning when the app starts. And also, I assume that the screen in regard matches the parent's height.
contentHeight > 0 this check provides me to know if the regarding screen is hidden or shown to apply keyboard event listening for this specific screen. Also I pass the layout view of the regarding screen in attachKeyboardListeners(<your layout view here>) in my main activity's onCreate() method. Every time when the height of the regarding screen changes, I save it to prevContentHeight variable to check later whether the keyboard is shown or hidden.
For me, so far it's been worked pretty well. I hope that it works for others too.
check with the below code :
XML CODE :
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/coordinatorParent"
style="#style/parentLayoutPaddingStyle"
android:layout_width="match_parent"
android:layout_height="match_parent">
.................
</android.support.constraint.ConstraintLayout>
JAVA CODE :
//Global Variable
android.support.constraint.ConstraintLayout activityRootView;
boolean isKeyboardShowing = false;
private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;
android.support.constraint.ConstraintLayout.LayoutParams layoutParams;
//onCreate or onViewAttached
activityRootView = view.findViewById(R.id.coordinatorParent);
onGlobalLayoutListener = onGlobalLayoutListener();
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
//outside oncreate
ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener() {
return new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
activityRootView.getWindowVisibleDisplayFrame(r);
int screenHeight = activityRootView.getRootView().getHeight();
int keypadHeight = screenHeight - r.bottom;
if (keypadHeight > screenHeight * 0.15) { // 0.15 ratio is perhaps enough to determine keypad height.
if (!isKeyboardShowing) { // keyboard is opened
isKeyboardShowing = true;
onKeyboardVisibilityChanged(true);
}
}
else {
if (isKeyboardShowing) { // keyboard is closed
isKeyboardShowing = false;
onKeyboardVisibilityChanged(false);
}
}
}//ends here
};
}
void onKeyboardVisibilityChanged(boolean value) {
layoutParams = (android.support.constraint.ConstraintLayout.LayoutParams)topImg.getLayoutParams();
if(value){
int length = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, getResources().getDisplayMetrics());
layoutParams.height= length;
layoutParams.width = length;
topImg.setLayoutParams(layoutParams);
Log.i("keyboard " ,""+ value);
}else{
int length1 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 175, getResources().getDisplayMetrics());
layoutParams.height= length1;
layoutParams.width = length1;
topImg.setLayoutParams(layoutParams);
Log.i("keyboard " ,""+ value);
}
}
#Override
public void onDetach() {
super.onDetach();
if(onGlobalLayoutListener != null) {
activityRootView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
}
}
at the first create a kotlin file and add these methods:
fun Activity.getRootView(): View {
return findViewById<View>(android.R.id.content)
}
fun Context.convertDpToPx(dp: Float): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
this.resources.displayMetrics
)
}
fun Activity.isKeyboardOpen(): Boolean {
val visibleBounds = Rect()
this.getRootView().getWindowVisibleDisplayFrame(visibleBounds)
val heightDiff = getRootView().height - visibleBounds.height()
val marginOfError = Math.round(this.convertDpToPx(50F))
return heightDiff > marginOfError
}
fun Activity.isKeyboardClosed(): Boolean {
return !this.isKeyboardOpen()
}
then create a listener class for checking the keyboard is open or not :
class KeyboardEventListener(
private val activity: AppCompatActivity,
private val callback: (isOpen: Boolean) -> Unit
) : LifecycleObserver {
private val listener = object : ViewTreeObserver.OnGlobalLayoutListener {
private var lastState: Boolean = activity.isKeyboardOpen()
override fun onGlobalLayout() {
val isOpen = activity.isKeyboardOpen()
if (isOpen == lastState) {
return
} else {
dispatchKeyboardEvent(isOpen)
lastState = isOpen
}
}
}
init {
// Dispatch the current state of the keyboard
dispatchKeyboardEvent(activity.isKeyboardOpen())
// Make the component lifecycle aware
activity.lifecycle.addObserver(this)
registerKeyboardListener()
}
private fun registerKeyboardListener() {
activity.getRootView().viewTreeObserver.addOnGlobalLayoutListener(listener)
}
private fun dispatchKeyboardEvent(isOpen: Boolean) {
when {
isOpen -> callback(true)
!isOpen -> callback(false)
}
}
#OnLifecycleEvent(value = Lifecycle.Event.ON_PAUSE)
#CallSuper
fun onLifecyclePause() {
unregisterKeyboardListener()
}
private fun unregisterKeyboardListener() {
activity.getRootView().viewTreeObserver.removeOnGlobalLayoutListener(listener)
}
}
and use it like this :
override fun onResume() {
super.onResume()
KeyboardEventListener(this) { isOpen -> // handle event }
}
I hope you find it useful.
Solution with extra property in Activity\Fragment, but without any hypothetical hardcoded heights (like 100 etc) .
Just add OnGlobalLayoutListener to your root view and save its initial height before keyboard will be shown:
var firstLoad = true
var contentFullWeight = 0
override fun onViewCreated(layoutView: View, savedInstanceState: Bundle?) {
super.onViewCreated(layoutView, savedInstanceState)
view?.viewTreeObserver?.addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener {
if(firstLoad){
contentFullWeight = view?.height!!
firstLoad = false
}
if (view?.height!! < contentFullWeight) {
Log.d("TEZT_KEYBOARD", ">> KBD OPENED")
} else {
Log.d("TEZT_KEYBOARD", ">> KBD closed")
}
})
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/addresses_confirm_root_view"
android:orientation="vertical">
<---In the xml root use the id--->
final LinearLayout activityRootView = view.findViewById(R.id.addresses_confirm_root_view);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);
int heightDiff = activityRootView.getRootView().getHeight() - r.height();
if (heightDiff > 0.25 * activityRootView.getRootView().getHeight()) {
// if more than 25% of the screen, its probably a keyboard...
onkeyboard();
} else {
//Keyboard not visible
offkeyboard();
}
}
});