I have a custom View inherited from SurfaceView and I have EditText in the same RelativeLayout (both custom View and EditText are siblings in layout's xml).
When I make visible an EditText, this way
setVisibility(View.VISIBLE);
the custom View receives continuous calls of onDraw(Canvas c) method.
Does anybody know why? I never called invalidate() explicitly.
There isn't anything wrong here. This in normal behaviour. onDraw() is called a lot of times because Android redraws your activity whenever it feels there is a change it needs to display. There are a lot of sophisticated internal algorithms that decide when and why an activity is redrawn. Android takes care of it for us and we don't need to worry about it.
Once an activity is redrawn, all it's children are redrawn as well. This happens with all views and not custom views. Rest assured that there isn't any problem with your code.
Interested in learning more about the internals? Check out the source code for the View class!
Related
I have downloaded the 4.2 image for tests and I'm surprised with one thing. I have a HorizontalScrollView filled by ImageView's ancestors in my application. I rely on the fact, that onDraw in these ImageViews is called only when the view becomes explicitly visible to a user. But what I'm observing in 4.2 is that onDraw is called just at the moment of adding views to the HorizontalScrollView. Is it a supposed behaviour? Or am I missing anything?
Thanks for help.
Generally onDraw is called when the view has been inflated and after its measured.
I would never assume that onDraw means that that view is visible to a user. Even the onDisplayHint is not assurance that the view is visible.
Also worth noting the onDraw can be called multiple times, as other views move or the View hierarchy is invalidated().
Hope that helps?
If not have a good read through - Custom Views
I put a Log.d() call into the onDraw() of my extended View, so I could see how often and when it's getting called. It gets called upon instantiation of the view, which is not surprising. But then I notice, it gets called on every tap that is handled by onTouchEvent(), even though my code there isn't doing anything remotely related to graphics. However, in the documentation for Views, I can't seem to find anything about when onDraw() is actually called. I'm not really concerned about my particular project here (this doesn't cause a problem for me), I would just like to know if there is a list somewhere or something that shows the order of operations for a View, particularly what causes onDraw() to get called.
AFAIK, a View's onDraw() is called when:
The view is initially drawn
Whenever invalidate() is called on the view
Invalidate can be called by you or the system whenever needed. For example, a lot of Views change how they look onTouch, like an EditText getting an outline and cursor, or a button being in the pressed state. Due to this, Views are redrawn on touch.
I agree that it would be nice to have a document that detailed the working of Views, and if one exists and somebody knows where to find it, please let us know.
onDraw() is called when invalidate() is called.
But you should know for ViewGroups: onDraw() will not be called like you expect. Rather, onDispatchDraw().
However, in a ViewGroup you can call setWillNotDraw(false) in the constructor to make onDraw() to be called on invalidate().
Take a look at this answer
If you set a background drawable for a View, then the View will draw
it for you before calling back to its onDraw() method.
onAttachedToWindow () is called when the view is attached to a window.
At this point it has a Surface and will start drawing. Note that this
function is guaranteed to be called before
onDraw(android.graphics.Canvas), however it may be called any time
before the first onDraw -- including before or after onMeasure(int,
int).
invalidate() mark the area defined by dirty as needing to be drawn. If
the view is visible, onDraw(android.graphics.Canvas) will be called at
some point in the future.
One important thing to keep in mind is that try to minimize calling of invalidate() function with no arguments.
Instead try to maximize the invalidate() function with four arguments.As drawing whole view is very expensive.The second variant refreshes only the part of view.
Additional to the above: The soft keyboard causes a View.invalidate()-->View.onDraw() sequence after resizing the Window to sensibly accommodate the 'keyboard'. A custom View.onDraw() must leave itself in a state that anticipates this possibility.
Such phenomenum explains why the app you developed and tested on a tablet with a bluetooth keyboard went to the dogs once it reached the real world (-:
When is onDraw called (check this for more details)
The onDraw method is called whenever android thinks that your view
should be redrawn. This can be tha case when your view is animated, in
which case onDraw is called for every frame in the animation. It is
also called when the layout changes and your view is re-positioned on
the screen.
But what if some data inside your view has changed and you want to
make sure that the view is redrawn. You can’t call onDraw directly.
Instead you should call invalidate to tell the view that it needs to
redraw itself.
I have a LinearLayout that I would like to change the background color of when one of its child views (an ImageButton) is clicked. I am able to do this, but not immediately - the change doesn't occur on the screen until later (I think during an onResume call). I would like to find out how to force the layout to redraw after the background is set, before the next line of code executes. Here is the onClick method of my OnClickListener for the button:
public void onClick(View v) {
LinearLayout parentLayout = (LinearLayout) v.getParent();
parentLayout.setBackgroundResource(R.color.my_color);
SystemClock.sleep(1000); //ms
}
The sleep command is in there to test whether the redraw happens before or after it. The result: after. Most questions on this topic (like here and here) say to use invalidate() on the view. I have used the commands parentLayout.invalidate();, parentLayout.postInvalidate();, and parentLayout.refreshDrawableState(); in between the background and sleep lines, all to no avail. The redraw still happens after the sleep command. Can anyone tell me how to make it happen immediately?
Other possibly useful information: The LinearLayout is bound to the rows of a ListView, and the OnClickListener above is in a custom class that extends SimpleCursorAdapter, not in the activity itself. (This way I can set a listener for each of the rows in the ListView.)
You might try forceLayout() with a call to draw(canvas) immediately after. If you did not override onLayout(), onMeasure, draw() or onDraw(), then the default implementation will run. The only issue with this is that you have to either get the Canvas or create one manually.
It is strongly discouraged to pursue this approach as Android is specifically designed to avoid drawing outside of normal updates. While you are correct that invalidate() does not happen immediately, it is the safest and best way to encourage a redraw. If you want to handle drawing outside of updates, Android provides a way to do that with the SurfaceHolder class.
Okay, I finally found a solution to this issue, and it's simple. It seems that the problem was just that I was executing the drawable changes in an OnClickLIstener, when I should have been doing it in an OnTouchListener. If I set an OnTouchListener for my button that looks for ACTION_DOWN and then runs setBackgroundColor on its parent layout, the change occurs immediately.
An alternative, less robust solution comes from this post. You can assign a selector for the parent layout background in xml, then in the OnTouchListener mentioned above, run setPressed(true) on that layout. It's not a great solution because the selector gives you a lot less freedom than directly changing the view's properties in code. You couldn't set the layout background to a different color by pressing a different button, for example.
Thanks to those who helped out with suggestions on this!
make the changes and call listview.invalidate(); to reflect those changes on UI
I have a custom view being dynamically inserted and the onDraw is not being called. I initially made the custom view without the dynamic insertion and it worked fine, however when moving it to my working code base, I cannot get onDraw to get called ever, even though the view constructor gets called and the onMeasure also gets called. I've tried putting invalidate() and postInvalidate() calls around the place, but they don't seem to force the view to draw. The view hierarchy looks the same for both version and I'm lost.
Can anyone help?
EDIT: A bit more testing has shown that onDraw is getting called on the emulator, but not the handset. Also onSizeChanged on the handset has the correct values coming through, but on the emulator the values are all 0.
EDIT: I've been playing about with it and still can't get anywhere, so have put a bounty on it. If you need more code / xml / info then please let me know.
This sounds a lot like a problem i had recently, with my first custom view. Did you notice if, when loading view hierarchy, your custom views show up? and anyway do they appear laid out in the wireframe preview or not?
If the latter is the case, it's requestLayout() that doesn't get called (loading view hierarchy calls that on every view - that's why it makes views show up). i've also found that a similar bug was reported already here.
I changed the View to a TextView and it seems to work now, although I'm a little confused as to why the View didn't work.
First time poster, new to Android, and I seem to have hit a roadblock on this problem:
I'm creating a dynamic layout, consisting of several identical "composite" objects (these are basically "floating" LinearLayouts, each containing an icon (ImageView) and a caption (TextView)). The (x,y) coordinates of each LinearLayout are subject to change, based on user actions, and require precise placement (ie: can't use RelativeLayout, etc), so I'm positioning them inside an AbsoluteLayout.
During onCreate(), I'm adding each of these objects as a child View inside an AbsoluteLayout object, then setting the (x,y) manually. No problems so far, works great, initial layout is perfect.
The problem:
After the initial onCreate(), I can't get the (x,y) positions of these objects to change on the screen. I can update their layoutParams, but the on-screen layout (inside the AbsoluteLayout) is never refreshed. I've tried forceLayout(), invalidate(), requestLayout(), none of them work.
Is there a problem with my basic approach here, or is there something I'm just missing?
I'm thinking of changing to a SurfaceView and just doing the rendering the hard way, but my code works 99% great right now and I don't want to change it if I don't have to. The only problem is that the layout manager simply refuses to register the position changes for my child objects.
Help, what am I doing wrong?
Did you try creating a handler for UI and then updating your UI from that..
UI will not be able update from different thread. So, create a thread handler for UI and update the changes to UI using that handler.
-vinay