Keeping the last visible item of a ListView when the size of the ListView changes
Asked Answered
T

11

33

What I want is simple, well at least I thought it would be simple. I just want a window where an EditText is on the bottom of the screen, and the rest of the space is filled with ListView. Unfortunately, it did not work as I expected. What I want is the following image. Is there any easy way to do this in the XML, or should I write some special code for this? What I want

My Problematic Android Source Code.

<?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="fill_parent"
android:orientation="vertical" >
<ListView
    android:id="@+id/demolist"
    android:layout_width="match_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
     >
</ListView>
    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:layout_weight="0">
    </EditText>
</LinearLayout >
Telmatelo answered 19/2, 2012 at 17:43 Comment(1)
Did you find a way to do this? I'm looking for exactly the same behaviour.Weese
C
20

What you Want

Last Visible Item Without Keyboard

Last Visible Item Without Keyboard

Last Visible Item With Keyboard

Last Visible Item With Keyboard

Last Visible Item With larger input

Last Visible Item With larger input

Here is how i did it.

Summary

  • Set android:stackFromBottom="true" and android:transcriptMode="alwaysScroll" in your list view
  • Set android:windowSoftInputMode="adjustPan" in Activity
  • Call adapter.notifyDataSetChanged(); when you add new chats so that the list view will always scroll to the last position
  • Set android:inputType="textMultiLine" & android:maxLines="4" in EditText for growing text box

Details

Activity

<activity android:name="MainActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan"></activity>

Chat View

<RelativeLayout
    android:id="@+id/chat_header"
    android:layout_width="fill_parent"
    android:layout_height="40dp"
    android:padding="3dp" >

    <Button
        android:id="@+id/btnBackChat"
        android:layout_width="35dp"
        android:layout_height="35dp"
        android:layout_alignParentLeft="true"
        android:background="@drawable/back_button" />

    <TextView
        android:id="@+id/txt_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/btnBackChat"
        android:layout_centerHorizontal="true"
        android:textColor="@android:color/white"
        android:textSize="18sp" />

    <ImageView
        android:id="@+id/imgViewProfileChat"
        android:layout_width="35dp"
        android:layout_height="35dp"
        android:layout_alignParentRight="true"
        />
</RelativeLayout>

<ListView
    android:id="@+id/listView1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@+id/form"
    android:layout_below="@+id/chat_header"
    android:stackFromBottom="true"
    android:transcriptMode="alwaysScroll"/>

<RelativeLayout
    android:id="@+id/form"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"
    android:orientation="vertical" >

    <ImageButton
        android:id="@+id/btnAttach"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:contentDescription="@string/attach"
        android:src="@drawable/attach_icon" />

    <EditText
        android:id="@+id/txtChat"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@+id/btnSend"
        android:layout_toRightOf="@+id/btnAttach"
        android:inputType="textMultiLine"
        android:maxLines="4" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="@string/send" />
</RelativeLayout>

Celeriac answered 26/4, 2013 at 7:28 Comment(2)
As I already mentioned in another comment, I'd prefer to avoid using android:windowSoftInputMode="adjustPan". Also, doesn't your solution always scroll to the bottom of the list? I want whatever item is at the bottom of the list view window to stay at the bottom of the window when the window shrinks, NOT for the list to stay scrolled to the end of the list. (Note, my application is not a chat application like the OP's -- I want the new item to be inserted where the user is looking at in the list, not at the bottom.)Weese
You can use android:windowSoftInputMode="adjustResize" also with this solution. Then the actionbar won't disappear.Velvavelvet
B
7

I think you may want to use AdapterView.getLastVisiblePosition() before displaying the keyboard.

Two solutions here:

  • on EditText click
  • thanks to an OnScrollListener (and its onScroll function) listening to your ListView

then use the AdapterView.setSelection to jump to this item (or index - 2 or index - 3) after EditText focus.

Bellhop answered 22/4, 2013 at 16:14 Comment(3)
Awarded the bounty for being the only person to understand the question. I had already tried using the OnScrollListener, and using OnLayoutChangeListener to detect the appearance of the keyboard. I have had another go, and I'm still unhappy with the results. SetSelection() and smoothScrollToPosition(), etc. seem unreliable :-( I think I may have to give up on this UI altogether.Weese
Also do not forget to request focus as well. lstMessages.requestFocus(); then clear it and request other elements focus if neededFlyover
You give me the right clue to achieve this and keep adjustResize, thank you. I juste made few changes by using setSelectionFromTop() instead of setSelection() to display the last item just above the edittext.Eupepsia
C
3

You can use android:windowSoftInputMode="adjustPan" in your AndroidManifest.xml as below

 <activity android:name="com.example.SimpleListView"
            android:label="@string/app_name"
            android:windowSoftInputMode="adjustPan" >

This should produce the output you want. Official android documentation .

Optionally you can specify transcript mode(if you are working on some chat feature) for your list view if you want to scroll automatically to the end of listview. You can check the android:transcriptMode="normal" as well and see if that fits.

A related SO answer. Official android doc.

<ListView
android:id="@+id/comments"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transcriptMode="alwaysScroll">

This should solve most of your issues, maybe except for the last one(in the image) which I haven't tested.

Caspar answered 24/4, 2013 at 10:32 Comment(2)
I tried adjustPan already, but that scrolls the action bar out of view, and makes it awkward to scroll the list with the keyboard visible. Transcript mode is no use either, unfortunately -- I don't want it to scroll to the bottom of the list. (I thought the OP spelled this out very well already, which is why I added a bounty to his post instead of starting my own.) I need the last visible item to remain the last visible item when the keyboard appears.Weese
Hmm.. tricky, you could try to detect the softkeyboard height and manually adjust the height of the listview set the last visible position (if possible using setSelectionFromTop). Bit lot hackish solution though . Related SO post #13534865Caspar
B
3

Just override the method of ListView#onSizeChanged(int w, int h, int oldw, int oldh) like below: PS:Your ListView's height can not be 0 anytime,if it may 0,not working.

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (getChildCount() == 0) {
        return;
    }
    final int lastVisiblePosition = getLastVisiblePosition();
    int firstVisiblePosition = getFirstVisiblePosition();

    View last = getChildAt(lastVisiblePosition - firstVisiblePosition);
    int top = last.getTop();
    final int newTop = top + (h - oldh);
    Log.d(getClass().getSimpleName(), "onSizeChanged(" + w + ", " + h + ", " + oldw + ", " + oldh + ")");
    if (mFocusSelector == null) {
        mFocusSelector = new FocusSelector();
    }
    post(mFocusSelector.setup(lastVisiblePosition, newTop));
    super.onSizeChanged(w, h, oldw, oldh);
}

private class FocusSelector implements Runnable {
    private int mPosition;
    private int mPositionTop;

    public FocusSelector setup(int position, int top) {
        mPosition = position;
        mPositionTop = top;
        return this;
    }

    public void run() {
        setSelectionFromTop(mPosition, mPositionTop);
    }
}
Bulrush answered 11/8, 2016 at 2:54 Comment(0)
S
2

I actually got the same behavior that you need by adding to Activity declaration in manifest android:windowSoftInputMode="adjustResize" and to the ListView declaration android:transcriptMode="normal". For both keyboard and orientation changes.

Sapodilla answered 1/4, 2014 at 16:31 Comment(0)
W
1

I need this functionality too. Here's my attempt, but it doesn't quite work -- The scrolling is a bit erratic (doesn't always end up where it should) and it interferes with the preservation of the scroll position when rotating the screen.

@Override
public void onResume() {
    super.onResume();
    getListView().addOnLayoutChangeListener(stickyLastItemHandler);
}

@Override
public void onPause() {
    super.onPause();
    getListView().removeOnLayoutChangeListener(stickyLastItemHandler);
}

View.OnLayoutChangeListener stickyLastItemHandler = new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v,
            int left, int top, int right, int bottom,
            int oldLeft, int oldTop, int oldRight, int oldBottom) {

        final int heightChange = (bottom - top) - (oldBottom - oldTop);
        if (heightChange != 0) {
            ((AbsListView)v).smoothScrollBy(-heightChange, 1000);
        }
    }
};
Weese answered 12/4, 2013 at 17:57 Comment(2)
How do you want the last item to behave, snap so it's fully visible or just keep it as it currently is(like half, only a few pixels etc)?Phaidra
Snap to fully visible would probably be tidiest, I think. My app is a task list, and using the edit box should add a new task immediately below the last visible one. (Following text entry I'd scroll the new item up into view.)Weese
L
1

You can do it through xml:

<ListView
    android:stackFromBottom="true"
    android:layout_height=0dp
    android:layout_weight=1 />

Stacking from bottom will keep ListView scrolled down by default, setting ListView's weight will fill all possible space within parent vertical LinearLayout.

Lalia answered 23/4, 2013 at 13:6 Comment(1)
Unfortunately, this doesn't do what I need. The list will initially be scrolled to the bottom, but when the soft keyboard appears the behaviour is the same as it is without this flag.Weese
G
1

I created a layout engine for Android that allows you to listen for keyboard hidden and visible state changes (among many other core features). This library is available at http://phil-brown.github.io/AbLE/.

Simply add the jar to your libs directory (create this folder if it does not exist), and instead of inheriting from an Activity, inherit from AbLEActivity. Next, Override the methods onKeyboardShown and onKeyboardHidden, and in these you can animate your content view up or down as needed:

@Override
public void onKeyboardShown() {
    //animate up
}

@Override
public void onKeyboardHidden() {
    //animate down
}
Galvez answered 26/4, 2013 at 19:23 Comment(1)
What does this give me that OnLayoutChangeListener doesn't?Weese
P
0
Follow these steps
--------------------
1. Update your activity in AndroidManifest.xml file like below
<activity
            android:name=".MyListActivity"
            android:windowSoftInputMode="adjustResize" />

2. Set your list view with like below.
mListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
mListView.setStackFromBottom(true);

I hope it will solve your problem.

Pishogue answered 5/12, 2015 at 7:34 Comment(0)
P
-1

In your ListAdapter, you could do something like this :

   public View getView(int position, View convertView, ViewGroup parent) {

//buttonMore
if( position >= super.getCount()  )
      return textField ;

    ....

This way, you will always get your last item in the list as your textfield.

Pyrology answered 19/2, 2012 at 17:49 Comment(2)
No. Please see my picture carefully. I specifically mentioned that it is not the last item but the last ‘visible‘ item. See the leftmost part of the picture.Telmatelo
You will get what you want. The solution I gave you makes the textfield the last item in the list. If not visible, as user didn't scroll down to the end of the list, then it's not visible...Pyrology
C
-1

You need to set selection position... But - in a strange way, i don't really understand why - you need to do it with post()

public void ScrollToLast() {
    yourList.clearFocus();
    yourList.post(new Runnable() {
        @Override
        public void run() {
            yourList.setSelection(items.size() - 1);
        }
    });
}

So you can add a TextWatcher for your editText and you can call this method there... So your list will scroll to bottom.

Calendar answered 9/11, 2012 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.