Since this topic is quite popular, I have to say that I searched tons of similar questions and still don't have a working solution.
Problem short description: in my Activity I have a "search mode", when it starts I show the soft keyboard, when it finishes, I want to hide the keyboard, but can't find a way to do it.
What I have tried:
using flag InputMethodManager.HIDE_IMPLICIT_ONLY instead of 0
using flag InputMethodManager.HIDE_NOT_ALWAYS instead of 0
setting windowSoftInputMode="stateAlwaysHidden"
setting windowSoftInputMode="stateHidden"
Nothing seems to work, the keyboard stays visible. The code starting the "search mode":
private void onStartSearch(){
isSearch = true;
tvMyFriends.setVisibility(View.GONE);
etSearch.setVisibility(View.VISIBLE);
etSearch.startAnimation(AnimationUtils.loadAnimation(this, R.anim.on_show_search));
etSearch.requestFocus();
invalidateOptionsMenu();
imm.showSoftInput(etSearch, InputMethodManager.SHOW_IMPLICIT);
}
The code that finishes the "search mode":
private void onCancelSearch(){
Animation animHideSearch = AnimationUtils.loadAnimation(this, R.anim.on_cancel_search);
animHideSearch.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
/* no action required */
}
#Override
public void onAnimationEnd(Animation animation) {
etSearch.setVisibility(View.GONE);
tvMyFriends.setVisibility(View.VISIBLE);
isSearch = false;
invalidateOptionsMenu();
if(etSearch.getText().toString().length()>0)
etSearch.setText("");
etSearch.clearFocus();
imm.hideSoftInputFromInputMethod(etSearch.getWindowToken(), 0);
}
#Override
public void onAnimationRepeat(Animation animation) {
/* no action required */
}
});
etSearch.startAnimation(animHideSearch);
}
The question: how can I force the damn keyboard to disappear?
UPD: set a bounty. Still looking for a reliable, device-independent solution to force-hide the soft keyboard OR to detect whether the keyboard is currently showing.
Hide the keyboard before changing the Visibility of you EditText to GONE.
...
imm.hideSoftInputFromWindow(etSearch.getWindowToken(), 0);
etSearch.setVisibility(View.GONE);
...
Related
I want my fragment to not receive any clicks on the views while the fragment transition animation is not yet finished. It is just a simple fade. But things get wonky when I immediately press any view while the next fragment is fading in.
Any thoughts how to achieve this?
This is actually used in my own app. The idea is very simple, it just works, but needs quite a lot of additional coding.
The idea is very simple, use a boolean variable to maintain whether the screen should be locked, let's call it screenLocked. I do not actually block the click, but let the click do nothing.
For those actions which takes time, set screenLocked to true before start working, and set it back to false when the task is finished. Also you have to add checking on screenLocked before any action is done.
Another difficulty of the this method is that you need to have clear end point of your tasks. Using Fragment transition as an example, if the backstack is poped, there has no actual callback notifying you, for this case. To handle this, I would set another flag releaseOnResume before starting Fragment transition, and in onResume, I would use this flag to check if I should set screenLocked back to false.
Other solutions I have tried but not used:
Before I settled with the method I just mentioned, I have tried setEnabled, setClickable, or any UI based blocking, e.g. add a FrameLayout on top and capture all touch events.
These methods are not bad, especially given that they are easy to implement.
The only problem is that, onClick events can be queued due to double tapping, when you are handling the first onClick event, actually there could be another one queued up, even if you do any UI changes immediately to block any further clicks, you can't stop the next onClick event to come because it is queued already.
Hope this helps.
I use a countdown timer.
I manage this through the ontouch listener.
I create a method that manages the creation of the timer. I call it in the ontouch event. I use two methods (this is optional, but good for extensibility) to handle button enabling and disabling. I then use these methods with the timer to enable and disable the button.
See my code snippet.
In oncreate:
#Override protected void onCreate(Bundle savedInstanceState) {
/.../
button.setOnTouchListener(new View.OnTouchListener() {
#Override public boolean onTouch(View v, MotionEvent event) {
disableButton(button);
countDwn1();
/... time to do whatever you need..
// custom methods...
fragment = new MyFragAddFragment();
replaceFragment(fragment);
return false;
}
});
Methods:
public void countDwn1() {
CountDownTimer countDownTimer = new CountDownTimer(2000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
enableButton(button);
}
}.start();
}
public void disableButton(Button button) {
button.setEnabled(false);
}
public void enableButton(Button button) {
button.setEnabled(true);
}
You can extend this method to include passing the button as a parameter into the timer, for extensibility.
In the end I used something like this. I created a parent class for all my fragments and overriden the OnCreateAnimation method which is called on every animation.
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
//Check if the superclass already created the animation
Animation anim = super.onCreateAnimation(transit, enter, nextAnim);
//If not, and an animation is defined, load it now
if (anim == null && nextAnim != 0) {
anim = AnimationUtils.loadAnimation(getActivity(), nextAnim);
}
//If there is an animation for this fragment, add a listener.
if (anim != null) {
anim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
isAnimationFinished = false;
}
#Override
public void onAnimationEnd(Animation animation) {
isAnimationFinished = true;
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
return anim;
}
The isAnimationFinished variable is a public variable that can be used by the calling activity and the child classes
I am having an issue with the toolbar and the back button. Here is the setup I have:
When I add a detail fragment, I animate the toolbar hamburger as outlined here. and this causes the hamburger to animate to an arrow.
Even in the comments section, a user mentions:
This works perfectly. Just set start=0 and end=1 to go from hamburger
to arrow, and start=1 and end=0 for arrow to hamburger. One thing
you'll have to keep track of is when the drawer is closed when the
arrow is shown. At this point, the hamburger ends up being shown
(because of the drawer's slide), which you'll have to correct.
But I cannot figure out how to get the back arrow to function properly. When I press the back arrow, the drawer opens and the detail fragment does not pop. How should I go about implementing this?
Questions
How should I animate hamburger to back arrow when adding a detail fragment? assuming the linked solution is not good enough.
How do I override the back arrow to perform only specific functions I wish? like animate to hamburger, pop back stack and NOT open the drawer.
After several hours of searching and playing around, I was able to build a solution that delivered on each requirement. Sources: 1,2
detailFragmentActive = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
setSupportActionBar(mToolbar);
...
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(detailFragmentActive) {
onBackPressed();
//if(displayBackAgain)
//return; //return after so you don't call syncState();
}else if (mDrawerLayou.isDrawerOpen(GravityCompat.START))
mDrawerLayout.closeDrawer(GravityCompat.START);
else
mDrawerLayout.openDrawer(GravityCompat.START);
mDrawerToggle.syncState();
}
});
}
private void animateHamburger(boolean isArrow){
int start = 0, end = 1;
if(isArrow){
detailFragmentActive = false;
start = 1; end = 0;
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
}else{
detailFragmentActive = true;
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
ValueAnimator anim = ValueAnimator.ofFloat(start, end);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float slideOffset = (Float) valueAnimator.getAnimatedValue();
mDrawerToggle.onDrawerSlide(mDrawerLayout, slideOffset);
}
});
anim.setInterpolator(new DecelerateInterpolator());
anim.setDuration(500);
anim.start();
}
#Override
public void onBackPressed() {
super.onBackPressed();
animateHamburger(true);
}
public void onFragmentChange(){
...
animateHamburger(false);
}
You can set listener for this button:
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (flagDeterminingAction) {
drawerLayout.openDrawer(drawerListView);
} else {
onBackPressed();
//or popbackstack or whatever you are using to going back in navigation
}
}
I'm running into some problems trying to detect if the software keyboard is visible or not.
I search for a solution (SO included) but without luck.
What's weird is that I did this in the same app by checking if the window size has changed, like this:
final View activityRootView = findViewById(R.id.tlFull);
//tlFull is the activity table layout
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener()
{
public void onGlobalLayout()
{
int heightDiff = activityRootView.getRootView()
.getHeight() - activityRootView.getHeight();
if (heightDiff > 100)
{
//did some stuff here
}
else
{
//and here
}
}
});
and by adding
android:windowSoftInputMode="adjustResize" in the manifest file.
Now I am trying to do the same thing on a login activity but it seams that for some reason heightDiff is always 38 so this no longer works. Same applies for every method found on SO that uses the same approach.
I also tried this but it always returns true.
final View activityRootView = findViewById(R.id.tlFull);
activityRootView.getViewTreeObserver()
.addOnGlobalLayoutListener(new OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
if (getResources().getConfiguration()
.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO)
{}
else
{}
}
});
Also, since I am not using fragments I cannot use this:
InputMethodManager imm = (InputMethodManager) getActivity()
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm.isAcceptingText())
{}
else
{}
I also tried a different approach.
I do what I want to do in etPass (the edit text that triggers the keyboard) onClick method:
etPass.setOnClickListener(new OnClickListener()
{
public void onClick(View viewIn)
{
ivImageView.setVisibility(View.GONE);
}
});
Unfortunately, even if the keyboard shows up the image view does not always disappear on the first tap so I have to tap again.
The second problem is that I tried to show the iv again in the onBackPressed() method. This works but not how it should.
When I first hit the back button the keyboard disappears but I have to tap again to make the image visible. (I tried the solution provided here but it didn't work: EditText with soft keyboard and "Back" button)
Also, this prevents the app from closing on onBackPressed().
Any ideas on how to solve this and why that diff is always the same?
I couldn't find a better solution for this and now I fear that the same problem might appear on the old activity in the future (although it seams to work fine in tests)
you just use this method to sense the Configuration changes in your application....
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks whether a hardware keyboard is available
if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
} else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
}
}
Starting with API 14 you can use this flag SYSTEM_UI_FLAG_HIDE_NAVIGATION on a View within your Activity to tell the system you want to hide the navigation bar until there is user interaction (screen touch). Once the user taps the screen the bar is shown.
The Activity that I am doing this in takes some action (starts a separate activity) when the user touches the screen, before adding the above flag to my view this worked perfectly.
After adding that flag the system intercepts the first screen touch and reacts to it by showing the nav bar. It's not until the second touch that any of my Views, or my Activity are receiving a TouchEvents.
Does anyone know of a way that I can set up a listener that will let me launch my second activity the first time the screen is touched instead of needing to double tap when using this hide nav flag?
I've tried all of the following and I'm not getting callbacks to any of them when the screen is touched for the first time to show the nav bar.
#Override
public void onUserInteraction(){
Log.i(myTag, "INTERACT");
}
#Override
public boolean onGenericMotionEvent(MotionEvent me){
Log.i(myTag, "GENERIC");
return true;
}
//I thought maybe the size change would lead to a callback here. No dice though.
#Override
public void onWindowAttributesChanged(WindowManager.LayoutParams params){
Log.i(myTag, "WINDOW CHANGE");
}
#Override
public boolean dispatchTouchEvent(MotionEvent me){
Log.i(myTag, "TOUCH");
return true;
}
Note: I am not trying to prevent the nav bar from being shown upon the first touch, I just want to also take some other action when that event occurs.
As Josh Lee suggested in his comment, View.OnSystemUiVisibilityChangeListener was the key.
Here is the code that I used:
mView.setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int vis) {
Log.i(myTag, "System UI"+ vis);
if(vis == 0){
Intent i = new Intent(MainActivity.this, AnotherActivity.class);
startActivity(i);
finish();
}
}
});
I think that mView could be a reference to any view that is currently showing in your Activity. In my case it was a fullscreen VideoView, and was the only view in my layout.
I use ActionMode to select items in a grid. The problem is that I cannot recognize whether exactly the Done button is clicked. The only I can is to know that ActionMode is finished. But pressing Back finishes the ActionMode too.
The desired behavior is to accept selection on Done click, and exit ActionMode on Back press.
I tried to use ActionMode.setCustomView() but it doesn't affect the Done button. The Activity.onBackPressed() is not called when ActionMode is started.
The one solution I've found is to use ActionBarSherlock and get the Done button manually:
View closeButton = findViewById(R.id.abs__action_mode_close_button);
But it works on Android 2.x-3.x only, because on 4.x a native action bar is used.
Please don't do that as it's implementation specific and extremely non-standard.
You can use the onDestroyActionMode callback for when an action mode is dismissed.
Here is the solution:
ActionMode mMode = MyActivityClass.this.startActionMode(some implementation);
int doneButtonId = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android");
View doneButton = MyActivityClass.this.findViewById(doneButtonId);
doneButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// do whatever you want
// in android source code it's calling mMode.finish();
}
});
Here is my implementation, and it's a proper hack but it works and I can't really find an alternative to doing something specific when the ActionMode DONE is clicked. I find it really weird that you can't capture this event more elegantly.
Any suggestions to making this slightly less ugly would be greatly appreciated...
In my activity..
boolean mActionModeIsActive = false;
boolean mBackWasPressedInActionMode = false;
#Override
public boolean dispatchKeyEvent(KeyEvent event)
{
mBackWasPressedInActionMode = mActionModeIsActive && event.getKeyCode() == KeyEvent.KEYCODE_BACK;
return super.dispatchKeyEvent(event);
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu)
{
mActionModeIsActive = true;
return true;
}
#Override
public void onDestroyActionMode(ActionMode mode)
{
mActionModeIsActive = false;
if (!mBackWasPressedInActionMode)
onActionModeDoneClick();
mBackWasPressedInActionMode = false;
}
public void onActionModeDoneClick();
{
// Do something here.
}
If you are using Fragments with your Activity then some of this code will probably need to be in the Fragment, and the other bits in the Activity.
#JakeWharton (and other ActionBarSherlock users) if you see this on your travels. I'd be interested to know if the above is compatible with ABS as I have yet to integrate ABS with my current project.