I have been thinking about adding a button to a View which when pressed adds another view on the bottom. It would resemble the Clock application that comes preinstalled in which you push a view in form of an alarm which is then added to a list.
Does anyone have any experience with how to implement that?
Have a look at the documentation for ListView, particularly the setAdapter() method. You'll want to have some sort of backing collection most likely as the adapter, such as an ArrayAdapter, to which you can add new items, and have them populate into the list.
Use a listview, populate it.
Then when you press the button, add something to your data, then:
public void notifyDataSetChanged ()
Since: API Level 1 Notifies the attached observers that the underlying
data has been changed and any View reflecting the data set should
refresh itself.
Related
I'm making a file explorer synchronizing a directory in ftp, I would like that when a new item is downloaded it is displayed first in my recyclerview. For that I use sharedpreferences to store the new files and so I display or not a dot next to it to notify that it is new. Then I want to place it first at the begining of my MutableList, everything goes well except when I have to update with notifyItemMoved() which gives me the following error:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView{e8a722f VFED..... ......ID 0,60-1536,1098 #7f080104 app:id/main_folder_recycle_view}, adapter:fr.frd.calvezdocs.adapter.DirAdapter#f17203c, layout:androidx.recyclerview.widget.GridLayoutManager#31331c5, context:fr.frd.calvezdocs.MainActivity#5181ba0
Here is the code in my onBindViewHolder() :
val isRead: String? = prefNewDir.getString(currentDir.absolutePath, null)
if (isRead!=null) {
holder.DirIsRead.visibility = View.VISIBLE
println("before : " + items[0])
items.remove(currentDir)
items.add(0,currentDir)
println("after : "+items[0])
// Can't update the recyclerview
notifyDataSetChanged()
} else
holder.DirIsRead.visibility = View.GONE
Does anyone have an idea?
Thanks
onBindViewHolder is the method the adapter calls when it's displaying the list, and it's passing you a ViewHolder so you can update it to display a particular item in the list.
It's not the place to be doing stuff like calculating that the list has changed, and requesting a new layout, which is why you're getting an exception. You also definitely shouldn't be changing the items dataset while the RecyclerView is trying to display part of it!
Really, your adapter should have some kind of setData function, where you pass in updated data, and it can do stuff like working out what's moved, sorting the list for display, keeping a list of what's new and needs a highlight dot, etc. And that function can call notifyDataSetChanged(), or one of the more specific (and better) update functions as appropriate.
When you get new data (e.g. when you're storing a new file in the SharedPreferences) that's when you should be pushing an update to the adapter, so it can do what it needs to do, and refresh if necessary. You can even make it a simple update() call that passes nothing, and the adapter does all the SharedPreferences lookup you're currently doing in onBindViewHolder, if you want. And it'll be a lot more efficient than doing these lookups every time an item in the list is drawn
(I'd really recommend against what you're doing with SharedPreferences too, but maybe it complicates things less than I'm imagining. I'd still recommend trying to make your adapter work with a basic list internally, though - pass one in, so it doesn't need to know how the rest of the app is persisting data)
As far as I can tell, Views only receive DragEvents if they had implemented onDragEvent() or had set an OnDragListener before startDrag() (or startDragAndDrop() for API 24+) is called. They will then continue to receive additional drag events if they return true for DragEvent.ACTION_DRAG_STARTED.
However, I am looking for a way to receive DragEvents after a drag operation had already started, for Views that got added to the layout during the drag operation.
To illustrate why, my situation is roughly the following:
I have a ViewPager2 with ListView fragments whose list items can be dragged. If I drag an item over another item, I "enter" that item and a new ListView fragment is shown with new child items. This works fine.
However, since these new child items didn't exists at the time of starting the drag operation, they don't receive DragEvents when I continue to drag the item over those new items.
So, basically, I want to be able to enter multiple levels deep in one continuous drag operation.
Is it possible to have newly added Views receive DragEvents for an ongoing drag operation?
Okay, I've managed to solve it by re-dispatching the original DragEvent (the one with action DragEvent.ACTION_DRAG_STARTED) to my main component, which is an instance of ViewGroup.
On inspecting the source code for ViewGroup.dispatchDragEvent() I found that ViewGroup has a member variable called mChildrenInterestedInDrag that gets filled with children interested in drag events, when DragEvent.ACTION_DRAG_STARTED is received.
So when I called ViewGroup.dispatchDragEvent() with the original DragEvent, after I entered an item in the list to view its child items, those new ListView child items were now responding to additional DragEvents.
I'm a bit worried that this approach might yield unexpected side effects. If I come across such effects upon further testing, I'll update this answer. But for now it will do.
If somebody knows that this indeed might yield unexpected side effects and/or has a better solution I'd love to hear them.
Addendum:
Upon storing the original DragEvent for re-dispatching, I had to "clone" the event, because, while it worked properly in an API 19 emulator, on an API 19 phone the original DragEvent's action were continuously altered during dragging, so its action wouldn't reflect DragEvent.ACTION_DRAG_STARTED anymore and re-dispatching didn't have the desired effect of registering newly added Views as interested in the ongoing drag operation.
Since DragEvents cannot be cloned or constructed, I had to "clone" it with the help of a Parcel:
void storeStartEvent(DragEvent startEvent) {
Parcel parcel = Parcel.obtain();
startEvent.writeToParcel(parcel, 0);
// important to "rewind" the Parcel first
parcel.setDataPosition(0);
this.startEvent = DragEvent.CREATOR.createFromParcel(parcel);
}
Other than that, I didn't experience any noticeable problems yet, so it may have just solved my issue nicely.
I have a custom listview defined in my xml layout file. I can add items to this ListView inside onCreate method, through an array adapter.
However when I add items from another content view and then go back to the content view with the ListView all the items are gone and there's nothing listed. Even after calling .notifyDataSetChanged();
It seems like I can only add to the list when the content view containing the ListView is currently being displayed. Is this the default behavior?
Failed attempted workaround
I used another array to keep the newly added items and then try to add them when the ListView became visible again. I had to override onContentChanged() to do so but then no items were added still.
So the main question is
How can I dynamically add items to the ListView even if it's out of sight and still preserve the old items?
PS: I have to say the Android API is one of the worst I've ever come across.
If you change content view, then all the previous views are going to be destroyed. Are you using an adapter? If so, then it would be very easy to add all the items to the list again.
There shouldn't be any reason to setContentView any time other than in onCreate.
If you were looking to have multiple screens, instead of changing content view, then start a new activity.
dynamically add items:
//add at the top of the list
mListView.addHeaderView(itemView);
// add at the bottom of the list
mListView.addFooterView(itemView);
I have a few ListViews driven by custom Adapters (ArrayAdapter and CursorAdapter). In the getView() and bindView() functions of the adapters, I'm creating a row in the ListView and setting a Listener to a particular UI element in the row.
Currently, as the ListView rows are sent to the Recycler the listener is still hooked up so the memory is never released. I don't see a callback for when the row is being disposed of that would give me an opportunity to detach the listener before the row is recycled.
How do I manage the listener in the ListView rows? I only have the getView()/bindView() to add the listener to the row, but nothing to remove them.
Thanks
Android OS does the ListView row view's recycling for you. The views get generated on the fly when user scrolls the list and get destroyed when they go out of view. Android OS destructs the views whenever other application require more memory and some of your objects are not being used, Sort of garbage collection.
And as to your question regarding removing listeners, they get removed automatically since the object they were point to doesn't exist anymore and they get marked as garbage objects too.
After some playing around I decided to use a ViewHolder pattern, and within that I have a variable that tracks the listener. When the recycled view is provided (where applicable) I pull out the listener (it does still exist) and make sure to unregister it as a listener, before creating a new listener and registering that in its place. This probably doesn't catch EVERY listener, but I'm OK with a few ListView rows being stuck in memory.
I am having a bit of difficulty implementing the android-pulltorefresh widget by Johan Nilsson found at https://github.com/johannilsson/android-pulltorefresh
The problem I am having is after putting the custom listview into my application everything is fine it, but it asks to Tap to Refresh the list view but I need it to be set to pull down to refresh.
The code I am using below is pretty much from the github page and a screenshot of the app can be found below do demonstrate my issue:
PullToRefreshListView lv = (PullToRefreshListView)findViewById(R.id.listView);
lv.setOnRefreshListener(new OnRefreshListener() {
public void onRefresh() {
// Do work to refresh the list here.
GetData getData = new GetData();
getData.execute();
}
I need the Tap to refresh header gone and only to be shown once the listview has been dragged down. I get the feeling I just need to change some sort of flag but I can't find where this would be.
Unfortunately there is no way to work around this. The entire control is built around the idea that the "Pull to Refresh" header is a normal listview item that gets hidden by scrolling the list upward. Unfortunately, when you have a very short list, the list cannot be scrolled upward to hide the first item cause there are not enough items in the list -- so the fallback is to show the first item (the header) as well and have it display "Tap to Refresh".
EDIT: One kludge you may be able to do is insert dummy blank items so the list has enough items to hide the top header list item.
use this code
Hope it works for you.