I have a method called showProgress which runs a fade animation and switches the current view with a page that shows just a loading spinner (ProgressBar).
fun showProgress(show: Boolean, progressView: ProgressBar, layoutView: View): Completable {
val showView = if (show) progressView else layoutView
val hideView = if (show) layoutView else progressView
return fadeAnimation(showView, hideView)
}
private fun fadeAnimation(showView: View, hideView: View): Completable {
return Completable.create {
// animateSemaphore.acquire()
Log.d("beforeFade", "showView: ${showView.visibility}; hideView: ${hideView.visibility}")
val animTime: Long = showView.context.applicationContext.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
showView.alpha = 0f
handler.post {
showView.visibility = View.VISIBLE
showView.animate()
.alpha(1f)
.setDuration(animTime)
.setListener(null)
hideView.animate()
.alpha(0f)
.setDuration(animTime)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
hideView.visibility = View.GONE
Log.d("fade", "showView: ${showView.visibility}; hideView: ${hideView.visibility}")
it.onComplete()
}
})
}
}
}
I use it when I'm waiting for a network request to complete with RxJava and I want to show the ProgressBar while the app is waiting for the request to finish. However, sometimes the network request will return before showProgress is finished and will cause the animation to run twice causing progressView and layoutView to both be set to View.GONE.
My question is how do I make sure that when showProgress is called while another showProgress is still running, the showProgress being called will wait till the one running is finished? Just an FYI, semaphores won't exactly work since showProgress runs on the main thread and will cause a running showProgress to freeze.
Related
I want to animate the progress bar so i am setting its progress using for loop but the loop is too much faster that i can't see the animation . I want the code to add a delay in the loop , i tried using thread delay but not working -
here is the code
private fun showProgress() {
for(i in 0..100){
Thread{
binding.customProgressBar.progress=i
Thread.sleep(100)
}
}
}
Solution : Was not calling start method , but if there any other approach then please let me know
private fun showProgress() {
Thread {
for (i in 0..100) {
binding.customProgressBar.progress = i
Thread.sleep(100)
}
}.start()
}
now i want to terminate the thread when fragment is on pause state .
how to achieve that ?
since you are using kotlin it is better to use coroutine, you can achieve your goal with something like this:
private suspend fun someProgress(scope: CoroutineScope) {
val job = scope.launch {
for (i in 0..100) {
binding.customProgress.progress = i
delay(100)
}
}
// use job.cancel() for cancelling the job or use job.join() for waiting for the job to finish
}
you can learn more about coroutine and how it works in here.
What I am trying to do is retrieve data from the server when I click a button. When I click the button, I want to show my "Loading..." TextView for 2 seconds, and only then show the data I got from the server. How can I do this?
For now my animation is working, but the data is showing almost instantly. I want to delay that. Using Thread.sleep(2000) causes both the data and Loading to be delayed.
val loadingAnimation :TextView = findViewById<TextView>(R.id.loadingAnimationTextView)
val alphaAnim = AlphaAnimation(1.0f, 0.0f)
alphaAnim.startOffset = 0
alphaAnim.duration = 2000
alphaAnim.setAnimationListener(object : AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
//not sure what code to put here
}
override fun onAnimationEnd(animation: Animation) {
// make invisible when animation completes, you could also remove the view from the layout
loadingAnimation.setVisibility(View.INVISIBLE)
}
override fun onAnimationStart(animation: Animation?) {
loadingAnimation.setVisibility(View.VISIBLE)
}
})
loadingAnimation.setAnimation(alphaAnim)
Thread.sleep(2000)
You can use the handler for this task.
Handler(Looper.getMainLooper()).postDelayed({
// Show you data here
loadingAnimation.setVisibility(View.INVISIBLE)
}, 2000)
Here, 2000 = 2 seconds
It's probably easier to use the ViewPropertyAnimator stuff:
loadingAnimation.animate()
.alpha(0)
.duration(2000)
.withEndAction {
// data displaying code goes here
}.start()
but honestly I don't think there's anything wrong with populating an invisible list, and just making it visible when you want to display it. But that up there is a way to chain runnable code and animations, one after the other
So I read this great Medium post by Chris Banes here which explains how coroutines or specifically suspend functions can be used to coordinate animations without the "callback hell" that you can fall into when chaining animations together. I managed to get the onAnimationEnd listener extension working as per his example but I can't seem to do the same for the onAnimationStart listener, here is my awaitEnd method
suspend fun Animator.awaitEnd() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator) {
endedSuccessfully = false
}
override fun onAnimationEnd(animation: Animator) {
animation.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
})
}
In the code below I'm using this in an async block and awaiting first the animation to finish and then the coroutine to finish
val anim = async {
binding.splash.circleReveal(null, startAtX = x, startAtY = y).run {
start()
//...doStuff()
awaitEnd()
}
}
anim.await()
This works beautifully, adding logs to the correct functions shows me that everything is being called exactly as expected, now to add a started extension in the same way...
suspend fun Animator.started() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator?) {
endedSuccessfully = false
}
override fun onAnimationStart(animation: Animator?) {
Log.d("DETAIL", "Animator.started() onAnimationStart")
animation?.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
})
}
and call it from the same async block after the start method (this may have something to do with things)
val anim = async {
binding.splash.circleReveal(null, startAtX = x, startAtY = y).run {
start()
started()
//...doStuff()
awaitEnd()
}
}
anim.await()
now what happens is the coroutine for started gets suspended but never resumes, if I add some logging statements I can see that it calls start(), it then calls started() but then doesn't go any further, obviously I've tried changing the order of operations to no avail can anyone see what I'm doing wrong here?
many thanks
EDIT
I also tried this for adding a sharedElementEnter transition but again it just will not work for me,
suspend fun TransitionSet.awaitTransitionEnd() = suspendCancellableCoroutine<Unit> { continuation ->
val listener = object : TransitionListenerAdapter() {
private var endedSuccessfully = true
override fun onTransitionCancel(transition: Transition) {
super.onTransitionCancel(transition)
endedSuccessfully = false
}
override fun onTransitionEnd(transition: Transition) {
super.onTransitionEnd(transition)
Log.d("DETAIL","enterTransition onTransitionEnd")
transition.removeListener(this)
if (continuation.isActive) {
// If the coroutine is still active...
if (endedSuccessfully) {
// ...and the Animator ended successfully, resume the coroutine
continuation.resume(Unit)
} else {
// ...and the Animator was cancelled, cancel the coroutine too
continuation.cancel()
}
}
}
}
continuation.invokeOnCancellation { removeListener(listener) }
this.addListener(listener)
}
and again trying to use the await method
viewLifecycleOwner.lifecycleScope.launch {
val sharedElementEnterTransitionAsync = async {
sharedElementEnterTransition = TransitionInflater.from(context)
.inflateTransition(R.transition.shared_element_transition)
(sharedElementEnterTransition as TransitionSet).awaitTransitionEnd()
}
sharedElementEnterTransitionAsync.await()
}
You are correct - coroutine is never resumed.
Why?
By the time you call started() method animation has already started. It means onAnimationStart defined in a listener within started() will not be called (as it has been already called) placing the underlying coroutine in a forever waiting state.
The same will happen if you call started() before start(): underlying coroutine will be waiting for onAnimationStart to be called but it will never happen because start() method call is blocked by the coroutine created by started() method.
That is almost a dead-lock.
Solution 1
Call start() before started() returns:
suspend fun Animator.started() = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
...
})
// Before the coroutine is even returned we should start the animation
start()
}
Solution 2
Pass in a function (that optionally takes a CancellableContinuation<Unit> argument):
suspend fun Animator.started(executeBeforeReturn: (CancellableContinuation<Unit>) -> Unit) = suspendCancellableCoroutine<Unit> { continuation ->
continuation.invokeOnCancellation { cancel() }
this.addListener(object : AnimatorListenerAdapter() {
...
})
executeBeforeReturn(continuation)
}
It will allow you to:
use the coroutine (e.g. cancel) before you even start the animation;
avoid the lock.
Example:
val anim = async {
ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f).run {
started { continuation ->
if (anything) {
continuation.cancel()
}
start()
}
awaitEnd()
}
}
anim.await()
I'm basically trying to execute a method in my RecyclerView inside on scroll state change here I'm using a Handler thread to execute a method after some delay but the method is not getting executed at all but however if I put that method outside the handler thread it's getting executed successfully so my question is why am I unable to run Handler thread inside RecyclerView onscroll here is my code please take a look.
rvsongs!!.addOnScrollListener(object: RecyclerView.OnScrollListener(){
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if(newState== SCROLL_STATE_IDLE) {
val timerHandler = Handler()
var updater:Runnable?=null
updater = object:Runnable {
override fun run() {
rvsongs!!.setIndexBarVisibility(false)//this method is not getting executed however when put outside handler thread it's executed without any problem
}
}
timerHandler.postDelayed(updater,100)
}
else
{
rvsongs!!.setIndexBarVisibility(true)
}
}
})
The issue is that you are calling setIndexBarVisibilty after the complete onScrollStateChanged is processed. In other words, the recyclerView is rendered before setIndexBatVisibility.
To solve this, just call invalidate after setIndexBarVisibility
...
var updater:Runnable?=null
updater = object:Runnable {
override fun run() {
rvsongs!!.setIndexBarVisibility(false)
rvsongs.invalidate()
}
}
...
I'm having an issue where whenever I call this ViewModel method I got the UI blocked and the line ui_registration_done_progressBar.visibility = View.VISIBLE is not executed even though the debbuger shows me that is called normally
The sendRegistration method deals with a lot of stuff and takes some time to be finished, once it is finished the UI is unlocked and the ui_registration_done_progressBar.visibility = View.VISIBLE is executed
override fun onClick(v: View?) {
when(v?.id){
R.id.ui_registration_done_next ->{
ui_registration_done_progressBar.visibility = View.VISIBLE
sendRegistrationViewModel.sendRegistration()
}
}
}
I tried to call faster methods from ViewModel the in this case the problem does not occurs
I would recommend getting the long running task off of the UI thread. You could add use a runnable or an async task.
e.g.:
Runnable runnable = new Runnable() {
#Override
public void run() {
sendRegistrationViewModel.sendRegistration();
}
};
AsyncTask.execute(runnable);
You can use doAsync to run the task in a background thread
import org.jetbrains.anko.doAsync
...
...
override fun onClick(v: View?) {
when(v?.id){
R.id.ui_registration_done_next ->{
doAsync {
ui_registration_done_progressBar.visibility = View.VISIBLE
sendRegistrationViewModel.sendRegistration()
}
}
}
}
adding the dependency into your build.gradle file:
dependencies {
...
...
//* To use doAsync instead of AsyncTask
implementation "org.jetbrains.anko:anko-commons:0.10.4"
}