21

I encountered a problem when embedding a ListView inside a ScrollView, or at least that's where I guess the problem comes from. The ListView element is a fairly simple one:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/general_background_list_middle" 
    android:paddingTop="4dp"
    android:paddingBottom="4dp"
    android:paddingRight="0dp"
    android:paddingLeft="0dp">

    <ImageView
        android:id="@+id/chat_friends_avatar"       
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginLeft="7dp"
        android:layout_marginRight="8dp"
        android:paddingRight="0dp" 
        android:paddingLeft="0dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:src="@drawable/friends_icon_avatar_default"/>

    <TextView
        android:id="@+id/chat_message_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/chat_friends_avatar"
        android:layout_alignParentTop="true"
        android:layout_marginRight="35dp"
        android:maxLines="10"
        android:textSize="12dp"/>

    <TextView
        android:id="@+id/chat_friend_name"
        android:layout_width="140dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="3dp"
        style="@style/SubText"
        android:layout_toRightOf="@id/chat_friends_avatar"      
        android:layout_below="@id/chat_message_text" />

    <TextView
        android:id="@+id/chat_message_time"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="8dp"
        android:layout_marginTop="3dp"
        style="@style/SubText"
        android:layout_alignParentRight="true"
        android:layout_below="@id/chat_message_text" />

</RelativeLayout>   

However, when I embed a list of such elements in a ScrollView, in between some other elements, the rows are not fully displayed, they are clipped (see image below) if the text is wrapped. The ListView is instantiated as follows in the ScrollView:

<ListView
android:id="@+id/info_chat_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:cacheColorHint="@color/frame_background_color"
android:clickable="false"
android:divider="@null"
android:footerDividersEnabled="false"
android:focusable="false" >
</ListView> 

If the height of the ListView is set to "wrap_content" only the first element is shown. That's why I'm using a method to calculate the height of the rows of the list:

private int getCommentsListHeight() {
        if (mChatAdapter != null && mChatAdapter.getCount() != 0) {
            if (mChatList != null) {// && mCommentsListItemHeight == 0) {
                mCommentsListItemHeight = 0;
                for (int i = 0; i < mChatAdapter.getCount(); i++) {
                    // Get view item height
                    View viewItem = mChatAdapter
                            .getView(i, new View(OnAirActivity.this), mChatList);
                    viewItem.measure(0, 0);
                    Logger.d(LOGTAG, "View " + i + " measured height = " + viewItem.getMeasuredHeight());
                    mCommentsListItemHeight += viewItem.getMeasuredHeight();
                }
            }
            //return mChatAdapter.getCount() * mCommentsListItemHeight;
            return mCommentsListItemHeight;
        } else {
            return 0;
        }
    }

Unfortunately, in case when the text inside the TextView is wrapped, even over several lines, the height of the row element returned by the getMeasuredHeight() method is constant. Also the getLineCount() called on the TextView inside the row element returns 1 even if the text is wrapped.

On the other hand, if this ListView is embedded in a LinearLayout, everything works fine and the full list is displayed with no clipping.

Do you have any suggestions as to what might be wrong here? I really don't like the idea of manually measuring the height of the list elements and it apparently doesn't work but why can't android nicely stretch the ListView inside the ScrollView to fit it all in there?

Clipped list: see image

PawelPredki
  • 754
  • 1
  • 12
  • 26

10 Answers10

39

Use this method created by https://stackoverflow.com/users/205192/dougw

 public static void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter(); 
        if (listAdapter == null) {
            // pre-condition
            return;
        }

        int totalHeight = 0;
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
Community
  • 1
  • 1
Rajesh Wadhwa
  • 1,016
  • 2
  • 8
  • 14
  • This works for me but I don't have the issue of wrapping text like the original question was aksing about. Thanks! – Keith Entzeroth Feb 07 '14 at 15:14
  • This works fine, but then when I added padding around the listview to take the text off the edge of the screen, a small bit gets clipped again. What should I add to fix this? – cnfw Jul 19 '14 at 21:35
  • params.height is the final height that is set.you can add a fixed buffer in case u have to keep padding – Rajesh Wadhwa Jul 21 '14 at 08:56
  • How to implement load more data? – Krunal Shah Jan 17 '15 at 07:07
  • 1
    This answer is copied almost verbatim from here: http://stackoverflow.com/a/3495908/1221537 – adamdport Mar 09 '15 at 22:31
  • This is a bad solution! This will create many useless views. The idea itself is fine but not that way. – rekire Jul 10 '16 at 09:02
  • It can show listview item, but footer view cannot able to display when i added with Listview.addfooter(View).Actually footer layout is exist. How to make able to see footerview? – james Jul 27 '16 at 11:31
  • @Denny each call of `listAdapter.getView(i, null, listView)` will create a new view and just for the measurement. That is very bad for the performance. – rekire Sep 18 '18 at 19:16
  • Is that really creating new views or just getting what's already there? Seems to me the latter – Denny Sep 18 '18 at 23:55
11

It's a BAD practice to encapsulate ListView within a ScrollView because ListView itself contains scrolling capabilities. You should implement a solution that does not contain such hierarchy of views and I hope it will do the magic :)

waqaslam
  • 67,549
  • 16
  • 165
  • 178
  • Thanks for a quick answer. So your suggestion would be, for example, to instantiate those row views programatically inside the Scrollview one by one? – PawelPredki May 22 '12 at 20:33
  • No. You should use ListView but dont put it inside ScrollView. You may customize row items by overriding **getView** method from adapter used for the ListView – waqaslam May 22 '12 at 20:50
  • I am using an adapter and this works fine in this other activity I mentioned where the ListView is within a LinearLayout. I have to use ScrollView here as my Activity contains many elements that don't fit on the screen, including the chat ListView that's the source of all the problems... – PawelPredki May 22 '12 at 21:30
  • then perhaps i would suggest you to use ListView's headers and/or footers to set all other views which you have at the moment inside ScrollView. – waqaslam May 22 '12 at 21:41
  • 1
    This is a bit of an overkill since the majority of the elements in the currently used ScrollView are NOT the list that is troublesome... I understand there is no way to make the ListView work fine inside the ScrollView so I need to think about which workaround/solution is the best in my case. Thanks for the help! – PawelPredki May 22 '12 at 22:03
2

I had the same problem in my project.You need to create simple LinearLayout inside ScrollView. After that you need create new View with your listview item xml using LayoutInflater. After creation put all data in new View and add to LinearLayout as child view:

linearLayot.addView(newView, position_you_need).

Hope it would help you!

duggu
  • 37,851
  • 12
  • 116
  • 113
  • So you're suggesting putting the layouts in the following order: ` <... my other views ...> <... more views ...> ` Isn't this pretty much the same? The ListView is still inside the ScrollView? Right now I went for the 'manual' way by doing , x=0..5 And I have a placeholder array for those five Layouts which I fill out using the same adapter I used for my list view. In effect I have a dynamic 5-element list and it works fine. – PawelPredki May 23 '12 at 10:32
  • No, I suggest you do the following: <... my other views ...> . The main idea is to put you list items to linear layout, not in special listview layout. All items would be scrolled and shown without clipping. – Layko Andrey May 24 '12 at 03:35
2

Here resource of main layout with ScrollView:

<ScrollView android:layout_height="fill_parent" android:layout_width="fill_parent">
<LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/parentLayout"/>
</ScrollView>

Here the code to insert items:

parentLayout.removeAllViews();
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = comments.size() - 1; i >= 0; i--) {
CommentInfo comment = comments.get(i);
View view = inflater.inflate(your_resource_id, null, false);

TextView commentsContent =(TextView)view.findViewById(R.id.commentContent);
if (commentsContent != null) {
    String data = String.format("%s (by %s, %s)",  comment.getCommentText(), comment.getUserName(),
        commentsContent.setTextSize(st.getTextSize());
    commentsContent.setText(data);

}

parentLayout.addView(view, 0);

}       
duggu
  • 37,851
  • 12
  • 116
  • 113
  • This would be a good solution if the only elements in my ScrollView were the comments view. And in that case putting a ListView inside a LinearLayout would be the best solution. In my case, there are many more elements inside the LinearLayout inside the ScrollView so I would first need to know where to put the comments. I guess both approaches are fine. – PawelPredki May 24 '12 at 07:33
  • No problems to add any element into ScrollView after and before LinearLayout. I just cut some code to show exact you need. In real file i have many controls before LinearLaout and after including one more LinearLayout which is also used as ListView. – Layko Andrey May 24 '12 at 08:34
  • If you have other child elements inside the ScrollView except for the LinearLayout then this is against what Android tells you to do here: http://developer.android.com/reference/android/widget/ScrollView.html "A ScrollView is a FrameLayout, meaning you should place one child in it containing the entire contents to scroll; this child may itself be a layout manager with a complex hierarchy of objects" – PawelPredki May 24 '12 at 12:39
  • Add top layout, LinearLayout for example, as ScrollView child and add to it child layout any other controls including LinearLayouts that would be used as list container. I do the same thing, it works properly. – Layko Andrey May 24 '12 at 16:23
  • what if required ExpandableListView instead ListView ? – CoDe Jan 07 '15 at 09:32
1

I took the recommendation of not using a ListView element inside a ScrollView to heart and decided to use a slightly brute force method to achieve what I need. Since there is a constant number of up to five list rows that need to be displayed I removed the ListView instantiation from the xml file and replaced it with five instances of rows:

<include android:id="@+id/info_comment_1" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_2" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_3" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_4" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_5" layout="@layout/chat_single_message" />

In the Activity class I declare five placeholders for these views:

private RelativeLayout mChatMessages[] = new RelativeLayout[COMMENTS_NUMBER];

and initialize them with:

mChatMessages[0] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_1);
mChatMessages[1] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_2);
mChatMessages[2] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_3);
mChatMessages[3] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_4);
mChatMessages[4] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_5);

Then, whenever a new message is received I use the ChatAdapter (the same I used for the ListView previously) and call its getView() method:

protected void updateChatMessages() {
  int msgCount = mChatAdapter.getCount();
  for (int i = 0; i < COMMENTS_NUMBER; i++) {
    if (msgCount <= i) {
      mChatMessages[i].setVisibility(View.GONE);
    } else {
      mChatMessages[i] = (RelativeLayout) mChatAdapter.getView(i, mChatMessages[i], null); 
      mChatMessages[i].setVisibility(View.VISIBLE);
    }   
  }
}

I don't inflate the pariculat views ever again since the only thing that changes is the content of each row, not the layout. This means there is no performance penalty here.

This is basically a manual implementation of a ListView with a limited maximum number of elements. This time, however, ScrollView is able to fit them nicely and nothing gets clipped.

For a dynamic number of rows the approach suggested by Layko could be employed with the views being instantiated programatically and added to the LinearLayout inside the ScrollView.

PawelPredki
  • 754
  • 1
  • 12
  • 26
0

I can see the ListView is inside a ViewPager; one other simple approach to resolving this issue is to add app:layout_behavior="@string/appbar_scrolling_view_behavior" to the ViewPager in your layout xml as seen below.

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

To prevent the same behavior at the bottom of the list, you can also add android:layout_marginBottom="?attr/actionBarSize" to the ViewPager like so

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    android:layout_marginBottom="?attr/actionBarSize"/>

This is coming late, but I hope it helps any other person.

Cletus Ajibade
  • 1,550
  • 15
  • 14
0

try it.. after create all view add bellow line for ScrollView location on screen (x,y)

  ScrollView scrollView = (ScrollView)findViewById(R.id.scrollView);

  scrollView.smoothScrollTo(0,0);// top location zero index
Neeraj Singh
  • 610
  • 9
  • 15
0

I had a similar problem. I have a

RelativeLayout
  listView
  includeLayout  

where I include some bottom nav beneath the listView with this

<include
    android:id="@+id/includeLayout"
    layout="@layout/bottom_nav_bar"

and my listView was clipped - not taking the full height available between the header and bottom nav. I tried various xml settings suggested in this and other threads, but what worked for me was to add

android:layout_alignBottom="@+id/includeLayout"

to my listView. That seemed to pull the listView down to the top of the bottom nav, so that the listView is now using the full available height (and it scrolls as needed).

0

This works for me

<RelativeLayout                                          xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:padding="@dimen/activity_vertical_margin">

    <TextView
        android:id="@+id/tv_status"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="17sp"
        android:text="@string/text_list_devices" />

    <ListView
        android:id="@+id/lv_paired"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:cacheColorHint="#00000000"
        android:layout_above="@+id/signup_t"
        android:layout_below="@id/tv_status"
        />

    <Button

        android:id="@+id/signup_t"
        style="?android:textAppearanceSmall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textColor="@android:color/white"
        android:layout_alignParentBottom="true"
        android:text="Print All Records"
        android:typeface="sans"
        android:layout_marginLeft="45dp"
        android:layout_marginRight="45dp"
        android:textSize="24sp"
        android:background="@drawable/selector_for_button"
        android:textStyle="bold" />
</RelativeLayout>
Nouman Ch
  • 4,023
  • 4
  • 29
  • 42
0

This is BAD practice. But there are some situations we can not avoid using that. For example dynamic e-commerce layouts we may put multiple lists or recycle views but you don't want to scroll inside a single item height (if accidentally wanted!!). I faced this kind of problem. I fixed using a simple way. I don't tell this is the correct way but it may help some. !! I used to recycle the view.

(01) Create an Interface to return view height.

public interface AfterViewLoadListener {

    void onViewHeightMeasured(int height, String mode);
}

(02) implement with your activity

public class *Activity extends AppCompatActivity implements AfterViewLoadListener{
  /**  your codes **/
  final SimpleListRecycleAdapter order_adapter = new SimpleListRecycleAdapter(this,"ORDER");
}

@Override
public void onViewHeightMeasured(int height, String mode) {
    if(mode.equals("ORDER") && height > 0){
        recycleView.setMinimumHeight(height);
    }
}

(03) inside the recycle view custom adapter

AfterViewLoadListener viewLoadListener = null;
public SimpleListRecycleAdapter(AfterViewLoadListener listener, String mode) {
    if(listener instanceof AfterViewLoadListener){
        viewLoadListener = listener;
    }
    this.mode = mode;
}

(04) override the onViewAttachedToWindow method

@Override
public void onViewAttachedToWindow(@NonNull SimpleListViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    View view = holder.itemView;
    view.measure(0, 0);
    this.viewMinHeight = view.getMeasuredHeight();

    if(!firstFlag){
        firstFlag = true;
        viewLoadListener.onViewHeightMeasured(this.viewMinHeight*filtered.length(),mode);
    }
}

(05) That's it. It worked for me.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83