ExpandableListView dynamic child height - android

I have a ExpandableListView which is inside an LinearLayout as container and set by using CustomAdapter.
For its children, I'm using onGroupClick() method to send an request to specific service and getting result as String, then filling child list of clicked group item.
The Problem is since I can't get the updated height (after service response has set to text view of child view's text view) the linearlayout container height doesn't increase the way it should. And it also creates a scrolling problem.
Though list child item xml is WRAP_CONTENT as below:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="#dimen/rowHeight"
android:background="#color/colorLightYellow">
<TextView
android:id="#+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="#dimen/marginGeneral"
android:layout_toLeftOf="#+id/tvCount"
android:gravity="center_vertical"
android:paddingBottom="#dimen/marginGeneral"
android:paddingTop="#dimen/marginGeneral"
android:text="tvTitle"
android:textColor="#color/colorGray"
android:textSize="#dimen/fontSizeNormal" />
...
</RelativeLayout>
So the code part is little long stay with me:
#Override
public boolean onGroupClick(ExpandableListView parent, View v, final int groupPosition, long id) {
Map<Item, List<ItemDetail>> childList = detailExpandableAdapter.getChildList();
final Item item = detailExpandableAdapter.getGroup(groupPosition);
if (childList.get(item).size() == 0) {
startProgressDialog();
GlobalApplication.getService().getItemDetails(Session.getCurrent().getSessionId(), getItem.item.itemNo, item.name, new ServiceCallback<GetItemDetails>() {
#Override
public void onSuccess(GetItemDetails response) {
stopProgressDialog();
List<ItemDetail> itemDetailList = null;
if (GetItemDetails.isSuccess(response)) {
itemDetailList = response.getItemDetailList();
} else {
itemDetail itemDetail = new ItemDetail();
itemDetail.resultDesc = response.getResult().getResultDesc();
if (StringUtils.isNullOrWhitespace(itemDetail.resultDesc)) {
itemDetail.resultDesc = Result.getGeneralFailResult().getResultDesc();
}
itemDetailList = new ArrayList<ItemDetail>();
itemDetailList.add(itemDetail);
}
if (itemDetailList != null) {
Map<Item, List<ItemDetail>> childList = detailExpandableAdapter.getChildList();
if (childList.containsKey(item)) {
childList.remove(item);
}
childList.put(item, itemDetailList);
detailExpandableAdapter.setChildList(childList);
detailExpandableAdapter.notifyDataSetChanged();
detailExpandableAdapter.notifyDataSetInvalidated();
listViewLastItems.expandGroup(groupPosition);
}
}
#Override
public void onFail() {
stopProgressDialog();
}
});
return false;
}
return false;
}
#Override
public void onGroupExpand(int groupPosition) {
setExpandableListViewHeightStable(listViewLastItems, llListViewItemDetailContainer);
if (lastExpanded != -1 && groupPosition != lastExpanded)
listViewItems.collapseGroup(lastExpanded);
lastExpanded = groupPosition;
}
public void setExpandableListViewHeight(ExpandableListView expandableListView, LinearLayout linearLayoutParent){
try {
ExpandableListAdapter expandableListAdapter = expandableListView.getExpandableListAdapter();
int totalHeight = 0;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(expandableListView.getWidth(), View.MeasureSpec.UNSPECIFIED);
for (int i = 0; i < expandableListAdapter.getGroupCount(); i++) {
View groupItem = expandableListAdapter.getGroupView(i, false, null, expandableListView);
groupItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
//Logger.debug("recalculateExpandableListViewHeight listItem:"+groupItem.getMeasuredHeight());
totalHeight += groupItem.getMeasuredHeight();
if (expandableListView.isGroupExpanded(i)){
for (int j = 0; j < expandableListAdapter.getChildrenCount(i); j++) {
View listItemChild = expandableListAdapter.getChildView(i, j, false, null, expandableListView);
listItemChild.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, View.MeasureSpec.UNSPECIFIED));
listItemChild.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
Logger.debug("recalculateExpandableListViewHeight listItemChild:" + listItemChild.getMeasuredHeight());
totalHeight += listItemChild.getMeasuredHeight();
}
}
}
linearLayoutParent.getLayoutParams().height = totalHeight + (expandableListAdapter.getGroupCount() * expandableListView.getDividerHeight());
linearLayoutParent.requestLayout();
} catch (Exception e) {
Logger.printStackTrace(e);
}
}
Update: this is the linear layout I use as container
<LinearLayout
android:id="#+id/llListViewItemContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/tvItemDueDate"
android:layout_marginTop="#dimen/marginGeneral"
android:orientation="vertical"/>
Update 2: I'm adding ExpandableListView to LinearLayout dynamically.
listViewItems = new ExpandableListView(getContext());
listViewItems.setScrollContainer(false);
listViewItems.setDivider(new ColorDrawable(getResources().getColor(R.color.colorLightGray)));
listViewItems.setDividerHeight(UIHelper.convertDptoPixels(1));
listViewItems.setGroupIndicator(null);
listViewItems.setOnGroupClickListener(this);
listViewItems.setOnGroupExpandListener(this);
listViewItems.setOnGroupCollapseListener(this);
//generate empty child list
Map<Item, List<ItemDetail>> childMap = new HashMap<>();
for (Item item : getItems.getItemList()) {
childMap.put(item, new ArrayList<ItemDetail>());
}
detailExpandableAdapter = new detailExpandableAdapter(getActivity(), getItems.getItemList(), childMap);
listViewItems.setAdapter(detailExpandableAdapterF);
listViewItems.removeAllViews();
listViewItems.addView(listViewLastItems);
UIHelper.setExpandableListViewHeightStable(listViewItems, llListViewDetailContainer);

Use below given custom ExpandableListView class and override onMeasure method.
public class MyExpandableListView extends ExpandableListView {
public MyExpandableListView(Context context) {
super(context);
}
public MyExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyExpandableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
And use it like,
<com.app.custom.NonScrollExpandableListView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Replace com.app.custom with your package name in which you put this custom class.
If possible use NestedScrollView instead of ScrollView, as it supports acting as both a nested scrolling parent and child on both new and old versions of Android. Nested scrolling is enabled by default.
Let me know if this help you or not. Happy Coding!!!

I would suggest you to use HeaderView property of ExpandableListView. AS ExpandableListView is a derived class of ListView so HeaderView property must be there as I believe.
Issues in using ListView inside ScrollView -
Performance - If you going to play with measure property of ListView then it will surely affect recycling of cells.
User Experience - Strange behaviour comes when ListView's parent is another scrollable view.
Better move your LinearLayout stuff inside another view, then inflate that view and either put it in Header or Footer of ListView as per your need.
// Inflated View which is going to be use as Header of view
ViewGroup headerView = (ViewGroup)getLayoutInflater().inflate(R.layout.list_header,expListView,false);
// Add that view to Header
your_expandale_listView.addHeaderView(headerView);

Related

Detect setOnScrollListener in Listview properly after setting Listview height dynamically

I am using listview inside Viewpager where I need to set ListView height based on child and I need to add new Items when user scroll to last position of Listview. But the problem is when I am setting listview height dynamically its making current listview item visible(or selected). That's why getting (calling method to get data) automatically.
Code is given below:
int index = lvNetwork.getFirstVisiblePosition();
View v = lvNetwork.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
adapter = new NetworkAdapter(activity, R.layout.network_custom_row, networkDataArrayList);
adapter.notifyDataSetChanged();
lvNetwork.setAdapter(adapter);
Utils.setlistViewHeight(lvNetwork, activity);
lvNetwork.setSelectionFromTop(index, top);
lvNetwork.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int finalItem = firstVisibleItem + visibleItemCount;
Log.d("dataCalling", "visible " + finalItem);
Log.d("dataCalling", "total " + totalItemCount);
if (finalItem == totalItemCount) {
if (preLast != finalItem) {
preLast = finalItem;
Log.d("dataCalling", String.valueOf(totalItemCount));
Log.d("dataCalling", "Page " + nextid);
progressBar.setVisibility(View.VISIBLE);
getNetworkFeed();
}
}
}
});
setlistviewHeight method inside Utils,
public static void setlistViewHeight(ListView listView, Context context) {
ListAdapter myListAdapter = listView.getAdapter();
if (myListAdapter == null) {
return;
}
int totalHeight = 0;
for (int size = 0; size < myListAdapter.getCount(); size++) {
View listItem = myListAdapter.getView(size, null, listView);
if (listItem instanceof ViewGroup)
listItem.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT));
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
#SuppressWarnings("deprecation")
int screenWidth = display.getWidth();
int listViewWidth = screenWidth - 65;
int widthSpec = View.MeasureSpec.makeMeasureSpec(listViewWidth,
View.MeasureSpec.AT_MOST);
listItem.measure(widthSpec, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight
+ (listView.getDividerHeight() * (myListAdapter.getCount()));
listView.setLayoutParams(params);
listView.requestLayout();
}
***This code works good if I do not need to set listview height dynamically.
What should I change here to make it work or any alternative solution to get desire result?
Any help will be appreciated.
But the problem is when I am setting listview height dynamically its making current listview item visible
Setting height dynamically is not the problem. Rather, the problem is you are setting the height of the listview as the maximum possible height of listview by calculating height of each item in the list. So what will happen is all the items of the listview will be populated at once and will remain inflated in the list.(NOTE : No view recycling will happen now)
That's why getting (calling method to get data) automatically
The call is happening because you are setting the height of the listview based on the total number of items in the list. What happens because of this is, all the elements in your listview will be in visible state at any given point of time. Which means your condition
if (finalItem == totalItemCount){}
will always be true because your visibleItemCount will always be totalItemCount which makes your final item always equal to totalItemCount. (you can verify this by debugging your app).
What should I change here to make it work or any alternative solution to get desire result?
The best solution I can think of is setting the height of listview if and only if the total height calcuated by you on the basis of heights of all the items is lesser than the height of the screen. Otherwise, set the height of the listview as MATCH_PARENT.
ViewGroup.LayoutParams params = listView.getLayoutParams();
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int height = size.y;
if(totalHeight > height){
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
}else {
Log.d("", "");
params.height = totalHeight
+ (listView.getDividerHeight() * (myListAdapter.getCount()));
}
So this code will prevent making all the views of the listview to become visible and hence onScroll visibleItemCount you will receive, will the no of items currently visible.
Ankit already explained you what's the problem with your code, let me share an alternate solution with you.
As its no good to use listview when you are already populating its items instead it's better to use scrollview and add items dynamically. Scrollview does not have a scroll listener so we customise it to make one.
MyScrollView.Java
public class MyScrollView extends ScrollView {
public interface OnScrollListener {
void onScrollChanged(int l, int t, int oldl, int oldt);
}
private OnScrollListener onScrollListener;
public OnScrollListener getOnScrollListener() {
return onScrollListener;
}
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (onScrollListener != null) {
onScrollListener.onScrollChanged(l, t, oldl, oldt);
}
}
}
We use the scrolllistener in activity like this -
import android.graphics.Rect;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
public class NewScrollActivity extends AppCompatActivity {
private MyScrollView scrollView;
private LinearLayout container;
private ProgressBar progressBar;
int maxItem = 20;
private View lastItemView;
boolean alreadyExecutingRequest = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new_scroll);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
scrollView = (MyScrollView) findViewById(R.id.scrollView);
scrollView.setOnScrollListener(scrollListener);
container = (LinearLayout) findViewById(R.id.container);
addItemsAsynchronously();
}
private MyScrollView.OnScrollListener scrollListener = new MyScrollView.OnScrollListener() {
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
if (lastItemView != null && !alreadyExecutingRequest) {
Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (lastItemView.getLocalVisibleRect(scrollBounds)) {
// Any portion of the lastitem view, even a single pixel, is within the visible window
addItemsAsynchronously();
}
}
}
};
private void addItemsAsynchronously() {
new AsyncTask<Void,Void,Void>() {
#Override
protected void onPreExecute() {
super.onPreExecute();
progressBar.setVisibility(View.VISIBLE);
alreadyExecutingRequest = true;
}
#Override
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
progressBar.setVisibility(View.GONE);
addItemsToContainer();
alreadyExecutingRequest = false;
}
}.execute();
}
private void addItemsToContainer() {
int lastAddedItem = container.getChildCount();
for (int i=lastAddedItem;i<maxItem;i++) {
View view = LayoutInflater.from(this).inflate(R.layout.new_item, null);
TextView textView = (TextView) view.findViewById(R.id.textView);
textView.setText("Item - " + i);
container.addView(view);
}
lastItemView = container.getChildAt(container.getChildCount() -1);
maxItem+=10;
}
}
Here what we did is we checked the last item bound with the scrollview bounds, so it the view is visible then we are at the bottom, so add further items.
activity_new_scroll.xml
<?xml version="1.0" encoding="utf-8"?>
<com.sj.textinputlayout.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.sj.textinputlayout.NewScrollActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<ProgressBar android:id="#+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</com.sj.textinputlayout.MyScrollView>
new_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content">
<TextView android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

issue in listview size small when it is inside scrollview android

I want to integrate listview inside scrollview so below is my code
xml file
<ScrollView
android:id="#+id/scl_add_task"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone" >
<RelativeLayout
android:id="#+id/rel_hjistory"
android:layout_width="fill_parent"
android:layout_below="#+id/img_save"
android:visibility="gone"
android:layout_height="fill_parent" >
<ListView
android:id="#+id/list_history"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:dividerHeight="2dp"
android:layout_marginTop="5dp"
android:cacheColorHint="#android:color/transparent"
android:divider="#f2f2f2"
android:layerType="software"
android:numColumns="1"
android:verticalSpacing="5dp"
android:visibility="visible" />
<TextView
android:id="#+id/img_post"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/list_history"
android:layout_centerHorizontal="true"
android:text="POST"
android:padding="10dp"
android:background="#drawable/rect_orange"
android:textStyle="bold"
android:textColor="#android:color/white"
android:textSize="#dimen/text_15"
android:visibility="visible" />
</RelativeLayout>
</ScrollView>
When i run my code it look like below images
list view getting very small in height i want to it full height with full screen scroll.
public class ExpandableHeightListView extends ListView {
boolean expanded = false;
public ExpandableHeightListView(Context context)
{
super(context);
}
public ExpandableHeightListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightListView(Context context, AttributeSet attrs,
int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
hope this works for you..
use this method.
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
this method will calculate total item hieght of view from adapter and set it to the listview
i dont remember from where i had got this solution or else i would have posted link of that answer. anyways thanx to that guy.
Replace the Listview with linearlayout<LinearLayout
android:id="#+id/listdemo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"></LinearLayout>
in your code you u have to addd this method
private void customListview() {
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.listdemo);
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i <20 ; i++) {
View view = inflater.inflate(R.layout.item_recyclerview,null);
linearLayout.addView(view);
}
}`
item_recycleview is your layout of a single item of the list
count now set as 20 u can set as your wish
You should use a LinearLayout set to vertical orientation instead of a ListView.
Normally a ListView is used for displaying a dynamic number of items given to it by an Adapter and the ListView will handle the scrolling for the items. When you put a ListView inside a ScrollView then both widgets will be in conflict over who handles the scrolling.
Setting the height of a ListView to WRAP_CONTENT doesn't work, because it has a dynamic number of items. The ListView wants to have a static height (such as MATCH_PARENT or 400dp) and will scroll when needed and even create the item views on the fly when they become visible.
So use a LinearLayout and load it with child views yourself.
<LinearLayout
android:id="#+id/list_history"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
... snipped other attribs ... />
Since you already have an adapter that provides the views for the ListView you can use that to populate your LinearLayout:
adapter = new YourAdapter();
LinearLayout historyList = (LinearLayout) findViewById(R.id.list_history);
for (int position = 0; position < adapter.getCount(); position += 1) {
View itemView = adapter.getView(position, null, historyList);
historyList.addView(itemView);
}
Note: this is only a good solution if you know that your adapter will never provide a large number of items because you would then load your LinearLayout with a large number of views and things will grind to a halt or even crash.
If you cannot have a small number of items at all times then you will need to change your user interface. Perhaps you can just display the first 10 items only and when clicked go to a new activity that shows them all in a proper listview.
Okay, another answer based on your comment: "I want to display list item data without scrolling problem both scroll should be separate."
Set your ListView's height to a fixed amount:
<ListView
android:id="#+id/list_history"
android:layout_width="fill_parent"
android:layout_height="200dp"
... snipped other attribs ... />
This will allow the ScrollView to actually scroll the ListView and the ListView to scroll its own children.
Putting listview in scrollview is not a good idea.
Add a listview header instead.
create new layout and put everything you have on top of listview in there.
Then inflate the layout and add it as listview header:
listView = view.FindViewById<ListView> (R.Id.ListView);
var header = inflater.inflate(R.layout.header, null);
listView.AddHeaderView (header);
The only solution that worked for me was here:
londatiga.net/it/programming/android/make-android-listview-gridview-expandable-inside-scrollview/
which says (quotes):
package net.londatiga.android.widget;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ListView;
import android.content.Context;
public class ExpandableHeightListView extends ListView
{
boolean expanded = false;
public ExpandableHeightListView(Context context)
{
super(context);
}
public ExpandableHeightListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightListView(Context context, AttributeSet attrs,
int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
and then use it
myExpandableHeightListView.setExpanded(true);

Android list view inside a scroll view

I have an android layout which has a scrollView with a number of elements with in it. At the bottom of the scrollView I have a listView which is then populated by an adapter.
The problem that I am experiencing, is that android is excluding the listView from the scrollView as the scrollView already has a scroll-able function. I want the listView to be as long as the content is and for the master scroll view to be scroll-able.
How can I achieve this behavior?
Here is my main layout:
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:fillViewport="true"
android:gravity="top" >
<LinearLayout
android:id="#+id/foodItemActvity_linearLayout_fragments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
I then programmatically add my components to the linearlayour with the id: foodItemActvity_linearLayout_fragments. Below is one of the views that is loaded into that linearlayout. This is the one giving me trouble with the scrolls.
<?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"
android:orientation="vertical" >
<TextView
android:id="#+id/fragment_dds_review_textView_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reviews:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ListView
android:id="#+id/fragment_dds_review_listView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
My adapter then fills up this list view.
Here is an image from the android hierarchy viewer when I click on the master scrollView:
As you can see, it is excluding the reviews listView.
I should be able to scroll the page down and see 8 reviews, but instead it only shows me those 3, and I can scroll on the tiny part where the reviews are. I want a global page scroll
For any Child view to scroll inside a ScrollView. Anything like ListView, RecyclerView, etc. You just have to replace ScrollView with androidx.core.widget.NestedScrollView in your current xml and then magic happens.
Below is a sample xml code :
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:paddingBottom="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Recycler View inside a Scroll View"
android:textColor="#color/black"
android:textSize="#dimen/_20sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Below is a Recycler View as an example."
android:textSize="16sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#id/et_damaged_qty" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="This textview automatically goes below the Recycler View."
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.core.widget.NestedScrollView>
Now you can get rid of all the ugly hacks you did to get around with nested scrolling.
The answer is simple and I am surprised it has yet to be answered here.
Use a Header View or/and Footer View on the list itself.
Don't mix a ScrollView with a ListView or anything that can scroll. It's meant to be used with headers and footers :)
Essentially, take all the content above your ListView, put it in another .xml file as a layout and then in code inflate it and add it to the list as a header view.
i.e.
View header = getLayoutInflater().inflate(R.layout.header, null);
View footer = getLayoutInflater().inflate(R.layout.footer, null);
listView.addHeaderView(header);
listView.addFooterView(footer);
I know it's been so long but I got this problem too, tried this solution and it's working. So I guess it may help the others too.
I add android:fillViewport="true" on the layout xml for the scrollView.
So overall my ScrollView will be like this.
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scrollView6"
android:fillViewport="true">
And it works like magic to me.
the ListView that located inside my ScrollView expand to its size again.
Here is the full example code for the ScrollView and the ListView.
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scrollView6" android:fillViewport="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
....
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/lv_transList" android:layout_gravity="top"
android:layout_marginTop="5dp"/>
....
</LinearLayout>
</ScrollView>
You Create Custom ListView Which is non Scrollable
public class NonScrollListView extends ListView {
public NonScrollListView(Context context) {
super(context);
}
public NonScrollListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
In Your Layout Resources File
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdgeLength="0dp"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- com.Example Changed with your Package name -->
<com.Example.NonScrollListView
android:id="#+id/lv_nonscroll_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.Example.NonScrollListView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/lv_nonscroll_list" >
<!-- Your another layout in scroll view -->
</RelativeLayout>
</RelativeLayout>
</ScrollView>
In Java File
Create a object of your customListview instead of ListView like :
NonScrollListView non_scroll_list = (NonScrollListView) findViewById(R.id.lv_nonscroll_list);
public static void setListViewHeightBasedOnChildren(ListView listView) {
// 获取ListView对应的Adapter
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0, len = listAdapter.getCount(); i < len; i++) { // listAdapter.getCount()返回数据项的数目
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0); // 计算子项View 的宽高
totalHeight += listItem.getMeasuredHeight(); // 统计所有子项的总高度
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
// listView.getDividerHeight()获取子项间分隔符占用的高度
// params.height最后得到整个ListView完整显示需要的高度
listView.setLayoutParams(params);
}
you can use this code for listview in scrollview
Don't do anything in Parent ScrollView. Only do this to child ListView. Everything will work perfectly.
mListView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mScrollView.requestDisallowInterceptTouchEvent(true);
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_UP:
mScrollView.requestDisallowInterceptTouchEvent(false);
break;
}
return false;
}
});
This code will solve your problem if you have implemented just a ListView in a code.
If you are using RelativeLayout as ListView child than this code return a NullPointerException here listItem.measure(0, 0);, because of RelativeLayout.And the solution is put your Relativelayout inside a LinearLayout and it will work fine.
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);
listView.requestLayout();
}
You may solve it by adding android:fillViewport="true" to your ScrollView.
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:fillViewport="true"
android:scrollbars="vertical">
<ListView
android:id="#+id/statusList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:animationCache="false"
android:divider="#null"
android:scrollingCache="false"
android:smoothScrollbar="true" />
</ScrollView>
before use that property, there was only one child of my list view is visible. after using that all the rows or child of list are visible.
I'll leave it here in case anyone will face the same issue. I had to put a ListView inside a ScrollView. ListView with header was not an option by a number of reasons. Neither was an option to use LinearLayout instead of ListView. So I followed the accepted solution, but it didn't work because items in the list had complex layout with multiple rows and each listview item was of variable height. Height was measured not properly. The solution was to measure each item inside ListView Adapter's getView() method.
#Override
public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
if (view == null) {
. . .
view.setTag(holder);
} else holder = (ViewHolder)view.getTag();
. . .
// measure ListView item (to solve 'ListView inside ScrollView' problem)
view.measure(View.MeasureSpec.makeMeasureSpec(
View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
return view;
}
You can easy put ListView in ScrollView!
Just need to change height of ListView programmatically, like this:
ViewGroup.LayoutParams listViewParams = (ViewGroup.LayoutParams)listView.getLayoutParams();
listViewParams.height = 400;
listView.requestLayout();
This works perfectly!
Done after lots of R&D:
fragment_one.xml should looks like:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scrollViewParent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="400dip" >
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="#+id/customView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/transparent" />
</RelativeLayout>
<!-- Your other elements are here -->
</LinearLayout>
</ScrollView>
Your Java class of FragmentOne.java looks like:
private ListView listView;
private View customView
onCreateView
listView = (ListView) rootView.findViewById(R.id.listView);
scrollViewParent = (ScrollView)rootView.findViewById(R.id.scrollViewParent);
customView = (View)rootView.findViewById(R.id.customView);
customView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// Disallow ScrollView to intercept touch events.
scrollViewParent.requestDisallowInterceptTouchEvent(true);
// Disable touch on transparent view
return false;
case MotionEvent.ACTION_UP:
// Allow ScrollView to intercept touch events.
scrollViewParent.requestDisallowInterceptTouchEvent(false);
return true;
case MotionEvent.ACTION_MOVE:
scrollViewParent.requestDisallowInterceptTouchEvent(true);
return false;
default:
return true;
}
}
});
My requirement is to include a ListView of equally-sized items within a ScrollView. I tried a few of the other solutions listed here, none seemed to size the ListView correctly (either too little space or too much). Here's what worked for me:
public static void expandListViewHeight(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null)
return;
ViewGroup.LayoutParams params = listView.getLayoutParams();
listView.measure(0, 0);
params.height = listView.getMeasuredHeight() * listAdapter.getCount() + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
Hope this helps someone.
I had a similar problem to the issue posed by the Original Poster - how to make the listview scroll inside the scrollview - and this answer solved my problem.
Disable scrolling of a ListView contained within a ScrollView
I didn't call new fragments into existing layouts or anything like that, like the OP was doing, so my code would look something like this :
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:fillViewport="true"
android:gravity="top" >
<LinearLayout
android:id="#+id/foodItemActvity_linearLayout_fragments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/fragment_dds_review_textView_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reviews:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ListView
android:id="#+id/my_listView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
</ScrollView>
Basically what I am doing is checking the length of the listview before I call it and when I call it I make it into that length. In your java class use this function:
public static void justifyListViewHeightBasedOnChildren (ListView listView) {
ListAdapter adapter = listView.getAdapter();
if (adapter == null) {
return;
}
ViewGroup vg = listView;
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams par = listView.getLayoutParams();
par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
listView.setLayoutParams(par);
listView.requestLayout();
}
And call the function like this:
justifyListViewHeightBasedOnChildren(listView);
The result is a listview with no scrollbar, the whole length of the listview being displayed, that scrolls with the scroll bar of the scrollview.
As others had already mentioned, don't use ListView inside a ScrollView.
To workaround, you can use a LinearLayout, but to still keep things neat - populate your LinearLayout with an Adapter, same as you do with a ListView
You can use this class as a LinearLayout replacement that supports Adapters
import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
public class AdaptableLinearLayout extends LinearLayout {
private BaseAdapter mAdapter;
private int mItemCount = 0;
private boolean mDisableChildrenWhenDisabled = false;
private int mWidthMeasureSpec;
private int mHeightMeasureSpec;
public AdaptableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public BaseAdapter getAdapter() {
return mAdapter;
}
public void setAdapter(BaseAdapter adapter) {
mAdapter = adapter;
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
updateLayout();
super.onChanged();
}
#Override
public void onInvalidated() {
updateLayout();
super.onInvalidated();
}
});
updateLayout();
}
private void updateLayout() {
mItemCount = mAdapter.getCount();
requestLayout();
invalidate();
}
/**
* set size for the current View
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidthMeasureSpec = widthMeasureSpec;
mHeightMeasureSpec = heightMeasureSpec;
removeAllViewsInLayout();
for (int i = 0; i < mItemCount; i++) {
makeAndAddView(i);
}
}
private View makeAndAddView(int position) {
View child;
// Nothing found in the recycler -- ask the adapter for a view
child = mAdapter.getView(position, null, this);
// Position the view
setUpChild(child, position);
return child;
}
private void setUpChild(View child, int position) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
if (lp == null) {
lp = generateDefaultLayoutParams();
}
addViewInLayout(child, position, lp);
// Get measure specs
int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, getPaddingTop() + getPaddingBottom(), lp.height);
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width);
// Measure child
child.measure(childWidthSpec, childHeightSpec);
int childLeft;
int childRight;
// Position vertically based on gravity setting
int childTop = getPaddingTop() + ((getMeasuredHeight() - getPaddingBottom() - getPaddingTop() - child.getMeasuredHeight()) / 2);
int childBottom = childTop + child.getMeasuredHeight();
int width = child.getMeasuredWidth();
childLeft = 0;
childRight = childLeft + width;
child.layout(childLeft, childTop, childRight, childBottom);
if (mDisableChildrenWhenDisabled) {
child.setEnabled(isEnabled());
}
}
}
You can put all into linear layout. That is, create linear layout and it will have 2 childs, scrollview and another linear layout. Give them layout weights and here you go :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ScrollView
android:layout_width="fill_parent"
android:layout_height="0dip" android:layout_weight="0.8">
<LinearLayout
android:id="#+id/seTaskActivityRoot"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="#string/taskName" />
<Spinner
android:id="#+id/seTaskPrioritiesSP"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="#+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="#string/taskTargetInNumeric" />
<Spinner
android:id="#+id/seTaskUnitsSP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="#+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="#string/newTaskCurrentStatus" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:hint="#string/addTaskCurrentStatus"
android:inputType="numberDecimal" />
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dip"
android:orientation="vertical" android:layout_weight="0.2">
<TextView
android:id="#+id/textView8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
<ListView
android:id="#+id/logList"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
You should never use a ScrollView with a ListView, because ListView takes care of its own vertical scrolling. Most importantly, doing this defeats all of the important optimizations in ListView for dealing with large lists, since it effectively forces the ListView to display its entire list of items to fill up the infinite container supplied by ScrollView.
http://developer.android.com/reference/android/widget/ScrollView.html
Best solution is add this android:nestedScrollingEnabled="true" attribute in child scrolling for example i have inserted this attribute in my ListView that is child of ScrollView. i hope this mathod works for you :-
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="TextView"/>
<ListView
android:nestedScrollingEnabled="true" //add this only
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="300dp"/>
</LinearLayout>
</ScrollView>
Do NEVER put a ListView inside of a ScrollView! You can find more information about that topic on Google. In your case, use a LinearLayout instead of the ListView and add the elements programmatically.
Update
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:fillViewport="true"
android:gravity="top" >
<LinearLayout
android:id="#+id/foodItemActvity_linearLayout_fragments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
to
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="2"
android:fillViewport="true"
android:gravity="top" >
<LinearLayout
android:id="#+id/foodItemActvity_linearLayout_fragments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
The point here is you are trying to set height to 0dp (fixed)
found a solution for scrollview -> viewpager -> FragmentPagerAdapter -> fragment -> dynamic listview, but im not the author. there is some bugs, but at least it works
public class CustomPager extends ViewPager {
private View mCurrentView;
public CustomPager(Context context) {
super(context);
}
public CustomPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mCurrentView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int height = 0;
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = mCurrentView.getMeasuredHeight();
if (h > height) height = h;
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void measureCurrentView(View currentView) {
mCurrentView = currentView;
this.post(new Runnable() {
#Override
public void run() {
requestLayout();
}
});
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}
}
public class MyPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
private int mCurrentPosition = -1;
public MyPagerAdapter(FragmentManager fm) {
super(fm);//or u can set them separately, but dont forget to call notifyDataSetChanged()
this.fragments = new ArrayList<Fragment>();
fragments.add(new FirstFragment());
fragments.add(new SecondFragment());
fragments.add(new ThirdFragment());
fragments.add(new FourthFragment());
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position != mCurrentPosition) {
Fragment fragment = (Fragment) object;
CustomPager pager = (CustomPager) container;
if (fragment != null && fragment.getView() != null) {
mCurrentPosition = position;
pager.measureCurrentView(fragment.getView());
}
}
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
fragments layout can be anything
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent" tools:context="nevet.me.wcviewpagersample.FirstFragment">
<ListView
android:id="#+id/lv1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#991199"/>
</LinearLayout>
then somewhere just
lv = (ListView) view.findViewById(R.id.lv1);
lv.setAdapter(arrayAdapter);
setListViewHeightBasedOnChildren(lv);
}
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null)
return;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(),
View.MeasureSpec.UNSPECIFIED);
int totalHeight = 0;
View view = null;
for (int i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, listView);
if (i == 0)
view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth,
LinearLayout.LayoutParams.WRAP_CONTENT));
view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
using this ListView Worked for me
package net.londatiga.android.widget;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ListView;
import android.content.Context;
public class ExpandableHeightListView extends ListView
{
boolean expanded = false;
public ExpandableHeightListView(Context context)
{
super(context);
}
public ExpandableHeightListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightListView(Context context, AttributeSet attrs,
int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
and in xml
<com.pakagename.ExpandableHeightListView
android:id="#+id/expandableHeightListView"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.Example.ExpandableHeightListView>
and in MainActivity
ExpandableHeightListView listView = new ExpandableHeightListView(this);
listview=(ExpandableHeightListView)findViewById(R.id.expandableHeightListView);
listView.setAdapter(adapter); //set your adaper
listView.setExpanded(true);
Refer This article for more info and also to know how to keep gridview inside scroll view
It is not possible to use Scroll-view inside List-view as List-view already has scrolling property.
To use list-view inside Scroll-view you can follow these steps which worked for me :
1) Create NonScrollListView java file that disable the default scrolling property of list-view. and code is below
package your-package-structure;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ListView;
public class NonScrollListView extends ListView {
public NonScrollListView(Context context) {
super(context);
}
public NonScrollListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
2) Now create xml file which which has NestedScrollView and inside this use NonScrollListView for listing your items. This will make your entire screen to scroll with all the views.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ViewFlipper
android:id="#+id/v_flipper"
android:layout_width="match_parent"
android:layout_height="130dp">
</ViewFlipper>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SHOP"
android:textSize="15dp"
android:textStyle="bold"
android:gravity="center"
android:padding="5dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="5dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="#ddd"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1"
>
<com.abc.xyz.NonScrollListView
android:id="#+id/listview"
android:divider="#null"
android:layout_width="match_parent"
android:layout_marginBottom="10dp"
android:layout_height="match_parent"
android:padding="8dp">
</com.abc.xyz.NonScrollListView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom">
<include layout="#layout/footer" />
</LinearLayout>
</LinearLayout>
3) Now in java class i.e, home.java define NonScrollListView instead of Listview.
package comabc.xyz.landscapeapp;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.Toolbar;
import android.widget.ViewFlipper;
public class home extends Fragment {
int pos = 0;
ViewFlipper v_flipper;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_home, container, false);
return view;
}
#Override
public void onViewCreated(#NonNull final View view, #Nullable Bundle savedInstanceState) {
NonScrollListView listView = (NonScrollListView) view.findViewById(R.id.listview);
customAdapter customAdapter = new customAdapter(getActivity());
listView.setAdapter(customAdapter);
listView.setFocusable(false);
customAdapter.notifyDataSetChanged();
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("listview click", "onItemClick: ");
/* FragmentTransaction fr = getFragmentManager().beginTransaction().replace(R.id.fragment_container, new productdisplay());
fr.putExtra("Position", position);
fr.addToBackStack("tag");
fr.commit();*/
Intent intent = new Intent(getActivity(), productdisplay.class);
intent.putExtra("Position", position);
startActivity(intent);
}
});
//image slider
int images[] = {R.drawable.slide1, R.drawable.slide2, R.drawable.slide3};
v_flipper = view.findViewById(R.id.v_flipper);
for (int image : images) {
flipperImages(image);
}
}
private void flipperImages(int image) {
ImageView imageView = new ImageView(getActivity());
imageView.setBackgroundResource(image);
v_flipper.addView(imageView);
v_flipper.setFlipInterval(4000);
v_flipper.setAutoStart(true);
v_flipper.setInAnimation(getActivity(), android.R.anim.slide_in_left);
v_flipper.setOutAnimation(getActivity(), android.R.anim.slide_out_right);
}
}
Note: I used Fragments here.
Ok, here 's my answer. The method that fixes the ListView height is closed enough, but not perfect. In case that most of the items are the same height, that work well. But in case that's not, then there's a big problem. I've tried many time, and when I put out the value of listItem.getMeasureHeight and listItem.getMeasuerWidth into the log, I saw the width values vary a lot, which is not expected here, since all the item in the same ListView should have the same width. And there go the bug :
Some used measure(0 ,0), which actually made the view unbound, in both direction, and width run wild. Some tried to getWidth of listView, but then it return 0, meaningless.
When I read further into how android render the View, I realize that all of this attempt can't reach the answer that I searched for, unless these function run after the view is render.
This time I use the getViewTreeObserver on the ListView that I want to fix height, then addOnGlobalLayoutListener. Inside this method, I declare a new OnGlobalLayoutListener, in which, this time, getWidth return the actual width of the ListView.
private void getLayoutWidth(final ListView lv, final int pad){
//final ArrayList<Integer> width = new ArrayList<Integer>();
ViewTreeObserver vto = lv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
lv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//width.add(layout.getMeasuredWidth());
int width = lv.getMeasuredWidth();
ListUtils.setDynamicHeight(lv, width, pad);
}
});
}
public static class ListUtils {
//private static final int UNBOUNDED = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
public static void setDynamicHeight(ListView mListView, int width, int pad) {
ListAdapter mListAdapter = mListView.getAdapter();
mListView.getParent();
if (mListAdapter == null) {
// when adapter is null
return;
}
int height = 0;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(width - 2*pad, View.MeasureSpec.EXACTLY);
for (int i = 0; i < mListAdapter.getCount(); i++) {
View listItem = mListAdapter.getView(i, null, mListView);
listItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
//listItem.measure(UNBOUNDED, UNBOUNDED);
height += listItem.getMeasuredHeight() + 2*pad;
Log.v("ViewHeight :", mListAdapter.getClass().toString() + " " + listItem.getMeasuredHeight() + "--" + listItem.getMeasuredWidth());
}
ViewGroup.LayoutParams params = mListView.getLayoutParams();
params.height = height + (mListView.getDividerHeight() * (mListAdapter.getCount() - 1));
mListView.setLayoutParams(params);
mListView.requestLayout();
}
}
The value pad, is the padding that I set in ListView layout.
If for some reason you don't want to use addHeaderView and addFooterView, e.g. when you have several lists, a good idea would be to reuse ListAdapter to populate a simple LinearLayout so there's no scrolling functionality.
If you already have a whole fragment derived from ListFragment and want to convert it to a similar fragment with simple LinearLayout without scrolling instead (e.g. to put it in ScrollView), you can implement an adapter fragment like this:
// converts listFragment to linearLayout (no scrolling)
// please call init() after fragment is inflated to set listFragment to convert
public class ListAsArrayFragment extends Fragment {
public ListAsArrayFragment() {}
private ListFragment mListFragment;
private LinearLayout mRootView;
// please call me!
public void init(Activity activity, ListFragment listFragment){
mListFragment = listFragment;
mListFragment.onAttach(activity);
mListFragment.getListAdapter().registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
refreshView();
}
});
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// create an empty vertical LinearLayout as the root view of this fragment
mRootView = new LinearLayout(getActivity());
mRootView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mRootView.setOrientation(LinearLayout.VERTICAL);
return mRootView;
}
// reusing views for performance
// todo: support for more than one view type
ArrayList<View> mViewsToReuse = new ArrayList<>();
ArrayList<View> mCurrentViews = new ArrayList<>();
// re-add views to linearLayout
void refreshView(){
// remove old views from linearLayout and move them to mViewsToReuse
mRootView.removeAllViews();
mViewsToReuse.addAll(mCurrentViews);
mCurrentViews.clear();
// create new views
for(int i=0; i<mListFragment.getListAdapter().getCount(); ++i){
View viewToReuse = null;
if(!mViewsToReuse.isEmpty()){
viewToReuse = mViewsToReuse.get(mViewsToReuse.size()-1);
mViewsToReuse.remove(mViewsToReuse.size()-1);
}
final View view = mListFragment.getListAdapter().getView(i, viewToReuse, mRootView);
ViewGroup.LayoutParams oldParams = view.getLayoutParams();
view.setLayoutParams(new LinearLayout.LayoutParams(oldParams.width, oldParams.height));
final int finalI = i;
// pass click events to listFragment
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListFragment.onListItemClick(null, view, finalI, finalI);
}
});
mRootView.addView(view);
mCurrentViews.add(view);
}
}
You may also want to forward onCreate, onPause, onResume, etc. to the original fragment depending on your needs or try inheritance instead of composition (but override certain methods so original fragment is not actually attached to layout hierarchy); but I wanted to isolate original fragment as much as possible, because we only need to extract its ListAdapter. If you call original fragment's setListAdapter in onAttach, that's probably enough.
Here's how to use ListAsArrayFragment to include OriginalListFragment without scrolling. In parent activity's onCreate:
ListAsArrayFragment fragment = (ListAsArrayFragment) getFragmentManager().findFragmentById(R.id.someFragmentId);
OriginalListFragment originalFragment = new OriginalListFragment();
fragment.init(this, originalFragment);
// now access originalFragment.getListAdapter() to modify list entries
// and remember to call notifyDatasetChanged()
found a solution for scrollview -> viewpager -> FragmentPagerAdapter -> fragment -> dynamic listview, but im not the author.
public class CustomPager extends ViewPager {
private View mCurrentView;
public CustomPager(Context context) {
super(context);
}
public CustomPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mCurrentView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int height = 0;
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = mCurrentView.getMeasuredHeight();
if (h > height) height = h;
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void measureCurrentView(View currentView) {
mCurrentView = currentView;
this.post(new Runnable() {
#Override
public void run() {
requestLayout();
}
});
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}
}
public class MyPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
private int mCurrentPosition = -1;
public MyPagerAdapter(FragmentManager fm) {
super(fm);//or u can set them separately, but dont forget to call notifyDataSetChanged()
this.fragments = new ArrayList<Fragment>();
fragments.add(new FirstFragment());
fragments.add(new SecondFragment());
fragments.add(new ThirdFragment());
fragments.add(new FourthFragment());
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position != mCurrentPosition) {
Fragment fragment = (Fragment) object;
CustomPager pager = (CustomPager) container;
if (fragment != null && fragment.getView() != null) {
mCurrentPosition = position;
pager.measureCurrentView(fragment.getView());
}
}
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
fragments layout can be anything
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent" tools:context="nevet.me.wcviewpagersample.FirstFragment">
<ListView
android:id="#+id/lv1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#991199"/>
</LinearLayout>
then somewhere just
lv = (ListView) view.findViewById(R.id.lv1);
lv.setAdapter(arrayAdapter);
setListViewHeightBasedOnChildren(lv);
}
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null)
return;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(),
View.MeasureSpec.UNSPECIFIED);
int totalHeight = 0;
View view = null;
for (int i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, listView);
if (i == 0)
view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth,
LinearLayout.LayoutParams.WRAP_CONTENT));
view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
In xml:
<com.example.util.NestedListView
android:layout_marginTop="10dp"
android:id="#+id/listview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="#null"
android:layout_below="#+id/rl_delivery_type" >
</com.example.util.NestedListView>
In Java:
public class NestedListView extends ListView implements View.OnTouchListener, AbsListView.OnScrollListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null, this);
//now it will not throw a NPE if listItem is a ViewGroup instance
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}
Just call this function after assign adapter to listview
public static void setListViewHeightBasedOnChildren
(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) return;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(),
View.MeasureSpec.UNSPECIFIED);
int totalHeight = 0;
View view = null;
for (int i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, listView);
if (i == 0) view.setLayoutParams(new
ViewGroup.LayoutParams(desiredWidth,
ViewGroup.LayoutParams.WRAP_CONTENT));
view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() *
(listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
if you what show all items in listView inside ScrollView use this code
val params: ViewGroup.LayoutParams = listView!!.layoutParams
params.height = useitemsList.size * 200 //add static height
listView!!.layoutParams = params
listView!!.requestLayout()
Just set the value of required height in a listview height attribute inside a parent scrollview. It will scroll along with other parents child item.
This worked for me (link1, link2):
You Create Custom ListView Which is non Scrollable
public class NonScrollListView extends ListView {
public NonScrollListView(Context context) {
super(context);
}
public NonScrollListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = View.MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
In Your Layout File
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- com.Example Changed with your Package name -->
<com.thedeveloperworldisyours.view.NonScrollListView
android:id="#+id/lv_nonscroll_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.thedeveloperworldisyours.view.NonScrollListView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/lv_nonscroll_list" >
<!-- Your another layout in scroll view -->
</RelativeLayout>
</RelativeLayout>
</ScrollView>
Create a object of your customListview instead of ListView like :
NonScrollListView non_scroll_list = (NonScrollListView) findViewById(R.id.lv_nonscroll_list);

android: listview in listview

i'm trying to place a listview inside a listviewitem. the inner listview should not be scrollable but take all size it needs to display all it's rows. is there a better way to to this? table, grid, ...? the problem i'm facing right now is that the inner listview doesn't take the space it needs, so it's cut at about the end of the first listitem. if i try to scroll, just the outer listview is scrolling which is exactly what i want.
thanks, my final solution is
LinearLayout layout = (LinearLayout) row.findViewById(R.id.LLBroadcasts);
layout.removeAllViews();
for (Item b : bs.getItems()) {
View child = _inflater.inflate(R.layout.item_row, null);
TextView tvTitle = (TextView) child.findViewById(R.id.TVItemTitle);
tvTitle.setText(b.getTitle());
TextView tvDesc = (TextView) child.findViewById(R.id.TVItemDescription);
tvDesc.setText(b.getDescription());
layout.addView(child);
}
From the Android documentation - Listview: ListView is a view group that displays a list of scrollable items
You do not really want to scroll that inner list view, you want to scroll the outer listview. However I asume that the inner listview may vary on the amount of elements it contains.
Instead of the inner list view you could use a
linear layout, see this tutorial or look at Adding content to a linear layout dynamically?
table layout
For the linear layout (some sample code):
// access your linear layout
LinearLayout layout = (LinearLayout)findViewById(R.id.layout);
// load the xml structure of your row
View child = getLayoutInflater().inflate(R.layout.row);
// now fill the row as you would do with listview
//e.g. (TextView) child.findViewById(...
...
// and than add it
layout.addView(child);
You should save the linear layout in a view holder (see View Holder pattern). I think the removeAllViews() is only necessary when the current row has lesser inner rows than the reused one, so I would also save the number of rows in the view holder.
If the maximum number of inner rows is not to high you could also think about caching them in the view holder to avoid the inflate and findByViewId (lets say in an ArrayList).
I have the same problem in my App but I needed to use a ListView cause it was a shared item and I didn't want to replicate equal components. So.. I just fixed the size of inner ListView programatically to show all rows and.. voila! Problem solved:
ViewGroup.LayoutParams layoutParams = innerListView.getLayoutParams();
layoutParams.height = (int) context.getResources().getDimension(R.dimen.rowheight) * innerListView.getCount();
innerListView.setLayoutParams(layoutParams);
CustomAdapter adapter = new CustomAdapter(context, blabla..);
innerListView.setAdapter(adapter);
rowListView.invalidate();
Maybe somebody will find my solution useful.
It is based on #ChrLipp answer and uses LinearLayout.
public class NotScrollableListView extends LinearLayout {
private ListAdapter adapter;
private DataChangeObserver dataChangeObserver;
private Drawable divider;
private int dividerHeight;
private List<View> reusableViews = new ArrayList<>();
public NotScrollableListView(Context context) {
super(context);
}
public NotScrollableListView(Context context, AttributeSet attrs) {
super(context, attrs);
setAttributes(attrs);
}
public NotScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setAttributes(attrs);
}
public ListAdapter getAdapter() {
return adapter;
}
public void setAdapter(ListAdapter adapter) {
if (this.adapter != null && dataChangeObserver != null) {
this.adapter.unregisterDataSetObserver(dataChangeObserver);
}
this.adapter = adapter;
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (adapter != null) {
dataChangeObserver = new DataChangeObserver();
adapter.registerDataSetObserver(dataChangeObserver);
fillContents();
}
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (adapter != null) {
adapter.unregisterDataSetObserver(dataChangeObserver);
dataChangeObserver = null;
}
}
private void fillContents() {
// clearing contents
this.removeAllViews();
final int count = adapter.getCount(); // item count
final int reusableCount = reusableViews.size(); // count of cached reusable views
// calculating of divider properties
ViewGroup.LayoutParams dividerLayoutParams = null;
if (divider != null && dividerHeight > 0) {
dividerLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dividerHeight);
}
// adding items
for (int i = 0; i < count; i++) {
// adding item
View converView = null;
if (i < reusableCount) { // we have cached view
converView = reusableViews.get(i);
}
View view = adapter.getView(i, converView, this);
if (i >= reusableCount) { // caching view
reusableViews.add(view);
}
addView(view);
// adding divider
if (divider != null && dividerHeight > 0) {
if (i < count - 1) {
ImageView dividerView = new ImageView(getContext());
dividerView.setImageDrawable(divider);
dividerView.setLayoutParams(dividerLayoutParams);
addView(dividerView);
}
}
}
}
private void setAttributes(AttributeSet attributes) {
int[] dividerAttrs = new int[]{android.R.attr.divider, android.R.attr.dividerHeight};
TypedArray a = getContext().obtainStyledAttributes(attributes, dividerAttrs);
try {
divider = a.getDrawable(0);
dividerHeight = a.getDimensionPixelSize(1, 0);
} finally {
a.recycle();
}
setOrientation(VERTICAL);
}
private class DataChangeObserver extends DataSetObserver {
#Override
public void onChanged() {
super.onChanged();
fillContents();
}
#Override
public void onInvalidated() {
super.onInvalidated();
fillContents();
}
}
}
<com.sample.ui.view.NotScrollableListView
android:id="#+id/internalList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#color/list_divider_color"
android:dividerHeight="#dimen/list_divider_width"
/>
I tried making this exact structure (a ListView inside of a ListView) and had the same problem of it only showing the first item of the inner ListView. I fixed it by changing the layout_height of the inner list from match_parent to a set dp.
It seemed to work exactly as I wanted it to.
#Try this nested class
this works for scroll listView inside listView Or 2 listviews in same activity
<com.example.taskgrptaskslistview.NestedListView
android:id="#+id/listviewTasks"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:cacheColorHint="#00000000" >
</com.example.taskgrptaskslistview.NestedListView>
</LinearLayout>
NestedListView :
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListAdapter;
import android.widget.ListView;
public class NestedListView extends ListView implements OnTouchListener, OnScrollListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null, this);
//now it will not throw a NPE if listItem is a ViewGroup instance
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}

android listview display all available items without scroll with static header

I'm having a little difficulties while trying to get a certain layout to work: I want to have list. List does not have to be scrollable, but should be shown completely. But the page itself should be able to scroll (with the lists in it), if the total content ist higher than the screen.
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#ff181818"
>
<Textview android:id="#+id/my_text" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<Textview android:id="#+id/headertext" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<ListView
android:id="#+id/my_list1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
/>
</LinearLayout>
</ScrollView>
it only uses a small part of the screen (about 2 lines per list), instead of filling the available height, and the lists themselves can be scrolled. How can I change the layout to always show the whole lists but have the screen be scrollalbe?
The solution I used is to replace ListView with LinearLayout. You can create all your items inside LinearLayout, they will all be displayed. So there's really no need to use ListView.
LinearLayout list = (LinearLayout)findViewById(R.id.list_recycled_parts);
for (int i=0; i<products.size(); i++) {
Product product = products.get(i);
View vi = inflater.inflate(R.layout.product_item, null);
list.addView(vi);
}
As #Alex noted in the accepted answer that LinearLayout is hardly a replacement. I had a problem where LinearLayout was not an option, that's when i came across this blog. I will put the code here for reference purposes. Hope it helps someone out there!
public class UIUtils {
/**
* Sets ListView height dynamically based on the height of the items.
*
* #param listView to be resized
* #return true if the listView is successfully resized, false otherwise
*/
public static boolean setListViewHeightBasedOnItems(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter != null) {
int numberOfItems = listAdapter.getCount();
// Get total height of all items.
int totalItemsHeight = 0;
for (int itemPos = 0; itemPos < numberOfItems; itemPos++) {
View item = listAdapter.getView(itemPos, null, listView);
item.measure(0, 0);
totalItemsHeight += item.getMeasuredHeight();
}
// Get total height of all item dividers.
int totalDividersHeight = listView.getDividerHeight() *
(numberOfItems - 1);
// Set list height.
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalItemsHeight + totalDividersHeight;
listView.setLayoutParams(params);
listView.requestLayout();
return true;
} else {
return false;
}
}
}
Usage:
//initializing the adapter
listView.setAdapter(adapter);
UIUtils.setListViewHeightBasedOnItems(listView);
//whenever the data changes
adapter.notifyDataSetChanged();
UIUtils.setListViewHeightBasedOnItems(listView);
You can make your own customlistview. (It can extends ListView/ExpandableListView/GridView) and override the onMeasure method with this. With this you'll never need to call a function or anything. Just use it in your xml.
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
I had a ListView in my layout and wanted to use a library which can't handle a ListView here because it wraps it into a ScrollView. The best solution for me is based on Fedor´s answer.
Since I already got an ArrayAdapter for the ListView I wanted to re-use it:
LinearLayout listViewReplacement = (LinearLayout) findViewById(R.id.listViewReplacement);
NamesRowItemAdapter adapter = new NamesRowItemAdapter(this, namesInList);
for (int i = 0; i < adapter.getCount(); i++) {
View view = adapter.getView(i, null, listViewReplacement);
listViewReplacement.addView(view);
}
For me this works fine because I just need to display dynamic data varying from 1 to 5 elements. I just had to add my own divider.
If someone still has the problem then you can make customList and add onMesure() method just like I implemented it:
public class ScrolleDisabledListView extends ListView {
private int mPosition;
public ScrolleDisabledListView(Context context) {
super(context);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int actionMasked = ev.getActionMasked() & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Record the position the list the touch landed on
mPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
return super.dispatchTouchEvent(ev);
}
if (actionMasked == MotionEvent.ACTION_MOVE) {
// Ignore move events
return true;
}
if (actionMasked == MotionEvent.ACTION_UP) {
// Check if we are still within the same view
if (pointToPosition((int) ev.getX(), (int) ev.getY()) == mPosition) {
super.dispatchTouchEvent(ev);
} else {
// Clear pressed state, cancel the action
setPressed(false);
invalidate();
return true;
}
}
return super.dispatchTouchEvent(ev);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
Check this out:
ListView ignoring wrap_content
Using android:layout_height and android:layout_weight solved it for me:
<ListView
android:layout_height="0dp"
android:layout_weight="1"
/>
I just did it using setting params of ListView
public static void setListViewHeightBasedOnChildren(ListView listView) {
//this comes from value from xml tag of each item
final int HEIGHT_LARGE=75;
final int HEIGHT_LARGE=50;
final int HEIGHT_LARGE=35;
ViewGroup.LayoutParams params = listView.getLayoutParams();
int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
switch(screenSize) {
case Configuration.SCREENLAYOUT_SIZE_LARGE:
params.height =(int) (HEIGHT_LARGE*size);
break;
case Configuration.SCREENLAYOUT_SIZE_NORMAL:
params.height =(int) (HEIGHT_NORMAL*size);
break;
case Configuration.SCREENLAYOUT_SIZE_SMALL:
params.height =(int) (HEIGHT_SMALL*size);
break;
}
listView.setLayoutParams(params);
}
I don't have a static header, but using HussoM's post as a clue, here is what I was able to get to work. In my scenario, the height of the items in the list was non-uniform, due to variable text sentences in each of the items, and I am using wrap_content for the height and match_parent for the width.
public class NonScrollableListView extends ListView {
public NonScrollableListView(Context context) {
super(context);
}
public NonScrollableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Measure the height of all the items in the list and set that to be the height of this
* view, so it appears as full size and doesn't need to scroll.
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ListAdapter adapter = this.getAdapter();
if (adapter == null) {
// we don't have an adapter yet, so probably initializing.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int totalHeight = 0;
// compute the height of all the items
int itemCount = adapter.getCount();
for (int index=0; index<itemCount; index++) {
View item = adapter.getView(index, null, this);
// set the width so it can figure out the height
item.measure(widthMeasureSpec, 0);
totalHeight += item.getMeasuredHeight();
}
// add any dividers to the height
if (this.getDividerHeight() > 0) {
totalHeight += this.getDividerHeight() * Math.max(0, itemCount - 1);
}
// make it so
this.setMeasuredDimension(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY));
}
}
If all items has the same height
int totalItemsHeight = baseDictionaries.size() * item.getMeasuredHeight();
int totalDividersHeight = listView.getDividerHeight() * (baseDictionaries.size() - 1);
int totalPadding = listView.getPaddingBottom() + listView.getPaddingTop();
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) listTranslationWords.getLayoutParams();
lp.height = totalItemsHeight + totalDividersHeight + totalPadding;
listTranslationWords.setLayoutParams(lp);
Iam supprised no one see this.U cant have two scrolls on the same layout. 1st u have a scrollview and then u have a list, i bet u are killing some android good practices there.
If you want a simple solution to this problem without extending ListView class, this is a solution for you.
mListView.post(new Runnable() {
#Override
public void run() {
int height = 0;
for(int i = 0; i < mListView.getChildCount();i++)
height += mListView.getChildAt(i).getHeight();
ViewGroup.LayoutParams lParams = mListView.getLayoutParams();
lParams.height = height;
mListView.setLayoutParams(lParams);
}
});
In my case, I had ListView inside ScrollView and scrollview was shrinking listview by default. So I just add this in my ScrollView and it worked for me
android:fillViewport="true"
Set android:layout_height="fill_parent" in your LinearLayout

Categories

Resources