How to remove focus without setting focus to another control? - android

I like my UIs to be intuitive; each screen should naturally and unobtrusively guide the user on to the next step in the app. Barring that, I strive to make things as confusing and confounding as possible.
Just kidding :-)
I've got three TableRows, each containing a read-only and non-focusable EditText control and then a button to its right. Each button starts the same activity but with a different argument. The user makes a selection there and the sub-activity finishes, populating the appropriate EditText with the user's selection.
It's the classic cascading values mechanism; each selection narrows the available options for the next selection, etc. Thus I'm disabling both controls on each of the next rows until the EditText on the current row contains a value.
I need to do one of two things, in this order of preference:
When a button is clicked, immediately remove focus without setting focus to a different button
Set focus to the first button when the activity starts
The problem manifests after the sub-activity returns; the button that was clicked retains focus.
Re: #1 above - There doesn't appear to be a removeFocus() method, or something similar
Re: #2 above - I can use requestFocus() to set focus to the button on the next row, and that works after the sub-activity returns, but for some reason it doesn't work in the parent activity's onCreate().
I need UI consistency in either direction--either no buttons have focus after the sub-activity finishes or each button receives focus depending on its place in the logic flow, including the very first (and only) active button prior to any selection.

Using clearFocus() didn't seem to be working for me either as you found (saw in comments to another answer), but what worked for me in the end was adding:
<LinearLayout
android:id="#+id/my_layout"
android:focusable="true"
android:focusableInTouchMode="true" ...>
to my very top level Layout View (a linear layout). To remove focus from all Buttons/EditTexts etc, you can then just do
LinearLayout myLayout = (LinearLayout) activity.findViewById(R.id.my_layout);
myLayout.requestFocus();
Requesting focus did nothing unless I set the view to be focusable.

Old question, but I came across it when I had a similar issue and thought I'd share what I ended up doing.
The view that gained focus was different each time so I used the very generic:
View current = getCurrentFocus();
if (current != null) current.clearFocus();

You can use View.clearFocus().
Use View.requestFocus() called from onResume().

android:descendantFocusability="beforeDescendants"
using the following in the activity with some layout options below seemed to work as desired.
getWindow().getDecorView().findViewById(android.R.id.content).clearFocus();
in connection with the following parameters on the root view.
<?xml
android:focusable="true"
android:focusableInTouchMode="true"
android:descendantFocusability="beforeDescendants" />
https://developer.android.com/reference/android/view/ViewGroup#attr_android:descendantFocusability
Answer thanks to:
https://forums.xamarin.com/discussion/1856/how-to-disable-auto-focus-on-edit-text
About windowSoftInputMode
There's yet another point of contention to be aware of. By default,
Android will automatically assign initial focus to the first EditText
or focusable control in your Activity. It naturally follows that the
InputMethod (typically the soft keyboard) will respond to the focus
event by showing itself. The windowSoftInputMode attribute in
AndroidManifest.xml, when set to stateAlwaysHidden, instructs the
keyboard to ignore this automatically-assigned initial focus.
<activity
android:name=".MyActivity"
android:windowSoftInputMode="stateAlwaysHidden"/>
great reference

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/ll_root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
LinearLayout llRootView = findViewBindId(R.id.ll_root_view);
llRootView.clearFocus();
I use this when already finished update profile info and remove all focus from EditText in my layout
====> Update: In parent layout content my EditText add line:
android:focusableInTouchMode="true"

What about just adding android:windowSoftInputMode="stateHidden" on your activity in the manifest.
Taken from a smart man commenting on this: https://stackoverflow.com/a/2059394/956975

I tried to disable and enable focusability for view and it worked for me (focus was reset):
focusedView.setFocusable(false);
focusedView.setFocusableInTouchMode(false);
focusedView.setFocusable(true);
focusedView.setFocusableInTouchMode(true);

First of all, it will 100% work........
Create onResume() method.
Inside this onResume() find the view which is focusing again and again by findViewById().
Inside this onResume() set requestFocus() to this view.
Inside this onResume() set clearFocus to this view.
Go in xml of same layout and find that top view which you want to be focused and set focusable true and focusableInTuch true.
Inside this onResume() find the above top view by findViewById
Inside this onResume() set requestFocus() to this view at the last.
And now enjoy......

android:focusableInTouchMode="true"
android:focusable="true"
android:clickable="true"
Add them to your ViewGroup that includes your EditTextView.
It works properly to my Constraint Layout. Hope this help

You could try turning off the main Activity's ability to save its state (thus making it forget what control had text and what had focus). You will need to have some other way of remembering what your EditText's have and repopulating them onResume(). Launch your sub-Activities with startActivityForResult() and create an onActivityResult() handler in your main Activity that will update the EditText's correctly. This way you can set the proper button you want focused onResume() at the same time you repopulate the EditText's by using a myButton.post(new Runnable(){ run() { myButton.requestFocus(); } });
The View.post() method is useful for setting focus initially because that runnable will be executed after the window is created and things settle down, allowing the focus mechanism to function properly by that time. Trying to set focus during onCreate/Start/Resume() usually has issues, I've found.
Please note this is pseudo-code and non-tested, but it's a possible direction you could try.

You do not need to clear focus, just add this code where you want to focus
time_statusTV.setFocusable(true);
time_statusTV.requestFocus();
InputMethodManager imm = (InputMethodManager)this.getSystemService(Service.INPUT_METHOD_SERVICE);
imm.showSoftInput( time_statusTV, 0);

Try the following (calling clearAllEditTextFocuses();)
private final boolean clearAllEditTextFocuses() {
View v = getCurrentFocus();
if(v instanceof EditText) {
final FocusedEditTextItems list = new FocusedEditTextItems();
list.addAndClearFocus((EditText) v);
//Focus von allen EditTexten entfernen
boolean repeat = true;
do {
v = getCurrentFocus();
if(v instanceof EditText) {
if(list.containsView(v))
repeat = false;
else list.addAndClearFocus((EditText) v);
} else repeat = false;
} while(repeat);
final boolean result = !(v instanceof EditText);
//Focus wieder setzen
list.reset();
return result;
} else return false;
}
private final static class FocusedEditTextItem {
private final boolean focusable;
private final boolean focusableInTouchMode;
#NonNull
private final EditText editText;
private FocusedEditTextItem(final #NonNull EditText v) {
editText = v;
focusable = v.isFocusable();
focusableInTouchMode = v.isFocusableInTouchMode();
}
private final void clearFocus() {
if(focusable)
editText.setFocusable(false);
if(focusableInTouchMode)
editText.setFocusableInTouchMode(false);
editText.clearFocus();
}
private final void reset() {
if(focusable)
editText.setFocusable(true);
if(focusableInTouchMode)
editText.setFocusableInTouchMode(true);
}
}
private final static class FocusedEditTextItems extends ArrayList<FocusedEditTextItem> {
private final void addAndClearFocus(final #NonNull EditText v) {
final FocusedEditTextItem item = new FocusedEditTextItem(v);
add(item);
item.clearFocus();
}
private final boolean containsView(final #NonNull View v) {
boolean result = false;
for(FocusedEditTextItem item: this) {
if(item.editText == v) {
result = true;
break;
}
}
return result;
}
private final void reset() {
for(FocusedEditTextItem item: this)
item.reset();
}
}

Related

Emoji keyboard crashing

I'm a fairly new developer, so please go easy on me.
I'm making a chat app, and I'm planning on adding support for custom emojis, similar to how Discord manages custom emojis. However, my app is crashing once I tap on the emoji button. I want it to inflate my layout (emoji_keyboard_layout.xml) and view that in a LinearLayout in ChatActivity.
I've tried adding the view itself to the layout (yeah that didn't work in my case, but if that is the only solution possible, then I will try it again) and I've also tried modifying my onClick, modifying the actual method, but whatever I did, nothing would fix it.
My openEmojiKeyboard method:
private void openEmojiKeyboard(Boolean EMOJI_STATE, Boolean GIF_STATE)
{
View emojiKey = getLayoutInflater().inflate(R.layout.emoji_keyboard_layout, llEmojiKeyboard);
llEmojiKeyboard.addView(emojiKey);
llEmojiKeyboard.setVisibility(View.VISIBLE);
hideKeyboard(etMessage);
final LinearLayout llSelectContent = emojiKey.findViewById(R.id.llSelectContent);
final LinearLayout llSelectToolbar = emojiKey.findViewById(R.id.llSelectToolbar);
final LinearLayout llEmoji = emojiKey.findViewById(R.id.llEmoji);
final LinearLayout llGif = emojiKey.findViewById(R.id.llGif);
final LinearLayout llEmojiSelected = emojiKey.findViewById(R.id.llEmojiSelected);
final LinearLayout llGifSelected = emojiKey.findViewById(R.id.llGifSelected);
final TextView tvEmptyContent = emojiKey.findViewById(R.id.tvEmptyContent);
final TextView tvEmptyContent1 = emojiKey.findViewById(R.id.tvEmptyContent1);
if (EMOJI_STATE && !GIF_STATE) // The emoji keyboard is open, gif keyboard is closed
{
llEmojiSelected.setVisibility(View.VISIBLE);
llGifSelected.setVisibility(View.GONE);
tvEmptyContent1.setText(R.string.add_emoji);
tvEmptyContent.setText(R.string.empty_emoji_content);
}
else if (GIF_STATE && !EMOJI_STATE) // The gif keyboard is open, emoji keyboard is closed
{
llGifSelected.setVisibility(View.VISIBLE);
llEmojiSelected.setVisibility(View.GONE);
tvEmptyContent1.setText(R.string.retry);
tvEmptyContent.setText(R.string.empty_gif_content);
}
}
My activity's onClick event:
case R.id.ivEmoji:
openEmojiKeyboard(EMOJI_STATE, GIF_STATE);
break;
The emoji keyboard functionality is not ready yet, just preparing the layout
Stack Trace
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
Crashing on lines llEmojiKeyboard.addView(emojiKey); and openEmojiKeyboard(EMOJI_STATE, GIF_STATE);
The error you have indicated in the logs is telling you that the view (emojiKey) already has a parent (root) view. This is because you passed llEmojiKeyboard as a root view via the second argument in LayoutInflater.inflate(). Since this is already the root view, you don't need to call .addView() after inflating, or if the .addView() call is necessary, you can pass a boolean as a third argument to .inflate() to control whether the newly inflated view gets attached to the root ViewGroup (here is the documentation for that version of the .inflate() method):
View emojiKey = getLayoutInflater().inflate(R.layout.emoji_keyboard_layout, llEmojiKeyboard, false);

disable all elements in a layout - Android

In my Android app there is a requirement that a number of UI elements should be disabled until a button click carryout. Can I disable all the UI elements in a layout by referring the layout without disable them one by one. Is it possible.Can some one help me.
You could disable all views recursively like this.
Just pass the layout as view to the method:
private void enableViews(View v, boolean enabled) {
if (v instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) v;
for (int i = 0;i<vg.getChildCount();i++) {
enableViews(vg.getChildAt(i), enabled);
}
}
v.setEnabled(enabled);
}
Just run enableViews(view, false) to disable, or enableViews(view, true) to enable again.
use following attribute in your xml layout( as a example textView)
android:visibility="gone"
in button click event
myText.setVisible(myText.VISIBLE)
you can either use them one by one or you can put all invisible content in a single layout and hide the layout. then once you want to show them, just VISIBLE the layout. then all will display
need any more comment.. just comment.

How to disable all buttons in a Layout?

The way app works is the following: App prompts 30 buttons to user and user may guess the right ones by tapping. When user taps some button all the buttons (say a view containing these buttons) should be locked while corresponding (right or wrong guess) animation is playing. Tapped button by itself should be disabled till the next round. After animation is finished all not tapped previously buttons (say a view containing these buttons) should be available again.
So I have a Layout which includes another layout with these 30 buttons:
...
<RelativeLayout
android:id="#+id/alphabetContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include layout="#layout/alphabet" />
</RelativeLayout>
...
Now I need to lock the buttons from being clicked and then unlock. So I tried:
...
private RelativeLayout alphabetPanel;
...
public void onCreate(){
...
alphabetPanel = (RelativeLayout) findViewById(R.id.alphabetContainer);
...
}
...
private void lockButtons(){
alphabetPanel.setEnabled(false);
}
but this doesn't lock buttons. I also tried:
alphabetPanel.setFocusable(false);
alphabetPanel.setClickable(false);
Doesn't help either. Seems like it all relies only to a layout by itself but not the views it contains.
Also I tried to add a fake layout to place it over layout with buttons by bringing it to the front. This is a workaround and its tricky cuz both layouts must be placed inside a RelativeLayout only:
...
blockingLayout = new RelativeLayout(this);
blockingLayout.setLayoutParams(alphabetPanel.getLayoutParams());
...
but this works very strange: somehow both layouts in this case appears and disappears every second or so or doesn't appear at all - I cant understand that at all cuz there is no setVisibility() method used in code!
The only one way left is to iterate every view (button) to make it disabled and than back.
Is there any other way?
UPDATE
Finally I had to add a "wall"-layout into the xml. Now by making it clickable and focusable it becomes a solution.
Try setting for each Button's xml definition
android:duplicateParentState="true"
I'm not sure, but I think it should make them not only to seem disabled, but also to act accordingly.
Hmm it surprises me that disabling the parent-layout doesn't work.. as far as i know it should.
Try fetching your included layout instead, and disable that.
Anyway, if all else fails you can always loop through the buttons themselves.
for(int i=0;i<relativeLayout.getChildCount();i++){
View child=relativeLayout.getChildAt(i);
//your processing....
child.setEnabled(false);
}
I used extension to lock and unlock the view
//lock
fun View.lock() {
isEnabled = false
isClickable = false}
//unlock
fun View.unlock() {
isEnabled = true
isClickable = true}
if you want to lock all children of the view group
//lock children of the view group
fun ViewGroup.lockAllChildren() {
views().forEach { it.lock() }}
//unlock children of the view group
fun ViewGroup.unlockAllChildren() {
views().forEach { it.unlock() }}
firstly define your button
Button bit = (Button)findViewById(R.id.but);
bit.setEnabled(false);
and set enabled false;
Java:-
public void disableButtons(Layout layout) {
// Get all touchable views
ArrayList<View> layoutButtons = layout.getTouchables();
// loop through them, if they are instances of Button, disable them.
for(View v : layoutButtons){
if( v instanceof Button ) {
((Button)v).setEnabled(false);
}
}
}
Kotlin:-
fun disableButtons(layout: Layout) {
// Get all touchable views
val layoutButtons: ArrayList<View> = layout.getTouchables()
// loop through them, if they are instances of Button, disable them.
for (v in layoutButtons) {
if (v is Button) {
(v as Button).setEnabled(false)
}
}
}
Retrieve all touchables views into an ArrayList, then loop through them and check if it is an instance of the Button or TextView or which ever you want, then disable it!
In case data binding is needed
import android.view.ViewGroup
import android.widget.Button
import androidx.core.view.children
import androidx.databinding.BindingAdapter
#BindingAdapter("disableButtons")
fun ViewGroup.setDisableButtons(disableButtons: Boolean) {
children.forEach {
(it as? Button)?.isEnabled = !disableButtons
}
}
Usage:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="#dimen/guideline"
app:disableButtons="#{vm.busy}">
....
</androidx.constraintlayout.widget.ConstraintLayout>
Might work in constraint layout . Use group widget and add all the button ids.
In the java code set enabled false for the group.
For disable all buttons in any nested layouts.
void DisableAllButtons( ViewGroup viewGroup ){
for( int i = 0; i < viewGroup.getChildCount(); i++ ){
if( viewGroup.getChildAt(i) instanceof ViewGroup ){
DisableAllButtons( (ViewGroup) viewGroup.getChildAt(i) );
}else if( viewGroup.getChildAt(i) instanceof Button ){
viewGroup.getChildAt(i).setEnabled( false );
}
}
}
write these two lines on your button declartion in XML
android:setEnabled="false"
android:clickable="false"

Setting of TextView stops marquee scrolling of other TextView

This was asked elsewhere, but the solution did not work for me. So posing it again with more context. The issue is that an activity contains a scrolling music title text view which is disrupted by an updating elapsed time counter text view.
I have these two TextView widgets in my activity layout (although they are encompassed by other layout containers).
<TextView android:id="#+id/v_current_time"
android:minWidth="26dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="right|center_vertical"
android:singleLine="true"
android:textSize="12sp"
android:enabled="false"
/>
<TextView android:id="#+id/v_track_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="normal"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:enabled="true"
/>
The music title is dynamically set to (in the test case) a long line of text. If I never update the text for the current time with the following line, the music title will happily scroll forever no matter how I interact with my pause and play buttons.
tvCurrentTime.setText(DataFormat.formatTime(progress));
But if I do set the text for the current time, the music title will stop. Pressing my pause button somehow kicks scrolling back into gear, but pressing play will cause the current time to update and stop it again.
Per the suggestion in this thread, I attempted to couple the setting of the time text with re-enabling of the scrolling title as follows:
tvCurrentTime.setText(DataFormat.formatTime(progress));
tvTitle.setEnabled(true);
This has no effect on the failure other than to reset the scroller each time it restarts.
Is there some detail that I am missing, or any other thoughts as to what could be going wrong? Thanks a lot!
There is another way to solve this without removing all RelativeLayout.
You can simply wrap the marquee TextView with a LinearLayout inside the RelativeLayout as a container.
Marquee is problematic. When TextView (or the Window containing the TextView)loses focus the marquee is stopped and reset (see the sources for TextView).
I guess you have 3 possible solutions here:
You can set android:focusable="false" in all other Views in your layout. That way your TextView shouldn't lose focus. But that's probably not the solution you want.
You can subclass TextView and change onFocusChanged() and onWindowFocusChanged() to prevent marquee from being stopped and reset.
Create your own implementation of marquee.
(Promoting comment above to an answer) Turns out that the TextView XML configs above work fine without any runtime changes (to reset enabled=true or whatever) IF I get rid of the RelativeLayout's in my layout file and replace them with LinearLayout's. And neither suggestion 1 or 2 above (not sure about 3) works if I don't. That seems like a subtle and bogus undocumented failure of RelativeLayout.
In java code, doing tvTitle.setSelected(true); (here, tvTitle is your sliding TextView variable) worked for me. Doing this, seems to make it focused again. So worked like a charm.
We had an adapter with multiple view types, and first item was one with marquee TextView. After scrolling back to top of the screen text was not shown (we've called textView.isSelected == true).
Also, issue was not the RelativeLayout, there was no need to wrap TextView with LinearLayout, as current structure in layout is:
RelativeLayout
Button
TextView
Below is method from TextView to start marquee:
private void startMarquee() {
// Do not ellipsize EditText
if (getKeyListener() != null) return;
if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
return;
}
if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
&& getLineCount() == 1 && canMarquee()) {
if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
final Layout tmp = mLayout;
mLayout = mSavedMarqueeModeLayout;
mSavedMarqueeModeLayout = tmp;
setHorizontalFadingEdgeEnabled(true);
requestLayout();
invalidate();
}
if (mMarquee == null) mMarquee = new Marquee(this);
mMarquee.start(mMarqueeRepeatLimit);
}
}
It requires for view to have focus or to be selected to start marquee.
In TextView.onFocusChanged(...) the startStopMarquee(focused) method is called and it will trigger the scroll animation. Issue we had with this approach is that we needed to request focus by using postDelayed, which might cause some issues.
After checking what TextView.setSelected(boolean) method was doing, it was clear why textView.isSelected = true was not triggering animation. Inside it it was checking previous isSelected state, and it would handle startMarquee() or stopMarquee() if new isSelected state was different from the previous one.
Solution was to change selected state to false and after that to set it to true which worked perfectly.
Below are both methods, setSelected, and onFocusChanged.
#Override
public void setSelected(boolean selected) {
boolean wasSelected = isSelected();
super.setSelected(selected);
if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
if (selected) {
startMarquee();
} else {
stopMarquee();
}
}
}
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (isTemporarilyDetached()) {
// If we are temporarily in the detach state, then do nothing.
super.onFocusChanged(focused, direction, previouslyFocusedRect);
return;
}
if (mEditor != null) mEditor.onFocusChanged(focused, direction);
if (focused) {
if (mSpannable != null) {
MetaKeyKeyListener.resetMetaState(mSpannable);
}
}
startStopMarquee(focused);
if (mTransformation != null) {
mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}

Defining EditText imeOptions when using InputMethodManager.showSoftInput(View, int, ResultReceiver)

In my application I have a custom view which requires some text input. As the view in itself doesn't contain any actual views (it's a Surface with custom drawing being done), I have a FrameLayout which contains the custom view and underneath it an EditText -view. When the user does a specific action, the custom view is hidden and the EditText takes over for user input. This works fine, but android:imeOptions seem to be ignored for this view. I'm currently doing this:
InputMethodManager inputMethodManager = (InputMethodManager)parent.getSystemService(Context.INPUT_METHOD_SERVICE);
EditText t = (EditText)parent.findViewById(R.id.DummyEditor);
t.setImeOptions(EditorInfo.IME_ACTION_DONE);
inputMethodManager.showSoftInput(t, 0, new ResultReceiver(mHandler) {
#Override
protected void onReceiveResult(
int resultCode, Bundle resultData) {
// We're done
System.out.println("Editing done : " +
((EditText)parent.findViewById(R.id.DummyEditor)).getText());
}
}
);
It seems that the setImeOptions(EditorInfo.IME_ACTION_DONE) has no effect. I've also tried adding the option to the layout XML with android:imeOptions="actionDone". No help.
Any ideas?
this post looks like will answer your question:
How do I handle ImeOptions' done button click?

Categories

Resources