I have used Animatable.animateTo for animating like below,
val percentageAnimate = remember { Animatable(0.001f) }
LaunchedEffect(Unit) {
percentageAnimate.animateTo(percentage)
}
with percentageAnimate.value I will be drawing my PieChart in the Canvas Composable.
I need the animation only during the first composition.
When I used the above said item in the LazyVerticalGrid, the animation gets triggered everytime when the list item got recycled and added.
This is because you have set percentageAnimate as key, the key is changed (and its internal state) when you call .animateTo() and that leads to relaunching the coroutine in LaunchEffect. If you want to start the animation only once you need to have constant key.
val percentageAnimate = remember { mutableStateOf(Animatable(0.001f)) }
LaunchedEffect(Unit) {
percentageAnimate.value.animateTo(percentage)
}
Then use it in the draw phase percentageAnimate.value
Related
I have a Column with remember scroll state. I am using a derived state as follows.
val isHidden = remember {
derivedStateOf {
columnState.value > 400
}
}
I want to display a search bar whenever it scrolls beyond 400 pixels.
Another approach i used is to create a state based on offset of first item in Column using function onGloballyPositioned.
The modifier of first item is as follows:
Box(modifier = Modifier
.graphicsLayer { translationX = horizontalOffset.value }
.onGloballyPositioned { coordinates ->
isHidden.value = coordinates.boundsInRoot().top == 0f
}) {
The problem is no matter which approach i choose it always lags when the state is changed (when the value checks for < or > 400) or in case of onGloballyPositioned if the Compose reaches top of the screen i.e. y coordinate of view == 0.
In both of the approaches isHidden is going to be true. Changing is hidden in both cases causing lag at the point of change.
I can't use lazy row because of some restrictions. Is there any solution to implement this without the lag.
I'm having troubles with some animation in a recycler view. I do the relevant measurements in onViewAttachedToWindow:
override fun onViewAttachedToWindow(holder: PairingViewHolder) {
super.onViewAttachedToWindow(holder)
// get originalHeight & expandedHeight if not gotten before
if (holder.expandedHeight < 0) {
// Execute pending bindings, otherwise the measurement will be wrong.
holder.itemViewDataBinding.executePendingBindings()
holder.cardContainer.layoutParams.width = expandedWidth
holder.expandedHeight = 0 // so that this block is only called once
holder.cardContainer.doOnLayout { view ->
holder.originalHeight = view.height
holder.expandView.isVisible = true
// show expandView and record expandedHeight in next layout pass
// (doOnPreDraw) and hide it immediately.
view.doOnPreDraw {
holder.expandedHeight = view.height
holder.expandView.isVisible = false
holder.cardContainer.layoutParams.width = originalWidth
}
}
}
}
The problem is that doOnPreDraw gets called just for some views. It is something related to the visibility of the views I guess, since the smaller the items (expanded) are, the highest the count of the ones on which onPreDraw gets called.
My guess is that since I'm expanding them in onLayout, the recyclerView consider visible only the ones that when expanded are actually visible on screen. In onPreDraw I collapse them, resulting in some views being able to animate correctly and some not.
How would you solve this?
Thanks in advance.
I am trying to create a progress bar that will display while an image is downloading from a server. This image is loaded into a custom view. (I need it to be custom because I draw on the image.)
My solution was to add the custom view into the XML under the layout of the fragment, and mark its visibility as Visibility.GONE. This worked in the XML editor, as the progress bar took up the full space. Invisible did not work as it's position was still displayed.
The issue comes when the image path is given to my custom view. It would seem that setting Visibility.GONE on a view means that the view is not measured. But I need the dimensions of the view to measure how large the bitmap should be.
// Create the observer which updates the UI.
val photoObserver = Observer<String?> { photoPath ->
spinner.visibility = View.GONE
thumbnailFrame.visibility = View.VISIBLE
thumbnailFrame.invalidate()
thumbnailFrame.setImage(photoPath)
Looking at the Logs from the custom view, it is calling onMeasured() but it is doing it too late. I need onMeasure() to be called before setImage(). Is there a better way of handling this and if not is there a way to force the code to wait until I know the view has finished its measuring process?
Solved using a basic listener pattern with an anonymous class inline. I'm not sure if there is a better way but this way works just fine. Delay is not much of an issue since the view draws quite fast anyways.
* Set a listener to notify parent fragment/activity when view has been measured
*/
fun setViewReadyListener(thumbnailHolder: ViewReadyListener) {
holder = thumbnailHolder
}
interface ViewReadyListener {
fun onViewSet()
}
private fun notifyViewReadyListener() {
holder?.onViewSet()
}
spinner.visibility = View.INVISIBLE
thumbnailFrame.visibility = View.VISIBLE
//We have to make sure that the view is finished measuring before we attempt to put in a picture
thumbnailFrame.setViewReadyListener(object : ThumbnailFrame.ViewReadyListener {
override fun onViewSet() {
thumbnailFrame.setImage(photoPath)
//If we have a previous saved state, load it here
val radius = viewModel.thumbnailRadius
val xPosit = viewModel.thumbnailXPosit
val yPosit = viewModel.thumbnailYPosit
if (radius != null) {
thumbnailFrame.setRadius(radius)
}
if (xPosit != null) {
thumbnailFrame.setRadius(xPosit)
}
if (yPosit != null) {
thumbnailFrame.setRadius(yPosit)
}
}
})
}
I have following transition in my app, but the animation to the new destination lags because of extensive loading into recyclerview and other graphic elements.
I have tried to use postDelayed when loading elements on the recyclerview, but on some devices you feel you are waiting after transition and others it take longer time to do the transition (device dependent). So I landed on 700ms, but it is not good for all...
Is there a way (i.e. callbacks) where I can detect when the transition has actually confirmed ended.
My code is farly simple, when clicking a button in one fragment I call this:
private fun onDetailClick(stationId: String, itemView: View)
{
(itemView.context as MainActivity).bottom_navigation.visibility = View.GONE
StationDetailRepository.readStationCurrentPrices2(stationId)
val direction = StationListFragmentDirections.actionStationListFragmentToStationDetailFragment(stationId)
try {
Log.i(TAG,"trns: before navigate")
itemView.findNavController()?.navigate(direction)
Log.i(TAG,"trns: after navigate")
}catch(iae: IllegalArgumentException)
{
iae.printStackTrace()
}
catch (ise: IllegalStateException)
{
ise.printStackTrace()
}
}
Simply, how can I detect that this navigation actually has ended, and there set a viewModel share which I can observe in the other Fragment, so can trigger the loading of elements in a sane manner ?
RG
I have a RecyclerView holding a list of Artists. An artist can be marked as a favorite or not a favorite.
There's a heart to represent this state, and I have a transition drawable, where I'm going from a outlined heart, to a filled in heart based on if the artist in question has been marked as a favorite. The animation itself is working correctly and smoothly transitions between the two when the user clicks on the heart, and looks beautiful.
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border),
ContextCompat.getDrawable(getActivity(), R.drawable.favorite)});
artistViewHolder.image.setImageDrawable(td);
if (artist.isFavorite()) {
td.startTransition(0);
}
In the onClick for the item, I call either .reverseTransition(300) or .startTransition(300) depending on the next state.
I'm running into problems anytime the view is created (1st screen load) and I need to start in the 2nd position (favorite). The initial load you can see it flicker from the empty heart to the filled heart, even though my animation time is set to 0 when it's created. This also happens anytime the list gets invalidated such as if it gets filtered into a smaller set of artists.
I guess since it needs to do things and isn't actually just setting the drawable, 0 isn't actually "instant". I can't find any other way to start in the "end" position though, other than to reverse the starting position of the images in the transition drawable itself.
If I do that though, start/reverse are going to behave differently on a heart by heart basis. At best, I would need to extend the transition drawable, and override the start/reverse/reset methods to take into account which initial state it's in, and that seems kind of hacky?
Am I missing something obvious to start in the end position but not cause that flicker?
Thanks!
You don't migtht remove flicker on td.startTransition(0);
So, you need change order in your Drawables array.
For example
Drawable favorite_border = ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border);
Drawable favorite = ContextCompat.getDrawable(getActivity(), R.drawable.favorite);
TransitionDrawable td = new TransitionDrawable(isFavorite
? new Drawable[]{favorite, favorite_border}
: new Drawable[]{favorite_border, favorite});
artistViewHolder.image.setImageDrawable(td);
This will work provided that you do this in onBindViewHolder
Unless there's a better way to do this, I've gone ahead and extended it, and flipped the start/reverse call based off the initial state.
public class MyTransitionDrawable extends TransitionDrawable{
private boolean initialFavorite = false;
public MyTransitionDrawable(Drawable[] layers) {
super(layers);
}
public MyTransitionDrawable(Drawable[] layers, boolean initialFavorite) {
super(layers);
this.initialFavorite = initialFavorite;
}
public boolean isInitialFavorite() {
return initialFavorite;
}
#Override
public void startTransition(int durationMillis) {
if(!initialFavorite){
super.startTransition(durationMillis);
}else{
super.reverseTransition(durationMillis);
}
}
#Override
public void reverseTransition(int durationMillis) {
if(!initialFavorite){
super.reverseTransition(durationMillis);
}else{
super.startTransition(durationMillis);
}
}
}
`