I have a messaging application. I can get messages and list from mysql database via json. When i get messages (for example last 10 messages and newer is at the bottom) first record is shown at the top, so user have to scroll down to see last message. I want to focus to last message and when user scrolls to top, I want to put there a Load Previous Messages button. I found Load More buttoni but its at the bottom of page. How can I do that?
My codes are these:
// Hashmap for ListView
categoryList = new ArrayList<HashMap<String, String>>();
// get listview
ListView lv = getListView();
Button btnLoadMore = new Button(this);
btnLoadMore.setText("Load older messages");
FrameLayout footerLayout = (FrameLayout) getLayoutInflater().inflate(R.layout.listfooter,null);
lv.addFooterView(btnLoadMore);
As I said in comments, simply change addFooterView() to addHeaderView() method which give you the Load More at the top of the list. This method use three parameters (at least one: View from reference) which are:
View v: the Load More view to add at the top
Object data: data associated to the view
boolean isSelectable: value to make the view selectable or not
Using these three params instead of only the view may allow you to prevent the color state on it by using android:listSelector attribute. Indeed, sometimes you want to prevent a background state on a header/footer view. That being says, the method might be:
lv.addHeaderView(headerLayout, null, false); // this isn't clickable now
Note: now, you can call the view variable headerLayout instead of footerLayout ;)
As I understand your requirements, HeaderView should have the Load More Button into it, to avoid to create it dynamically, as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load More"
android:onClick="loadMoreDatas" />
</LinearLayout>
Now, you have the right Button at the top of your list and you can add a method to handle the click event into the Activity (refer to android:onClick attribute) as:
public void loadMoreDatas(View v) {
// load more messages
}
Finally, to focus to a specific item, in your case the last item in the list (at the bottom), you should use setSelection(int position) which go to the index selected in its parameter. Then, after setting the Adapter, call this on the ListView as:
// set the adapter
setListAdapter(adapter);
// go to selection (last item)
lv.setSelection(adapter.getCount() - 1);
The getCount() method use (normally) your ArrayList size. Then, you have to prevent an IndexOutBoundsException because your array begins with position 0 and not 1. So, the last position is "All Items less First Position (0)".
However, the perfect method to begin at the bottom of the list is setStackFromBottom():
When stack from bottom is set to true, the list fills its content starting from the bottom of the view.
Then, it might be better to have:
// start from bottom
lv.setStackFromBottom(true);
This should do the trick and I hope this will be usefull.
Related
I have an app that's getting information from an API request and then displaying a list of devices. After several hours of combing through documentation, I cannot figure out how to format the View that is created from the ArrayAdapter. Essentially, if the device has an error, I want to display a red circle to the right of the button and display a green button if there is no error.
deviceList is the name of a ListView that I am trying to display my list of buttons inside of. deviceNames is an array of strings that contains the names of the devices.
The TextViews that are created are also clickable, which is what the onItemClickListener is handling. This section works, but I wanted to leave it in because I do need the buttons to start an activity that displays device-specific information.
Ideally I would like to essentially create a template that I can just change the values of the text and the color of the indicator for
Below is my code:
// List of device names
val listView: ListView = findViewById(R.id.deviceList)
val arrayAdapter1: ArrayAdapter<*>
arrayAdapter1 = ArrayAdapter(
this#Homepage,
R.layout.device_button,
deviceNames
)
listView.setAdapter(arrayAdapter1)
listView.onItemClickListener =
AdapterView.OnItemClickListener { parent, view, position, id ->
val pos = position
println(pos)
val device = jsonArray.getJSONObject(pos)
val ID = device.get("id") as String
println(ID)
goToDeviceDetail(ID)
}
Below is the XML file for device_button. I tried to add formatting here and essentially create a template for a button that would allow me to change the text and the color of the indicator, but it got mad that it wasn't just a TextView.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:gravity="center_vertical"
android:textColor="#25383C"
/>
Below is the button that I would like for it to look like. I'm likely going to just make the background a solid color rather than the image that is in the below picture:
I would say the biggest problem is your using a simple API for a more complex problem. It is entirely possible do it with a ListView and ArrayAdapter. But I would highly recommend looking into RecyclerView/RecyclerView.Adapter
The way it works out is...
RecyclerView.Adapter binds your list of data ie Devices to the individual RecyclerView.ViewHolder
The ViewHolder would inflate your xml layout that contains the button. You then have access to all View contained in that layout easily.
You then can put listeners on the button.
The Adapter then can be setup to receive new data, when received it can rebind the data that has changed.
Say the user clicks one of the device buttons it does a task. When it gets back it will say hey Adapter I have a new List for you.(The list now contains the "fixed" device).
ViewModel(contains observable data)->Fragment/Activity(Observers the data)->Adapter(Receives the data)->ViewHolder(Displays the data)->Activity("Fixes the data")->ViewModel->...loops
Here is a very good example.
https://medium.com/#atifmukhtar/recycler-view-with-mvvm-livedata-a1fd062d2280
If you really want to keep using the ListView and ArrayAdapter you are receiving the clicked view here.
OnItemClickListener {
/*Parent of the view*/ parent,
/*The view clicked*/ view,
/*position of data*/position,
/*id of the view clicked*/ id
->{
view.findById(R.id.text_view);
//onClick
}
}
With that you know what has been clicked so you know what has to be changed later when you get back from your other Activity.
I have a dialog containing a list of notes, and I want to make the user scroll to the end of the list to find the "OK" button to close the dialog.
The dialog contains a ListView and I add the button to the list using the footer row.
View footerView = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).
inflate(R.layout.list_footer_button, null, false);
listView.addFooterView(footerView);
When i turn on talk back and open this dialog, it reads the wrong count for the ListView as it is including the footer row. For example if my list has 3 items, talkback reads "Item 1 of 4".
ISSUE
How can i get talkBack to read the correct number of items ignoring the footer row?
OR
If i can't change this, how else do i create a dialog where the user has to scroll to the end of the list before seeing the button to dismiss the dialog.
I have resolved this issue by adding the button to the View for the last row rather than adding a footer.
My layout for each row contains a textView and a button, and the button is set to be "invisible".
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout android:id="#+id/notes_details"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- other views here but removed for this post -->
<TextView
android:id="#+id/notes_text"
style="#style/notes_text_style"/>
</LinearLayout>
<Button
android:id="#+id/notes_button"
style="#style/notes_button"
android:layout_below="#id/notes_details"
android:layout_centerHorizontal="true"
android:visibility="invisible" />
</RelativeLayout>
In the adapter for the list, I show/hide the button based on whether the row is the last row
boolean isLastRow = iPosition == getCount()-1;
okButton.setVisibility(isLastRow ? View.VISIBLE : View.GONE);
Accessibility changes needed
There was additional work to make this all work for Accessibility purposes. If no changes were made to support accessibility, only the rows are navigatable. Therefore the last row will select the notes text and OK button as one selectable item and you would not be able to navigate to the button using a keyboard/trackerball.
Firstly you have to set the list so that items in a row are focusable
listView.setItemsCanFocus(true);
Secondly, in code in the adapter, i set the focusable/nextFocusXXX on the notes textView and button accordingly
// when in accessibility mode, we want to set the navigation order for
// the list item's text and the OK button
boolean isLastRow = iPosition == getCount()-1;
if (isAccessibilityEnabled) {
notesTextView.setFocusable(true);
okButton.setFocusable(true);
rowView.setNextFocusForwardId(isLastRow ? R.id.notes_button: R.id.notes_text);
rowView.setNextFocusDownId(isLastRow ? R.id.notes_button: R.id.notes_text);
okButton.setNextFocusUpId(R.id.notes_text);
}
I had another usecase with this issue.
Requirements
I have a screen containing a list of items and I have a Floating Action Button (FAB) on the bottom right of the screen allowing the user to add items to the list. On the right hand side of each row is a button presenting options for that row item.
I need the FAB to not overlay the last row in the list, otherwise I am never able to click on the right hand button of the last row as it will be under the FAB.
Solutions and their issues
I tried several solutions. One was adding an extra count to the list items and when getting the view for this last row I return an empty view, the height of the FAB. I also tried adding this empty padding row as the list footer. In both cases TalkBack reads one too many for the number of items in the list.
I can not use the solution I have posted for this question for my list of notes. I need the FAB to always be in the bottom right hand side of the screen (not just after the last row) even if there are only 1 or 2 items in the list or over a screen-full of items.
Working Solution
Adding the last 4 attributes in the code below to my list definition in the xml layout seems to solve the issue. There are no additional items added to the list so TalkBack reads the list count correctly and the following ensures the FAB in the bottom left of the screen never overlaps the last row.
<!-- Instead of adding a footer row to stay clear of the FAB,
add a padding to the listView and set clipToPadding to false
android:overScrollFooter is set to transparent to avoid having a divider
for the last item so there is just white space below the last item -->
<ListView
style="#style/my_list_style"
android:layout_alignParentTop="true"
android:paddingBottom="82dp"
android:clipToPadding="false"
android:footerDividersEnabled="false"
android:overScrollFooter="#android:color/transparent"/>
I have a ListView. My point is, after selecting one of the elements on that view, I want to show a menubar Right below that element. What Im tryin to do is pretty much something like the app Any.Do.
Heres a picture of what I intent to do.
How can I do it ? Id rather not use GreenDroid.
To fill the array I use this code snniped:
TaskBoard.class
setListAdapter(new SimpleAdapter(this, array_tasks, R.layout.dash_tasks,
new String[] { "task", "project" , "date"}, new int[] { R.id.dashtask,
R.id.taskproject, R.id.final_date}));
R.layout.dash_tasks is the view im showing to the user.
This is a code snipped to call the menubar I want to show:
dash_tasks.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/menu_bar"
style="#style/page"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<include layout="#layout/menubar"></include>
</RelativeLayout>
EDITED:
Ive figured out how can I find the position Im clicking. The problem is, its a relative position. I mean, if I click in the 2nd item on the array, the menubar will apear in the 2nd position every 7 items (thts the number of items that fill a page). Im guessin this position is acording to the screen and not the ListView..
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
View x= parent.getChildAt(position);
View a= x.findViewById(R.id.mini_menu_bar);
a.setVisibility(View.VISIBLE);
I hope this helps you understand the problem
What you do is that in your list items layout xml you also have the menu (wrapped in a vertical linearlayout) but set it to android:visibility="gone". When the item is pressed you just show that hidden menu view.
It depends on how many view you wanna display at the same time. If there is only one, and you want it to sorta stand out without much of programming I would go for QuickAction. If you have multiple views at the same time expanded, my suggestion would be to use RelativeLayout and BeLow. What you need to do is fairly simple. Make sure there is a way for your to find the item below the tapped on, add the new view below the tapped item and add the change the item bellow tapped item to be below the new item. Something like the code below
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW, view.getId());
Another solution would be to use LinearLayout but requires a loop to find the index of the item tapped on and adding the new item below it:
parent.addView(subItem, index+1);
I have a row for a ListView defined as :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/menutext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="24sp"
android:layout_alignParentLeft="true"/>
<ImageView
android:id="#+id/icon"
android:layout_width="40dip"
android:layout_height="40dip"
android:src="#drawable/lock"
android:layout_gravity="right"
android:layout_alignParentRight="true"/>
</LinearLayout>
My adapter for the ListView is set in onCreate()
listView.setAdapter(new ArrayAdapter<String>(this, R.layout.send_menu_row, R.id.menutext, items));
I have 5 rows with a text and image on each row. In onResume(), I want to make the first row's ImageView invisible.
#Override
protected void onResume()
{
super.onResume();
LinearLayout linLayout = (LinearLayout) listView.getAdapter().getView(0, null, null);
ImageView v = (ImageView) linLayout.getChildAt(1);
v.setVisibility(View.INVISIBLE);
}
But it doesn't change the visibility. Can someone help me on this?
Probably what is happening is that you are not doing that in the right method.
Try to switch the device orientation from horizontal to vertical (or vice-versa). This should trigger onResume method to be invoked and it could work.
Anyway, hiding an image shouldn't be done that way. Perhaps you should use an empty image or override the getView method (in the adapter).
UPDATE - why do I say you shoudn't use this method to do that
The thing is adapter.getView is used to get a view that will be drawn. The OS calls this method when he needs to draw that item on the screen.
This method could be overriden by the developer to draw custom/complex views but it should be used (as in, invoked) exclusively by the system.
For example, when we are talking about long lists, if you scroll, you'll have the getView method invoked and it will receive a view to be reused (which is a lot more efficient). This means that if you hard-core that the first view will be invisible, when you scroll and the first view is reused to display the 20th item (for example), now the 20th item would be invisible because probably you would just update the label and image source.
Note:
When I say first view, I'm referring to the view where the first item is initially drawn. Later, the view that was used to accommodate the first item is going to be reused to accommodate another item.
What is happening:
I think I got it now. What I believe to be happening is the following:
When the activity is initially drawn, you'll have the getView method invoked 5 times (one for each item that you are displaying). Each time the OS collects the view returned and adds it to the listview.
Latter, you'll call getView by yourself. As you pass no view to be reused, the method will create another view and return it. What is different this time? You are not adding this view to the listview. (Also, this isn't what you what to do.)
what you wanted to do is get the view that was used to draw the first item. But in this case you are just getting another view that could be used to draw the first item.
The solution is overriding getView or using a transparent image (easier).
Here's a link for the first result on google:
http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/
How about override getView and then do setVisibility when you return the first row in getView()?
It's not a great idea to be modifying rows outside of the list adapter. Since everytime the user scrolls you will lose the changes.
A workaround maybe ?
Try making a transparent image (png) and put that in as first item :-)
I have a ListView. The data behind it is fetched from the Internet, in sets of 10-30 items whenever the user scrolls all the way to the bottom. In order to indicate that it is loading more items, I used addFooterView() to add a simple view that displays a "Loading..." message and a spinner. Now, when I'm out of data (no more data to fetch), I want to hide that message. I tried to do:
loadingView.setVisibility(View.GONE);
Unfortunately, while that does hide the view, it leaves space for it. I.e. I end up with a big blank space where the "Loading" message used to be. How can I go about properly hiding this view?
I can't use removeFooterView() because I may need to show it again, in which case I can't call addFooterView() again because an adapter has already been set on the ListView, and you can't call addHeaderView() / addFooterView() after setting an adapter.
It seems that you are allowed to call addHeaderView() / addFooterView() after setAdapter() as long as you call one of those methods at least once before. That is a rather poor design decision from Google, so I filed an issue. Combine this with removeFooterView() and you have my solution.
+1 for the other two answers I got, they're valid (and arguably more correct) solutions. Mine, however, is the simplest, and I like simplicity, so I'll mark my own answer as accepted.
Try setting the footer's height to 0px or 1px before hiding it. Alternatively, wrap the footer view in a wrap_content height FrameLayout and hide/show the inner view, leaving the FrameLayout visible; the height should wrap properly then.
in my case addFooterView / removeFooterView() cause some artefacts.
And I found other solution. I used FrameLayout as FooterView. And when I want to add Footer I called mFrameFooter.addView(myFooter); and mFrameFooter.removeAllViews(); for remove.
FrameLayout frameLayout = new FrameLayout(this);
listView.addFooterView(frameLayout);
......
......
//For adding footerView
frameLayout.removeAllViews();
frameLayout.addView(mFooterView);
//For hide FooterView
frameLayout.removeAllViews();
The Droid-Fu library has a class designed for having a loading footer show and hide: ListAdapterWithProgress.
Works well in my project:
1.Add footer view first
mListView.addFooterView(mFooterView);
mListView.setAdapter(mAdapter);
2.Set visibility
mFooterView.setVisibility(View.GONE);
mFooterView.setPadding(0, 0, 0, 0);
3.Set invisibility
mFooterView.setVisibility(View.GONE);
mFooterView.setPadding(0, -1*mFooterView.getHeight(), 0, 0);
As #YoniSamlan pointed out, it can be achieved in a simple way. You have to specify
android:layout_height="wrap_content"
in the ViewGroup that contains the "Load More" button. Doesn't have to be FrameLayout, see below for a simple -working- example that uses a LinearLayout.
Both images show a screen that is scrolled all the way to the bottom. First one has a visible footer that wraps around the "load more" button. Second images shows that the footer collapses if you set button's visibility to GONE.
You can show again the footer (inside some callback) by changing the visibility:
loadMore.setVisibility(View.VISIBLE); // set to View.GONE to hide it again
Perform listView initialization as usual
// Find View, set empty View if needed
mListView = (ListView) root.findViewById(R.id.reservations_search_results);
mListView.setEmptyView(root.findViewById(R.id.search_reservations_list_empty));
// Instantiate footerView using a LayoutInflater and add to listView
footerView = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.load_more_footer_view, null, false);
// additionally, find the "load more button" inside the footer view
loadMore = footerView.findViewById(R.id.load_more);
loadMore.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fetchData();
}
});
// add footer view to the list
mListView.addFooterView(footerView);
// after we're done setting the footerView, we can setAdapter
adapter = new ReservationsArrayAdapter(getActivity(), R.layout.list_item_reservations_search, reservationsList);
mListView.setAdapter(adapter);
load_more_footer_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="#+id/load_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="9dp"
android:gravity="center"
android:layout_gravity="center"
android:background="#drawable/transparent_white_border"
android:textColor="#android:color/white"
android:text="#string/LOAD_MORE"/>
It should be a bug of Android.
You don't need to remove or add footer view dynamically. You just need to create an unspecified height parent Layout (either inflate it from an xml file or create it programatically) and then add your view which you want to hide or show into it.
And you can set the view, but NOT the parent Layout, to VISIBLE or GONE or something else now. It works for me.
Used
footer.removeAllViews();
This does not remove footer but flushes children.
You again have to repopulate children. Can check by
footer.getChildCount()<2
I also found that is possible call onContentChanged() (if you use ListActivity) to force recreate ListView if I need add HeaderView to them after setAdapter() call, but it is very ugly hack.
I have created a ListView that handles this. It also has an option to use the EndlessScrollListener I've created to handle endless listviews, that loads data until there's no more data to load.
You can see these classes here:
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java
- Helper to make it possible to use the features without extending from SimpleListViewWithLoadingIndicator.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java
- Listener that starts loading data when the user is about to reach the bottom of the ListView.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java
- The EndlessListView. You can use this class directly or extend from it.
I have small hack way to resolve this problem for everywhere.
Put listview and footer view (just sub layout) in parent layout like LinnearLayout, remember that footerview below listview.
Controller this footer view gone and visibility like nomal view. And done!
first I am adding my footer to the listview,like this
listView.addFooterView(Utils.b);
Then on button click , I remove the view,
listView.removeFooterView(Utils.b);
I am adding the footer everytime when I am hitting the async,and theus the're no duplicate entry.I could aslo check for the count and so it like this,
if(listView.getFooterViewsCount() > 0){//if footer is added already do something}
When you want to remove the footer in ListView just call
listView.addFooterView(new View(yourContext));
It will add a dummy empty view which will not reserve any space