Add child views when scrolling in (extened) AdapterView - android

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.

Related

How to get First Visible child item of android scrollview on scrollChange event

I want to get First visible child view from android scrollview, on each scroll change event.
Like, when we scroll down, we get all the upcoming view id or reference that's just being visible on screen. How?
I got it on my own.. Here is my code:
Here, we need to create our custom callback listener. Inside its onScrollChanged(), We get int t which is scroll's Y position. Extra, we need child Row height and then just divide that t by rowHeight. Done. We can use getChildAt() further.
scrollView.setOnScrollViewListener(new OnScrollViewListener() {
#Override
public void onScrollChanged(ScrollViewEx v, int l, int t, int oldl,int oldt) {
int rowHeight = rowView.getHeight();
int firstPos = t / rowHeight;
scrollMainChildLL = (LinearLayout) v.getChildAt(0);
gotTxtLL = (LinearLayout) scrollMainChildLL.getChildAt(firstPos);
}
}

Android - do ViewGroup onDraw need to iterate through child view and call onDraw explicitly?

I have spent the whole day debugging various ways to add custom ViewGroup into another custom ViewGroup and nearly went crazy because none of them works, and there is no official documentation or sample that shows how it can be done...
Basically, I have 2 custom ViewGroup:
HorizontalDockView extends ViewGroup
GameEntryView extends FrameLayout
HorizontalDockView overrides onDraw, onMeasure, etc and everything is called normally and works perfectly.
However, when I create GameEntryView from inside HorizontalDockView's constructor and call addView(gameEntryView), the gameEntryView will never ever show regardless of the layoutParams, addView called from whatever thread, or however I call, load, and setContentView on the parent HorizontalDockView. If I list through the horizontalDockView.getChildAt(); all the gameEntryView objects are still there.
Hopeless, I try to debug through GameEntryView's onDraw, onMeasure, dispatchDraw methods and realized none of them actually get called! No.. not even once!
Do I need to iterate through all the child view in the parent (HorizontalDockView's) on* call and call the children's on* explicitly? I was just calling super.on*() on the parent.
I did call setWillNotDraw( false ); on both the parent and the child class.
How do I get the child to show up inside the parent's view? simple sample or existing small open source project is highly appreciated!
Thank you very much!
Did you overwrite onLayout? When Android lays out your ViewGroup, your ViewGroup is responsible for laying out the children.
This code is from a custom ViewGroup that lays out all children on top of each other:
#Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
int count = this.getChildCount();
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
For completeness, the onMeasure override:
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth, parentHeight);
int count = this.getChildCount();
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
this.measureChild(
child,
MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY));
}
}

A ListView where a specific row can always be scrolled to the top?

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.

Android Viewgroup: fade/blend two views on top of each other

I want to fade from one view to another on in a ViewGroup.
At the moment I'm doing the transition using setAlpha, but the problem is that only one view is being rendered, the one that was on top and is fading out.
Is the view-array inside ViewGroup an order by z-axis?
Is only the top view being rendered?
My layout method looks like this:
#Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
L.debug("laying out {} children", this.getChildCount());
for (int i = 0; i < this.getChildCount(); i++) {
L.debug("layout out {}", i);
View view = this.getChildAt(0);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
}
Why don't you want to use ViewSwitcher? It does exactly what you want. Here is an example.
Is the view-array inside ViewGroup an order by z-axis?
There is no such thing like Z-order in android. Views are drawn in oder they were added to ViewGroup. First added draws first.
Is only the top view being rendered
No, android will draw all views in visible rect even if they are totally overlaped by others.
I think you should fix this place this.getChildAt(0) and layout all childrens in your ViewGroup.

Calculating the height of each row of a ListView

I am trying to make the background of a ListView scroll with the ListView. I am basing my approach on this class in Shelves, but while in Shelves everything has the same height, I can't make the same guarantee.
I have an activity like so:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.listView);
List<String> items = new ArrayList<String>();
for(int i=0; i < 100; ++i) {
items.add("Hello " + i);
}
CustomArrayAdapter adapter = new CustomArrayAdapter(this, items);
listView.setAdapter(adapter);
}
}
Where CustomArrayAdapter is:
public class CustomArrayAdapter extends ArrayAdapter<String> {
private List<Integer> mHeights;
public View getView(int position, View convertView, ViewGroup parent) {
//snip
}
}
What I want to do is populate mHeights in the adapter with heights of the rows of the view.
My main attempt was to do this, in getView():
if(row.getMeasuredHeight() == 0) {
row.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY));
}
mHeights.set(position, row.getMeasuredHeight());
I've tried several ways to do this, but I can't get anything to work.
In getView, if I call getHeight() I get a return value of 0, while if I call getMeasuredHeight(), I get something non-zero but not the real height. If I scroll down and then up again (taking one of the rows out of view) getHeight() == getMeasuredHeight() and both have the correct value. If I just update mHeights in getView as I go (by saying mHeights.set(position, row.getHeight()); it works - but only after I've scrolled to the bottom of the ListView. I've tried calling row.measure() within the getView method, but this still causes getHeight() to be 0 the first time it is run.
My question is this: how do I calculate and populate mHeights with the correct heights of the rows of the ListView? I've seen some similar questions, but they don't appear to be what I'm looking for. The ViewTreeObserver method seems promising, but I can't figure out how to force calls to getView() from it (alternately, to iterate the rows of the ListView). Calling adapter.notifyDataSetChanged() causes an infinite (although non-blocking) loop.
Get Actual Height of View
how to get height of the item in a listview?
This is related to my previous question on the subject, which seems to have missed the point: ListView distance from top of the list
I figured out how to do this. It's was a bit tricky, but it works.
The subtlety was getting layout parameters when they are known. As I learned, getView() is returning the view for the initial time, so of course it can't have a height yet - the parent doesn't know about the row yet (since getView()'s job is to inflate it), so we need a callback.
What might that callback be? It turns out, as best I can tell it's the OnLayoutChangeListener() for the row's view. So in getView() in my custom ArrayAdapter, I added a callback for onLayoutChanged like so:
row.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
ref.removeOnLayoutChangeListener(this);
Log.d("CustomArrayAdapter", "onLayoutChange(" + position + "): height " + (bottom - top));
mHeights.set(position, (bottom - top));
if(position > 0) {
mDistances.set(position, bottom - top + mDistances.get(position-1) + ((ListView)parent).getDividerHeight());
}
holderRef.distanceFromTop = mDistances.get(position);
Log.d("CustomArrayAdapter", "New height for " + position + " is " + mHeights.get(position) + " Distance: " + mDistances.get(position));
}
});
Here, row is the view to be returned by getView() in the adapter, mHeights is a list of the height of each element of the ListView, and mDistances is a list of the distance from the top of the list view in pixels - the real thing that I wanted to calculate (that is, if you laid out the entire list view end to end, how far from the top element i would be from the top). All of these are stored within the custom ArrayAdapter. These lists are initialized to zeroes.
Adding this callback allowed me to calculate the height of the view.
My goal was to have a ListView that has a background that scrolls with the list. If anyone is interested, I put the code up on GitHub if anyone would like to review it: https://github.com/mdkess/TexturedListView
This SO answer should work for developers who also want to support API level < 11.
Basically, instead of adding a View.onLayoutChnageListener to your view, you can set a OnGlobalLayoutListener on your view's ViewTreeObserver.

Categories

Resources