How would you disable user clicks while fragment is transitioning? - android

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

Related

Animate fragments content before closing it

I am displaying a FrameLayout with some components inside a Fragment. In onCreateView(..) I am animating the content of the FrameLayout and everything works fine. Now I want to animate the content before closing the Fragment.
In my current solution I am overriding onBackPressed() in the parent Activity and then I'm calling the method onBackPressed() inside my Fragment and animating the content there. The problem with this solution is, that I want to inflate the Fragment from various activities and then this is not really a nice solution... Does anybody know a better approach?
Thanks for your help!
Note:
I also tried to override onCreateView() and onPause() but the animation is not shown if I start it in those methods
and the following method does not fulfill my requirements either as it animates the whole fragment and I want to animate the content
getActivity().getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.slide_in_up, R.anim.slide_out_up, R.anim.slide_out_down, R.anim.slide_in_down)
Maybe you can try to handle onBackPressed in your fragment like below:
yourRootLayout.setFocusableInTouchMode(true);
yourRootLayout.requestFocus();
yourRootLayout.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
animateMyViews();
return true;
}
return false;
}
});
I wanted to perform sliding exit animation before dismissing the view/fragment.
These are the steps I performed:
I created a runnable task which can dismiss the current screen.
Passed that runnable to animating view.
Used view.postOnAnimationDelayed(runnable, 400) so that it can animate and execute the runnable after 400 milliseconds.
Also I made sure that my animation duration is >= 400 so that the transition is smooth.
Below is a little bit altered version of code for idea.
I used view.postOnAnimationDelayed(runnable, 400) to
Dialog dialog = new Dialog(getActivity(), getTheme()) {
#Override
public void onBackPressed() {
ParentFragment.this.onBackPressed();
Runnable runnable = new Runnable() {
#Override
public void run() {
//Referencing this class in runnable
getInstance().dismiss();
}
};
//webView is the child view loaded on this fragment
if(webView != null && webView.webViewClient != null) {
webView.webViewClient.animateClose(webView, runnable);
} else {
super.dismiss();
}
};
function animateOnClose in webViewClient looked like this:
public void animateClose(final WebView view, Runnable runnable) {
Animation anim = AnimationUtils.loadAnimation(getMainActivityContext(),
animationResource);
view.startAnimation(anim);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.postOnAnimationDelayed(runnable, 400);
// you can also try view.postOnAnimation(runnable);
} else {
runnable.run();
}
}
I think what you need is to notify the Fragment that you are about to destroy it.
A crude pseudocode might look like
myFragment.aboutToClose();
While in your Fragment's aboutToClose() method.
public void aboutToClose()
{
// Perform all the animations you want.
// Don't forget to add onAnimationEnd() call back.
onAnimationEnd()
{
// Notify Activity that Animations have completed.
callback.animationsCompleted();
}
}
Finally in your calling Activity.
public void animationsCompleted()
{
// Destroy fragment.
getActivity().getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.slide_in_up, R.anim.slide_out_up, R.anim.slide_out_down, R.anim.slide_in_down);
}

Ripple effect activated on backpressed

as title says, how to make my ripple effect on ImageButton (ripple effect assigned to this ImageButton) activated on backpressed() without back to the previous activity
with a bit of logic you could achieve that easily. what i thought of is that you have a method lets call it doSomething();
boolean onBackpressed = false;
private void doSomthing() {
if(onBackpressed){
finish();
}else{
// do anything else that the button wants to do;
}
}
now onBackpressed() you could do this
#Override public void onBackpressed() {
onBackpressed = true;
myImageButton.performClick(); // myImageButton.callOnClick()
}
P.S: that i didn't call super.onBackpressed(); so we can control the back press without existing the app.
of course your imageButton click listener calls doSomething() method.

Identify when default Android Checkbox animation ends

When clicking on a Checkbox, a default Android material design animation is triggered (from blank to a "V" mark, and from "V" mark to a blank).
I want to identify when the animation ends.
According to documentation, this should be possible in one of two ways -
When the checkbox is checked or unchecked (setOnCheckedChangeListener()), obtain the current Animation object (getAnimation()) and register a listener on it (setAnimationListener()). Unfortunately, this doesn't work - the Animation object, at this point in time, is null.
Subclass the Checkbox object and implement its onAnimationEnd() method. Unfortunately, this doesn't work as well - the method is never called.
What am I missing? What is a good way to identify when such a default animation ends? I assume the relevant events can be registered on some other view in the activity, but I can't figure out which.
Here is a relevant code snippet for the first approach (animation is always null) -
CheckBox checkBox = (CheckBox)findViewById(R.id.checkbox1);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
Animation animation = compoundButton.getAnimation();
Log.d("Checkbox", "Animation is " + animation);
}
});
I was somewhat successful setting the following OnCheckedChangeListener on the Checkbox.
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Drawable drawable = buttonView.getButtonDrawable().getCurrent();
if (drawable instanceof AnimatedVectorDrawable) {
AnimatedVectorDrawable animatedDrawable = (AnimatedVectorDrawable) drawable;
animatedDrawable.registerAnimationCallback(new Animatable2.AnimationCallback() {
#Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
// your action goes here
}
});
} else {
// and maybe here as well as a fallback
}
}
Quite often (maybe 50% of the time) the onAnimationEnd callback is triggered twice. I don’t understand why, though.

Hide view every 5 seconds with postDelayed

PROBLEM
I have a horizontalListView that is showing user all the images loaded to ViewPager. Its situated on the bottom of the screen.
What I want to do is for horizontalListView to hide when its not being used for more then 5 sec.
How it should work:
User taps screen horizontalListView appears
User scrolls, selects do stuff on horizontalListView and its blocking threads to be fired
After 5 sec of doing nothing on horizontalListView, it disappears
How its working right now:
User taps screen horizontalListView appears
User scrolls, selects ect. and postDelayed is getting fired making my horizontalListView dissappear.
CODE
HorizontalListView and Runnable
horizontalListView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
hideViewHandler.removeCallbacks(mRunnable);
hideViewHandler.postDelayed(mRunnable,5*1000);
return true;
}
});
mRunnable = new StoppableRunnable() {
#Override
public void stoppableRun() {
hideAnimation();
}
};
TapListener that is set on Image responsible for showing horizontalListView
private class TapGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
hListView.clearAnimation();
if (((ViewGroup.MarginLayoutParams) hListView.getLayoutParams()).bottomMargin < 0) {
expandAnimation();
hideViewHandler.postDelayed(mRunnable,5*1000);
} else {
hideAnimation();
}
return true;
}
}
You have to check inside the listener for the MotionEvent.ACTION_UP or MotionEvent.ACTION_CANCEL to only trigger the runnable after them.
Your current code is posting the runnable on any event, but you want to do it only when the user has stopped using the view.
BTW: If you return true from the TouchListener it means that you have consumed the event and the event chain will stop. Most likely the ScrollView will not even scroll, since the event will not be propagated to it.
You can do something like this..
yourScreenLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (horizontalListView.getVisibility() == View.INVISIBLE) {
horizontalListView.setVisibility(View.VISIBLE);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (horizontalListView.getVisibility() == View.VISIBLE)
horizontalListView.setVisibility(View.INVISIBLE);
}
}, 5000);
} else {
horizontalListView.setVisibility(View.INVISIBLE);
}
}
});
I think the source of you problem might be calling removeCallbacks before postDelayed.
The thread itself is fired after u clear the queue.
I have managed to fix the problem myself.
FIX
The main reason why my onTouchListener did not work as intended was that it had not been triggered at all. OnClickListener inside horizontalListView adapter was consuming the trigger.
Now I am passing the info that the click occurred inside onClickListener and do all the things in different method.

Finish (Close) a Android Activity on Touch

How to terminate an Activity in Adroid on touch. Here i shows a view which is described in details.xml. I need to dismiss the activity on touch. I tried the following code. But its not working. Any ideas?
public class Details extends Activity {
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.details);
}
public boolean onTouchEvent(MotionEvent event){
this.finish();
return true;
}
}
You should use
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
finish();
return super.dispatchTouchEvent(ev);
}
Your onTouchEvent() will only be called if no other views under the finger in the view hierarchy have consumed the event. This is uncommon, since many views do interact with touch events.
It is wrong to do stuff in onTouchEvent() without look at what the actual event is.
Generally you should implement a view that reacts to touches and does the appropriate thing.
I know its an old question but my solution is this if anyone need it:
I had the same problem so I solved it with giving my parent layout i.e(consraintLayout) an id then calling finish() in setOnClickListener() method and it worked.
View v = findViewById(R.id.validateLayout);
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
validateLayout is parent constraint layout of my activity.
any review is welcomed.
Also i want to know if suggestion of Addev is better then this code.
You didn't install a listener in onCreate(). Like:
findViewById(R.id.some_view).setOnTouchListener(this);

Categories

Resources