Android - Waiting till view is laid on screen without any guesswork - android

I am creating a TutorialManager that handles all the user tutoring for app usage.
I have a ViewGroup that adds views to itself including a GridView that is filled with items after onMeasure() is called.
I am calling TutorialManagers method that's supposed to perform an action on the gridview but it needs to know when the gridview is filled in order to use any of it items
I added a callback method on the ViewGroup that is called right after all the view have been added
The problem is that the TutorialManager needs to know the X and Y coordinate of the views in order to highlight them using ShowCaseView
I've added a workaround using postDelayed(action, 100); which seems to work on my handset but what if on some device the view layout takes more than 100ms? It'll screw up the whole tutoring system.
I can't figure out any other way than creating a custom view that has a callback method which is called after the views position is known.
What's the common way of handling the issue that the laying view on screen is happening asynchronously?

You can try using an OnGlobalLayoutListener which fires whenever there is a layout change (Ie, your GridView and children have been added and measured. So where ever you initialize the GridView do this:
LayoutListener mLayoutListener = new LayoutListener;
mGridView.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
private class LayoutListener implements OnGlobalLayoutListener {
#Override
public void onGlobalLayout() {
...Do your calculations here...
mGridView.getViewTreeObserver().removeOnGlobalLayoutListener(this); //If you only need this to execute once.
}
}
Note this will be called quite often. I'd recommend removing as soon as you don't need it.

Related

What methods run after ViewGroup.addView() executed?

Trying to handle addView method - calculate some parameters after changes in layout (but before showing whole view), but what methods execute after addView() runned?
OnLayoutChangeListener and OnMeasure... anything else?
After adding a view as child into another view. You should go like this.
public void someMethod() {
View parentView;
View childView = (View)findViewById(R.id.my_view);
parentView.addView(childView);
// Now perform your calculations here. which you want to modify for childView. after that just call the invalidate() method to take changes effect.
parentView.invalidate();
}
Hope this helps. Feel free to tell if answer is not as expected.

How to detect a moment when ListView item becomes ActiveView (going off-screen)

According to concept of recycling items mechanism in ListView.
I want to know, actually, how i can detect a moment when a View going off-screen.
I explain why.
In most cases ListView have a custom
Adapter(? extends ArrayAdapter / ? extends BaseAdapter, etc.).
getView(...) method allows to manipulate visibility and content of views (text,bitmaps,drawables,etc.)
And in some cases i need to launch a separate Thread which doing background work, and after that update UI. Actually - using AsyncTask.
When i have many items in ListView each call of getView will be produced start a new Thread. I need to cancel them if View is no more longer present on the screen. How to do this?
You can override onDetachedFromWindow for the view.This will let you know when the view is going off-screen.The docs says:
protected void onDetachedFromWindow ()
Added in API level 1 This is called when the view is detached from a
window. At this point it no longer has a surface for drawing.
See Also
onAttachedToWindow()

Calling ViewGroup#addView or ViewGroup#removeView from View#draw

In Android documentation, it says the following for addView (and a few other methods):
Note: do not invoke this method from draw(android.graphics.Canvas),
onDraw(android.graphics.Canvas), dispatchDraw(android.graphics.Canvas)
or any related method.
What's the reason for this restriction? Also, what are the "related methods"? For instance, is surfaceChanged such method?
It's because when rendering Views, the ViewGroup containing the Views goes through certain steps when mapping out where each view will appear on the screen. The steps a ViewGroup takes are:
Measure (the ViewGroup measures all of its child views)
Layout (the ViewGroup positions the measured children at their locations on the screen according to their measurements and ViewGroup.LayoutParams)
Draw (the views are drawn on screen).
Since the draw step is the last step when rendering Views on the screen, adding additional Views at this step of the process could potentially change (invalidate) the entire layout. If you take a look at the source code for ViewGroup you'll see that making a call to addView(View v) starts the entire layout process over again:
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
Also, when the ViewGroup draws it's child Views on the screen, it normally iterates through all of the Views currently added to the ViewGroup. So, if this type of event were to be dispatched during a draw method, it could potentially cause the ViewGroup to attempt drawing a View that hasn't gone through the necessary steps for rendering yet.
As for related methods, these would essentially be methods that would invalidate the ViewGroup's the current drawing operation.
Edit:
The SurfaceHolder.Callback methods are just Interface methods for tracking the state of your drawing surface or SurfaceView/GLSurfaceView. Since the state of the layout should remain constant until the Draw step has finished, there shouldn't really be a need to call this method at that point in the process. However, since these methods are basically Interface methods that YOU implement (they're empty by default in the source), there shouldn't really be a reason why calling one of these methods should cause an error, even though I'm not sure that it would be appropriate to do so. One case that would case an issue would be if your underlying implementation leads to issues like the one I explained above...

Infinite scroll in LinearLayout/ScrollView?

I know it's somehow possible to make a ListView that loads more data when the user has reached the bottom of the list. However, I'm working with a ScrollView, which I have a LinearLayout in, and these two components works great with the scrolling and so. But I don't know how I'm supposed to do so it gets an infinite scroll.
I suppose I need to add something that reads what is shown of the LinearLayout on the screen, and when it calculates that it is the bottom of the LinearLayout that is being shown (by using the current position and the height of the View), it triggers an event.
But as I said, I don't know how to accomplish this. So, if anyone can give me some help I would be very grateful.
EDIT: I found this post here on StackOverflow How to trigger an event when scrollView reach the bottom with Android?, but I don't know what to do with the answer:
Given the requirements, you'll likely be extending BaseAdapter (as opposed to CursorAdapter which utilizes a different mechanism).
Here's a snippet for that:
public View getView(int position, View convertView, ViewGroup parent) {
if (position == backingLinkedList.size()) {
//get more items and add them to the backingLinkedList in a background thread
notifyDataSetChanged();
}
}
If you can trigger an event when you reach the bottom of a scrollview, You could try creating your own type of view to hold your data. When you reach the bottom of the view load the next data into your view class and add it into the layout of the scroll view.
So basically create a class that holds a section of what your showing. Add the first one into the scrollview. When you hit the bottom create another view holding your next data and use scrollviews_layout.addview(view_holding_the_new_data)
I know this was solved a long time ago, but I wanted to share my solution for future infinite scrollers.
I used the onScroll method within the OnScrollListener to trigger a background thread to grab more data and adapter.notifyDataSetChanged to notify the ListView of more data to load and create an infinite scroll. For some reason, onScrollStateChanged wasn't triggering, but onScroll did what I needed so I didn't need to bother.
For the longest time I didn't understand how to keep the ListView from reloading and placing me back at the top of the list, but then I realized I only need to initialize a new adapter and call setListAdapter once, and every time after that, only call notifyDataSetChanged. Before, I was also calling setListAdapter, thereby placing me back at the top.

How to detect when a drawable is touched

I'm working on an app that has a canvas with a bunch of little images that the user can drag around, and a "box" that the user will drag from to create new image objects or drag an existing image to it, and it will remove it.
What is the best way to detect if the user has touched an area, or an image on the screen? The first thing that comes to mind is obviously comparing the touch coordinates with each object's coordinates, but that seems a little cumbersome. Is there a better, or built in way?
Thanks
It depends on how complicated your interface is, but basically if you override onTouchEvent that will get you the coordinates you were referring to.
In order to find what was touched, without going through every object, you could find ways to simplify this, such as by splitting your screen into perhaps 8 grids, and know which grid(s) every object is in, so that when the screen is touched then you can find the objects that may be in that grid, and so you will have fewer items to look through.
For a brief answer on how to use onTouchEvent you can look at:
http://androidforums.com/android-games/210019-touch-event-image-made-canvas.html
The View class in Android has an onClickListener which you can use to detect when the user clicks on a View. If you implement your 'images' (as you call them) as Views you can simply add a click listener to each to find which view was clicked. Add the listener like this:
// Get a reference to the view from xml (or if you
// have created it dynamically, just use that)
View imageView = findViewById(R.id.my_view);
imageView.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
// Do stuff with view (the View v that is passed is the View that was touched)
doStuff(v);
}
});

Categories

Resources