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;
}
}
Related
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);
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>
I'm trying to achieve this effect that can be seen above on StickyListHeaders's sample app:
Basically I need to show a single, static, fixed header view on top of a ListView but bellow its scrollbar. I don't need anything related to sections or alphabetical indexing or anything like that.
I'm unable to figure out how to do this based on the source code of StickyListHeaders. I tried subclassing ListView and overriding dispatchDraw() like this:
protected void dispatchDraw(Canvas canvas)
{
View view = LayoutInflater.from(getContext()).inflate(R.layout.header, this, false);
drawChild(canvas, view, getDrawingTime());
super.dispatchDraw(canvas);
}
But it doesn't work, no header is drawn.
Answering my own question. This ListView subclass is able to do what I wanted. The first element of the list can become fixed calling showFixedHeader():
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;
public class FixedHeaderListView extends ListView
{
private View fixedHeader = null;
private boolean fixedHeaderLayoutDone = false;
private boolean showFixedHeader = true;
#SuppressWarnings("unused")
public FixedHeaderListView(Context context)
{
super(context);
}
#SuppressWarnings("unused")
public FixedHeaderListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#SuppressWarnings("unused")
public FixedHeaderListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public void showFixedHeader(boolean show)
{
this.showFixedHeader = show;
requestLayout(); // Will cause layoutChildren() and dispatchDraw() to be called
}
#Override
protected void layoutChildren()
{
super.layoutChildren();
if (!fixedHeaderLayoutDone)
{
ListAdapter adapter = getAdapter();
if (adapter != null && adapter.getCount() > 0)
{
// Layout the first item in the adapter's data set as the fixed header
fixedHeader = adapter.getView(0, null, this);
if (fixedHeader != null)
{
// Measure and layout
LayoutParams layoutParams = (LayoutParams)fixedHeader.getLayoutParams();
if (layoutParams == null)
{
layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
int heightMode = MeasureSpec.getMode(layoutParams.height);
if (heightMode == MeasureSpec.UNSPECIFIED)
{
heightMode = MeasureSpec.EXACTLY;
}
int heightSize = MeasureSpec.getSize(layoutParams.height);
int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
if (heightSize > maxHeight)
{
heightSize = maxHeight;
}
int widthSpec = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
fixedHeader.measure(widthSpec, heightSpec);
fixedHeader.layout(0, 0, fixedHeader.getMeasuredWidth(), fixedHeader.getMeasuredHeight());
// Flag as layout done
fixedHeaderLayoutDone = true;
}
}
}
}
#Override #SuppressWarnings("NullableProblems")
protected void dispatchDraw(Canvas canvas)
{
super.dispatchDraw(canvas);
if (fixedHeader != null && showFixedHeader)
{
drawChild(canvas, fixedHeader, getDrawingTime());
}
}
}
It's not heavily tested, but it's a good starting point.
EDIT: been working on it recently again and got it working, so don't need an answer anymore.
link
I've made an overscrollable listview and everything works except when you scroll beyond the last item with the direction pad/trackball. When scrolling with finger on screen, all the checking for top and bottom overscrolling work, even when scrolling with d-pad/trackball beyond the first item in the list... but just not the bottom side. That's why it's freaking me out, cuz the other checks all work... i'm not posting the whole activity cuz it's pretty big, i'll just post what i think is necesarry. All the checking is done in the OverscrollListview class.
Activity:
package com.somepackage;
public class SomeActivity extends ListActivity
{
private OverscrollListview mNotesList;
protected void onCreate(Bundle savedInstanceState)
{
mNotesList = (OverscrollListview) findViewById(android.R.id.list);
/* just a view in xml that can be sized so that the header and footer are always
the height of the complete screen no matter what device it runs on ...
*/
header = LayoutInflater.from(this).inflate(R.layout.listview_overscrollview, null);
header.findViewById(R.id.overscroll)
.setMinimumHeight(getWindowManager()
.getDefaultDisplay()
.getHeight());
mNotesList.addHeaderView(header, null, false);
mNotesList.addFooterView(header, null, false);
mNotesList.setOnScrollListener(mNotesList);
mNotesList.setAdapter(mainadapter);
populateNotesList();
}
...
}
public void populateNotesList()
{
...
// whenever the listview gets populated, these values need to be 0 again
mNotesList.item = mNotesList.itemOffset = 0;
}
The overscrollview class:
package com.somepackage;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Toast;
public class OverscrollListview extends ListView implements OnScrollListener
{
public int item = 0, itemOffset = 0, first = 0, count = 0, total = 0;
private int currentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
private Handler mHandler = new Handler();
private Toast toast;
private View listitem;
public OverscrollListview(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
toast = Toast.makeText(context, "", Toast.LENGTH_LONG);
}
public OverscrollListview(Context context, AttributeSet attrs)
{
super(context, attrs);
toast = Toast.makeText(context, "", Toast.LENGTH_LONG);
}
public OverscrollListview(Context context)
{
super(context);
toast = Toast.makeText(context, "", Toast.LENGTH_SHORT);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
first = firstVisibleItem;
count = visibleItemCount;
total = totalItemCount;
mHandler.postDelayed(checkListviewTopAndBottom, 100);
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState)
{
currentScrollState = scrollState;
mHandler.postDelayed(checkListviewTopAndBottom, 100);
}
private final Runnable checkListviewTopAndBottom = new Runnable()
{
#Override
public void run() {
toast.setText("first="+first+"\ncount="+count+"\nlast="+getLastVisiblePosition()+"\ntotal="+total+"\nitem="+item);
toast.show();
if ( getCount() <= 2 ) return; // do nothing, listview has no items, only header & footer
if ( currentScrollState != OnScrollListener.SCROLL_STATE_IDLE ) return; // do nothing, still scrolling
if ( getFirstVisiblePosition() < 1 ) {
setSelectionFromTop(1, getDividerHeight());
return;
}
if ( getLastVisiblePosition() == getCount()-getHeaderViewsCount() ) {
if ( item == 0 ) {
if ( getFirstVisiblePosition() < 1 ) {
item = 1;
itemOffset = getDividerHeight();
} else {
item = getCount()-getHeaderViewsCount();
listitem = getChildAt(item);
if ( listitem != null ) {
itemOffset = getHeight()-listitem.getHeight()+getDividerHeight();
} else {
itemOffset = getHeight()+getDividerHeight();
}
}
}
//toast.setText("LastVisPos()==getCount()-1\nitem="+item+"\nitemOffset="+itemOffset);
//toast.show();
if ( item == getCount()-getHeaderViewsCount() || (item == 1 && getFirstVisiblePosition() < 1) ) {
setSelectionFromTop(item, itemOffset);
}
}
}
};
}
Although the majority of it works, which i can live with, i'd appreciate if somebody could see what's going wrong cuz it's just buggin' me...
thanks.
This is not answers your main quesion, just wanted to leave some notes.
The height of the header/footer may be set in this way:
public class OverscrollListview extends ListView implements OnScrollListener
{
// ...
#Override
protected void layoutChildren() {
View v = findViewById(R.id.HEADER_VIEW_ID);
ViewGroup.LayoutParams lp = v.getLayoutParams();
lp.height = getHeight();
v.setLayoutParams(lp);
v = findViewById(R.id.FOOTER_VIEW_ID);
v.setLayoutParams(lp);
super.layoutChildren();
}
}
It seems this is more convenient way. In this way the height of header/footer can be set to the height of its LsitView.
I have layout having expandablelistview. and in that, each child item is having gridview. but gridview is having own scrolling and expandablelistview is having also. so I must have to set fix length for all gridview but there are dynamic number of gridviews.
I found solution for single gridview from here.
private OnGlobalLayoutListener mOnGlobalLayoutGridListener = new OnGlobalLayoutListener() {
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
Debug.e("", "onGlobalLayout is called");
if (gridView != null && gridView.getChildCount() > 0) {
gridView.getViewTreeObserver().removeGlobalOnLayoutListener(
this);
// gridView.get
View lastChild = gridView
.getChildAt(gridView.getChildCount() - 1);
int rows = gridView.getAdapter().getCount() / numColumns;
int extra = gridView.getAdapter().getCount() % numColumns;
if (extra > 0) {
rows++;
}
int height = (int) (lastChild.getMeasuredHeight() * rows);
gridView.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, height));
}
}
};
OnGlobalLayoutListener does not reference to particular Gridview. so that I can not use this listener for all gridviews because all gridviews are having different-different number of items.
see here
can anyone help me?
Thanks in advance.
You can use a custom GridView like this,then you don't have to set fix length for all gridview :
public class GridlistView extends GridView{
public GridlistView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
int expendSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expendSpec);
}
}
use the Gridlistview in your xml;