I've got and ExpandableList which I assign to an extended BaseExpandableListAdapter. In here, I implement the getChildView method to setup and return a View for each child:
public View getChildView(int groupIndex, int childIndex, boolean isLastChild, View convertView, ViewGroup parent) {
LinearLayout layout;
if (convertView == null) {
layout = (LinearLayout) LayoutInflater.from(MyApp.getContext()).inflate(R.layout.rooms_room_list_row, parent, false);
} else {
layout = (LinearLayout) convertView;
}
// Do some custom stuff with views on the layout
...
return layout;
}
While debugging, I've noticed that getChildView is executed twice per child. All the values passed in (i.e, groupIndex, childIndex, isLastChild) are the same... so in group 3, if I've got two children, I'll see:
groupIndex = 3, childIndex = 0
then
groupIndex = 3, childIndex = 1
then it repeats:
groupIndex = 3, childIndex = 0
and finally:
groupIndex = 3, childIndex = 1
My view appears fine, i.e, there are only two children listed for the group, but why is it doing all the extra work?
UPDATE & ANSWER
It seems that if the the listview is set to a height of wrap_content then the getChildView will be called twice per child. Changing the height to fill_parent seems to fix this behavior.
As I mentioned in your other question, the ListView calls getView() to get the dimensions (width and height) of some items first, then calls the getView() again to actually render the items. Check this video out, it's "required reading" for Android. The bit that deals with your question is at minute 41:30.
Related
I would like the rows in a ListView to be sized so that exactly six of them can fit on the screen. For that, I would need to know how much vertical space is available to the ListView (not the whole screen). However, no measuring can be done in onCreate() since no views have been rendered yet.
If I make measurements after rendering, the ListView might be drawn and then resized, which may be distracting. What is the smartest way to establish the necessary row height before rendering the ListView?
in onCreate you can get the height of your screen and divide by 6.
Now in your getView you get the reference of the top layout for each item, suppost you have named it's id to root and i.e it's a LinearLayout.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null){ some inflate }
LinearLayout root = (LinearLayout) view.findViewById(R.id.root);
LayoutParams lp = root.getLayoutParams();
lp.height = screenHeight/6;
root.setLayoutParams(lp);
....
return view;
}
Yes, this assumes the ListView is in fullscreen.
If you have other layouts, you will have to get those height into account.
Then your height will be: int heightForEachItem = (screenHeight - otherlayoutsHeightTogether) / 6;
Turns out that the earliest you can measure a ListView is in the onGlobalLayout() callback.
Here is my example.
params = new AbsListView.LayoutParams(-1,-1);
listview.getViewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
#Override
public void onGlobalLayout(){ //this is called just before rendering
params.height = listview.getHeight()/6; // this is what I was looking for
listview.getViewTreeObserver.removeOnGlobalLayoutListener(this); // this is called very often
}
adapter = new ArrayAdapter<...>(int position, ...){
#Override
public View getView(...){
LinearLayout item = new LinearLayout(context);
item.setLayoutParams(params);
// add text, images etc with getItem(position) and item.addView(View)
return item;
}
}
listview.setAdapter(adapter);
I want to be able to take a ListView and have a specific row be scrollable to the top of that Listview's bounds, even if the row is near the end and normally wouldn't be able to scroll that high in a normal android ListView (similar to how twitter works when you drill into a specific tweet and that tweet is always scrollable to the top even when there's nothing underneath it.)
Is there any way I can accomplish this task easily? I've tried measuring the row i want to scroll to the top and applying bottom padding to account for the extra space it would need, but that yields odd results (i presume because changing padding and such during the measure pass of a view is ill advised). Doing so before the measure pass doesn't work since the measured height of the cell in question (and any cells after it) hasn't happened yet.
Looks like you the setSelectionFromTop method of listview.
mListView.setSelectionFromTop(listItemIndex, 0);
I figured it out; its a bit complex but it seems to work mostly:
public int usedHeightForAndAfterDesiredRow() {
int totalHeight = 0;
for (int index = 0; index < rowHeights.size(); index++) {
int height = rowHeights.get(rowHeights.keyAt(index));
totalHeight += height;
}
return totalHeight;
}
#Override
public View getView(int position, View convertView, final ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if (measuringLayout.getLayoutParams() == null) {
measuringLayout.setLayoutParams(new AbsListView.LayoutParams(parent.getWidth(), parent.getHeight()));
}
// measure the row ahead of time so that we know how much space will need to be added at the end
if (position >= mainRowPosition && position < getCount()-1 && rowHeights.indexOfKey(position) < 0) {
measuringLayout.addView(view, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
measuringLayout.measure(MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);
rowHeights.put(position, view.getMeasuredHeight());
measuringLayout.removeAllViews();
view.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (position == getCount()-1 && view.getLayoutParams().height == 0) {
// we know how much height the prior rows take, so calculate the last row with that.
int height = usedHeightForAndAfterDesiredRow();
height = Math.max(0, parent.getHeight() - height);
view.getLayoutParams().height = height;
}
return view;
}
This is in my adapter. It's a subclass of a merge adapter, but you can just put it in your code and substitute the super call with however you generate your rows.
the first if statement in getView() sets the layout params of a frame layout member var that is only intended for measuring, it has no parent view.
the second if statement calculates all the row heights for rows including and after the position of the row that I care about scrolling to the top. rowHeights is a SparseIntArray.
the last if statement assumes that there is one extra view with layout params already set at the bottom of the list of views whose sole intention is to be transparent and expand at will. the usedHeightForAndAfterDesiredRow call adds up all the precalculated heights which is subtracted from the parent view's height (with a min of 0 so we don't get negative heights). this ends up creating a view on the bottom that expands at will based on the heights of the other items, so a specific row can always scroll to the top of the list regardless of where it is in the list.
I try to animate a single item in my ListView, I've figured out that this is kinda hard. But, after some googling I came up with some codesnippets that I combined, but when I try to call my ListView's getChildAt in order to return the View, this returns null.
public void animateSingleItemInListView() {
final Animation animBounce = AnimationUtils.loadAnimation(context, R.anim.bounce);
int totalItemsInListView = lastCases.getCount();
int wantedPosition = 1; // Whatever position you're looking for
int firstPosition = lastCases.getFirstVisiblePosition() - lastCases.getHeaderViewsCount(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
// Say, first visible position is 8, you want position 10, wantedChild will now be 2
// So that means your view is child #2 in the ViewGroup:
int childs = lastCases.getChildCount();
if (wantedChild < 0) {
Log.w("UPDATEUI", "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
// Could also check if wantedPosition is between listView.getFirstVisiblePosition() and listView.getLastVisiblePosition() instead.
View wantedView = lastCases.getChildAt(firstPosition);
wantedView.setAnimation(animBounce);
}
From the code:
The totalItemsInListView returns 10, which is correct number of rows in my ListView. The integer firstPosition is of course 0, but the getChildAt() method only returns null. How come? How can I get the first View from my ListView?
try to use
lastCases.getItemAtPosition(firstPosition)
I am trying to change gridView column number. I call setNumColumn() and invalidateViews() to update the view.
However, the grid's cell width will not change dynamically. I set the stretchMode="columnWidth", but it didn't work.
Problem Solved:
The child view in the grid view do not re-calculate its layout when the grid view changes the column on runtime.
In your BaseAdapter, you should call forceLayout() to calculate its layout.
#Override
public View getView(final int position, View convertView,
ViewGroup parent) {
if (null == convertView) {
convertView = mInflater.inflate(
R.layout.mnm_customer_user_thumbnail, null);
control.txtUserName = (TextView) convertView
.findViewById(R.id.mnmTxtStudentName);
control.image = (ImageView) convertView
.findViewById(R.id.mnmImageStudent);
control.imageCheckBox = (ImageView) convertView
.findViewById(R.id.mnmImageCheckBox);
control.mnmOuterBounder = (RelativeLayout) convertView
.findViewById(R.id.mnmOuterBound);
convertView.setTag(control);
} else {
convertView.forceLayout();
}
...
...
...
...
Thanks
May be you could get help through this nice sample offered by Google
http://developer.android.com/shareables/training/BitmapFun.zip
The following code which locate in the ImageGridFragment.java is used to handle column number change dynamically during runtime such as screen orientation change when rotate the device:
Here is the comments:This listener is used to get the final width of the GridView and then calculate the number of columns and the width of each column. The width of each column is variable as the GridView has stretchMode=columnWidth. The column width is used to set the height of each view so we get nic square thumbnails.
mGridView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (mAdapter.getNumColumns() == 0) {
final int numColumns = (int) Math.floor(
mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing));
if (numColumns > 0) {
final int columnWidth =
(mGridView.getWidth() / numColumns) - mImageThumbSpacing;
mAdapter.setNumColumns(numColumns);
mAdapter.setItemHeight(columnWidth);
if (BuildConfig.DEBUG) {
Log.d(TAG, "onCreateView - numColumns set to " + numColumns);
}
}
}
}
});
return v;
in xml u have to provide this attribute..
android:stretchMode="columnWidth" . In your adapter depending on the size(by `getCount()`) of the list colomn will create dynamically..
I am creating a scrolling panel with many child views (e.g. buttons). Each child view has a fixed location based on their row and column index. I cannot create them at the beginning since there are tons of them and I will run out of memory, so I'd like to only add a child view when it intersects with the screen view port (when users scrolls to that area). I override the onLayout() method with something like this:
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
for (int row = 0; row < ROW_NUM; row++) {
ColumnAdapter columnAdapter = mRowAdapter.getItem(row);
for (int col = 0; col < COLUMN_NUM; col++) {
ItemView itemView = columnAdapter.getView(col, null, this);
if (isOnScreen(row, col)) {
itemView.layout(col * 100, row * 100, (col + 1) * 100, (row + 1) * 100);
addViewInLayout(itemView, row + col, null, true);
}
}
}
scrollTo(getScrollX(), getScrollY());
}
(ColumnAdapter is an Adapter extension and ItemView is button extension). This wouldn't work because onLayout() is not called during scrolling. What should I do to add ItemViews dynamically as user scrolls?
Never mind I figured that you can use requestLayout()...
You may also want to consider using a ListView. It does exactly what you want, that is a lazy load of its elements. See the Google IO session on ListView for details.
In short, the Adapter class you are extending has a View getView(int position, View convertView, ViewGroup parent) function. This function can be overriden and doing a check to see if convertView is not null and loading that since it is actually a previously created View of that child element.
There are also additional details on avoiding the findViewById() function call since it is expensive.