Is it possible to pause Motion layout transition and then resume this transition?(for example I show dialog fragment and I need to pause this motion scene and after dismissing dialog I want to resume transition)
I launch transition by code like as MotionLayout.setTransitionToEnd()
You should be able to get the current progress and set to that value... then when you are ready to resume, you can call the transitionToEnd() again and it will resume from that value... so for example
private void pauseAnimation() {
float currentProgress = motionLayout.getProgress()
motionLayout.setProgress(currentProgress())
}
private void resumeAnimation() {
motionLayout.transitionToEnd()
}
That should do what you are looking to accomplish
I found the public method in MotionLayout class setProgress(float) and setInterpolatedPreogress(float) https://developer.android.com/reference/android/support/constraint/motion/MotionLayout.html#setProgress(float)
https://developer.android.com/reference/android/support/constraint/motion/MotionLayout.html#setInterpolatedProgress(float)
which allows to resume animation
You can store the position of progress that you left before pausing the fragment like so:
private var outState: Bundle? = null
const val KEY_CURRENT_POSITION_LIST_CATEGORY = "KEY_CURRENT_POSITION_LIST_CATEGORY"
override fun onPause() {
super.onPause()
outState = Bundle()
outState!!.putFloat(KEY_CURRENT_POSITION_LIST_CATEGORY, motion_layout.progress)
}
And onResume, you can just set the progress of the animation in the motion_layout
override fun onResume() {
super.onResume()
if (outState != null) {
motion_layout.progress=outState!!.getFloat(KEY_CURRENT_POSITION_LIST_CATEGORY)
}
}
Related
I use this fragment of code in an activity to display a timer countdown:
//SET COUNTDOWN TIMER in onCreate()
//var tempo is a :Long number
var textView = findViewById<TextView>(R.id.countdownTimer)
object : CountDownTimer(tempo, 1000) {
override fun onTick(millisUntilFinished: Long) {
textView.setText("Remaining time: "+tim(millisUntilFinished) )
}
override fun onFinish() {
textView.text = "Times up!"
}
}.start()
If possible I'd like to implement the possibility to pause this CountDownTimer in onBackPressed() and resume it again?
override fun onBackPressed() {
AlertDialog.Builder(this).apply {
setTitle("Confirm")
setMessage("Exit app?")
setPositiveButton("Yes") { _, _ ->
// if user press yes, cancel timer?
super.onBackPressed()
}
setNegativeButton("No"){_, _ ->
// if user press no, then return the activity and resume timer?
}
setCancelable(true)
}.create().show()
}
My idea would be to edit var tempo differentitate it everytime OnBackPressed and copy the section of this 1st code piece in onCreate() and put it onResume().
I'm quite confused on using this tbh. MAybe there's a faster and better way?
You cannot pause CountDownTimer. What you can do is stop it and then start another one (with the remaining time) when you want to resume it.
I recently updated my dependencies to include the OnBackPressedCallback change from an interface into an abstract class.
I have set things up according to the new documentation here but I feel like things are not working as they should.
My fragment's OnCreate looks a lot like the documentation:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(this) {
backPressed()
}
}
When I press the back button, the code in backPressed() is run, but nothing more happens.
I have tried calling handleBackPressed() and requireActivity().onBackPressedDispatcher.onBackPressed() and requireActivity().onBackPressed() from inside the callback, but those all cause a StackOverflowError because it seems to run that callback recursively.
There has got to be something really obvious I am missing...
There has got to be something really obvious I am missing...
You forget to disable your custom callback in you fragment before asking Activity to handle back pressed.
My solutiuon suitable for me:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final OnBackPressedCallback callback = new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
if (/*situation to handle back pressing*/){
//here handle your backPress in your fragment
} else {
setEnabled(false); //this is important line
requireActivity().onBackPressed();
}
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}
When you register an OnBackPressedCallback, you are taking on the responsibility for handling the back button. That means that no other on back pressed behavior is going to occur when you get a callback.
If you're using Navigation, you can use your NavController to pop the back stack:
requireActivity().onBackPressedDispatcher.addCallback(this) {
backPressed()
// Now actually go back
findNavController().popBackStack()
}
This works for me in androidx.appcompat:appcompat:1.1.0
requireActivity().onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(TAG, "Fragment back pressed invoked")
// Do custom work here
// if you want onBackPressed() to be called as normal afterwards
if (isEnabled) {
isEnabled = false
requireActivity().onBackPressed()
}
}
}
)
You can also remove callback instead of setting enabled if it's no longer needed. I use it with nested graph like this because when you touch back in a nested nav graph with it's NavHostFragment, it removes it from main fragment back stack instead of opening last fragment in nested nav graph.
// Get NavHostFragment
val navHostFragment =
childFragmentManager.findFragmentById(R.id.nested_nav_host_fragment)
// ChildFragmentManager of the current NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val backStackEntryCount = navHostChildFragmentManager!!.backStackEntryCount
if (backStackEntryCount == 1) {
// We are at the root of nested navigation, remove this callback
remove()
requireActivity().onBackPressed()
} else {
navController?.navigateUp()
}
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
I have an activity with two fragments.
The second one is called when I click on something to the first.
What I want is this : if i click on "back" button, I want to go back to the first fragment (that is working), but I want to set the visibility to VISIBLE on an element (if the first fragment is called with back press only)
How do I do that ?
I tried something like this (in my main fragment), I've found the idea in another topic, but this is trigger always in my main activity :
override fun onResume() {
super.onResume()
view?.isFocusableInTouchMode = true
view?.requestFocus()
view?.setOnKeyListener { v, keyCode, event ->
if(event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
Log.i(TAG, "backpress pressed")
return#setOnKeyListener true
}
return#setOnKeyListener false
}
}
Temporary solution :
I've created a companion object with a value true or false and I change it everytime I need it, but it's temporary only.
Assuming your second Fragment replaces the first (i.e. using FragmentTransaction#replace), your first Fragment (we'll call them FragmentA and FragmentB) will be paused (i.e. onPause() will be called on FragmentA).
When you press the back button, the backstack will be popped, and FragmentA will be resumed (i.e. onResume() will be called).
What I would recommend, is to save a boolean flag in FragmentA, and set it to true when you show FragmentB. Then, in FragmentA#onResume, you can check if the flag is set to true, and set it back to false while handing the case that you wanted.
For example, something like:
private const val STATE_WAITING_FOR_FRAGMENT_B = "state_waiting_b"
class FragmentA : Fragment() {
private var isWaitingForFragmentB: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
isWaitingForFragmentB = savedInstanceState.getBoolean(STATE_WAITING_FOR_FRAGMENT_B)
}
}
override fun onResume() {
super.onResume()
if (isWaitingForFragmentB) {
isWaitingForFragmentB = false
// handle your view state here
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putBoolean(
STATE_WAITING_FOR_FRAGMENT_B,
isWaitingForFragmentB
)
}
private fun showFragmentB() {
isWaitingForFragmentB = true
// do fragment transaction here
}
}
I'm not good at grammar.
First fragment do not call resume function when returning.
You must create callback with interface.
A good approach should be passing some flag, on the second fragment, by activity intent and to capture it on the first Fragment on onResume()
If you need extra info, just let me know
I'd like to show a custom dialog when a user clicks back button in Kotlin.
I tried this code but it doesn't work, when I click the back button the custom dialog shows and then disappears
override fun onBackPressed() {
super.onBackPressed()
onPause()
creatAlertDialog()
}
fun creatAlertDialog() {
var dialogs = Dialog(this#MainActivity)
dialogs.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialogs.setCancelable(false)
dialogs.setContentView(R.layout.back_press)
dialogs.btn_yes.setOnClickListener {
finish()
}
dialogs.btn_no.setOnClickListener {
dialogs.dismiss()
}
dialogs.show()
}
Delete super.onBackPressed() from your onBackPressed() callback. This way you'll avoid your super class to call its onBackPressed() method and your activity will not be destroyed.
override fun onBackPressed() {
creatAlertDialog()
// whatever you want here
}
I want to show dialog when user press back or quit from fragment if there are some data unsaved. I am trying to override onbackpressed but unfortunately I got error lateinit property barcodeList has not been initialized. how to solve it?
here is my script on activity:
override fun onBackPressed() {
val theFragment = supportFragmentManager.fragments
for(i in 0 until theFragment.size)
{
if(theFragment[i].tag == "stocker_fragment")
{
StockerFragment().onBackPressed()
}
}
}
and this is in fragment:
fun onBackPressed() {
var check = false
// this barcodeList variable error.
for(i in 0 until barcodeList.size)
{
if(barcodeList[i].barcode.trim()=="")
{
check = true
break
}
}
if (check)
{
AlertHelper(context).onBackPressedAlert()
}
}
FYI: I have initialized barcodeList on onCreateView and everything is fine. only error in onBackPressed.
And my last question is, how do i know if user quit from fragment without pressing back button?
I think the problem is in your onBackPressed() implementation in the Activity. With the line StockerFragment().onBackPressed() you are creating a new instance of the StockerFragment and calling onBackPressed() on it, rather than calling it on the instance that is actively being used.
You should be able to adjust your Activity onBackPressed() like so:
override fun onBackPressed() {
val theFragment = supportFragmentManager.fragments
for(i in 0 until theFragment.size)
{
if(theFragment[i].tag == "stocker_fragment")
{
(theFragment[i] as StockerFragment).onBackPressed()
}
}
}
You can also make this a bit more kotliny like so:
override fun onBackPressed() {
supportFragmentManager.fragments.forEach { fragment ->
if (fragment is StockerFragment) {
fragment.onBackPressed()
}
}
}
You'll probably also want to figure out a way to decide whether the fragment's onBackPressed has determined that the Activity should stick around or not. Then, if the fragment is happy, you should call super.onBackPressed() in the Activity so that the expected back behavior (leave the Activity) happens.