confused about viewTreeObserver.addOnGlobalLayoutListener usage - android

I am altering a recyclerview and would like to be notified of when that update is finished. I read How to know when the RecyclerView has finished laying down the items? that you are notified in the callback to addOnGlobalLayoutListener, but that is called too many times unless you add the last line.
Here is what I tried:
my_rv.viewTreeObserver.addOnGlobalLayoutListener {
calculate(myAdapter.getItems())
my_rv.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
in order for the last line to compile, my fragment needs to override onGlobalLayout. What am I supposed to put in onGlobalLayout? I never see that callback even called.

It's because in your calculate method you change the layout, like setting text, setting image resource, etc. so in the first call you need to remove the listener to avoid listening to the next GlobalLayout events.
and it is better to remove removeOnGlobalLayoutListener in first line:
my_rv.viewTreeObserver.addOnGlobalLayoutListener {
my_rv.viewTreeObserver.removeOnGlobalLayoutListener(this)
calculate(myAdapter.getItems())
}

Related

Images aren't changing on call

So this my pictures are supposed to change each time the value of limonadeState is being changed. But...they don't.
Can the reason for that be that the images are XML?
Because you're not listening for changes of limonadeState.
assuming you put when statement inside the Activity's onCreate method, and onCreate runs only once in a lifecycle #see https://developer.android.com/guide/components/activities/activity-lifecycle
to achieve your expected behaviour, extract when statement and wrap inside a function and call manually on limonadeState state changes.

Need to update an item in a recyclerview if it has been on screen for set amount of time

Using Kotlin, I have a recyclerview with an adapter that displays an Arraylist. What I need to do is set a Object.viewed property if it has been on screen for 2 seconds. Meaning if you can just scrolling quickly through the list nothing happens but if you stop and on one to read it then I need to make it as viewed, so I can increment a seen counter.
Thanks for any help or pointers.
You can take a handler with delay of 2 seconds & make your logic of Viewed in it. So, after 2 seconds handler will execute your code like below :
//Code inside bindViewHolder() method :
handler = new Handler(); // make it globally
handler.postDelayed(runnable, 2000); // make a runnable with your logic code of viewed.
Now override a method called onViewDetachedFromWindow(VH holder) and remove your handle callback here. so if your view get's detached before 2000 seconds, code won't be executed.
Jeel Vankhede approach would work but slightly heavy for my taste. I would suggest to create a map ids to currentTime, add element there inside onViewAttachedToWindow and inside onViewDetachedFromWindow check if 2 seconds has passed. (Don't forget to remove id from map inside onViewDetachedFromWindow regardless)
Try using countdowntimer in the OnResume function.
Object:Countdowntimer(2000,1000)........

Run a function after the view (and subviews) finish loading

How do run a function after the view and its subviews have finished loading like in iOS (viewDidAppear / viewDidLayoutSubviews)
I basically want to run a UI animation after the program loads but as my programs too heavy(and has many subviews) the animation doesn't run at all(I tried handler.postDelayed and it worked fine but I wanted to know the more efficient way to do this(for performance reason as a phone I tested on is quite old and takes 4s to load while another takes just a second, etc.).
I think you can use the ViewTreeObserver for that as stated here.
You will have something like:
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// do something here but make sure you unregister if you just want to get notified only once..
}
});
The developers page is stating the following: Register a callback to be invoked when the global layout state or the visibility of views within the view tree changes
You can then add this listener on the root view of your activity for example in onCreate and inside that method you can execute your animation.
The method gets called whenever the layout state or the visibility of the view gets changed. It is not like viewDidAppear but it is something similar to "viewDidLayoutSubviews".
You can also try to run the animation in onResume but that will run the animation whenever the activity resumes not only the first time you create it and I am not sure if you want or not that.

Invalidate is failing to call onDraw when returning to an activity that has a custom view

I have inherited some code hence I don't have true freedom to change it. :(
I have a main activity, from which other activities (I will refer to these as sub activities from now on) are called. Whenever one of these completes, it calls finish and returns data to the main activity.
Each activity (including the main one) has a bar on the top that displays a custom view. The custom view contains a canvas which has a drawing that is dependant upon the state of the network.. i.e. wifi/mobile etc...
Since that 'state' data never changes, it's held within a singleton and the view gets data from the singleton to define what it draws. That is working with no issues, i.e. the data is always as I expect it.
When I first launch the MainActivity, as the network changes, the data changes and each call to 'invalidate' the view receives a system call to 'onDraw' as I would expect.
In each of the sub activities the same is again true.
Upon finishing a sub activity and returning to the mainActivity, calls to invalidate no longer cause a call to onDraw to occur.
I have looked at this for quite a while now and just cannot figure out what is going wrong.
In my constructor I have:
setWillNotDraw(false);
Whenever the data changes the following methods are called:
invalidate();
requestLayout();
Now, there's one more thing... upon returning to the activity at that immediate point, I refresh and this DOES draw correctly, i.e. invalidate does trigger an onDraw call... any subsequent network changes (which are propogated) fail to result in the onDraw call.
I'm wondering if this is to do with the view somehow being detached. I can see that 'onDetachedFromWindow' is called, however the trigger for this is the destruction of the subactivity, hence I don't see why that should affect the MainActivity but it's the only thing I can think of.
I'm hoping I've provided enough information for someone to help me...
Well, in the end my answer has very little to do with the question and I guess this is an example of how an issue can be solved by going back to absolute basics and checking for the obvious.
My activities all inherit from an abstract activity. Within that activity there is an instance of the view. The views in which I was having trouble were using that declaration as opposed to having their own instance, hence behaviour from one activity was then affecting another inadvertently.
So, if I'd been able to post up all the code, I'm sure someone else would have spotted this but, unfortunately I couldn't in this instance.
Still, whilst this posting doesn't provide a resolution that will help others, maybe it does say... step back and check the obvious first!

How do I get adapter to update views after data changes when using fragments?

I have a list of orders in one fragment. In a second fragment, I display the detail of the order, and I use a third fragment to display the buttons that change the status of the order.
In the list, each order is displayed with a background color that indicates its status, for example green for a completed delivery.
When in landscape mode, both the detail and list are shown. In portrait mode I use two separate activities.
This all works fine, up until I change the status of an order. I can't find a way to get the list to update.
As I understand it, what needs to happen is the adapter needs to have its notifyDateChanged() method called. I've tried calling it directly from the method that processes the button click, I've tried an asynctask, and I've tried a handler. My debug methods show that the call is happening, but the list doesn't get updated.
It's possible I'm doing something completely bone-headed, but I've double and triple checked things. I suspect there is some key element I don't understand. I hope someone else does and will tell me what I'm missing.
I had some code posted, but it was clearly wrong. Not sure what code to post, since I think this is more a conceptual than coding issue.
If you want to update a view, try to use :
Thread + Handler
AsyncTask
What I discovered, through the helpful comments of other posters, is that my problem wasn't that the adapter wasn't getting the notifyDataChanged() call on the correct thread. I put in debug code that proved that. The problem was that the background of the listview item was based on a change in the underlying data itself, so the real answer was to get the Adapter to refresh the Cursor. I made some changes. I modified the AsyncTask I was using to notify the adapter. Now the AsyncTask gets a new cursor and calls adapter.changeCursor(cursor) with the result. The AsyncTask is called from the MainActivity (which hosts both the list and detail fragments) when the status is changed. It's also called in the onResume() portion of the ListFragment code, so the list will be updated properly when coming back from the detail fragment. Works great.

Categories

Resources