How to force a view to redraw immediately before the next line of code is executed
Asked Answered
C

3

23

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.)

Coff answered 6/1, 2012 at 7:27 Comment(5)
Seems you won't get the exact answer for this at least i couldn't find one . Can you tell us why you need the view to be redrawn immediately? May be there will be other solutions to solve the problem.Bluefield
@Bluefield What I am trying to do is just what is normally done with a view's background selector in xml (the background briefly changes color when pressed right before executing its callback), except that I want to change the background of a view different from the button that is pressed - namely the layout that contains the button and also a textview.Coff
@Bluefield The rows of my listview have text and a delete button, and I would like the entire row, including the textview, to be briefly highlighted before it is deleted and removed, so that the user has some feedback that he has pressed the row he meant to. Perhaps there is some way to cross link views with a selector in xml?Coff
Since you want to change your background of the parent listitem when the child button clicked. Use different background selector for parent and child(Button) and put duplicateparentstate=false in child(Button) view xml. I haven't tried this . see if it helps and let us know the result.Bluefield
@Bluefield This doesn't seem to do what I want. Looking at the documentation for this property, I'm not sure why it would. Setting duplicateparentstate to false in the button means that the button press state is independent of its parent, and when I do this, the selector for the parent background is in fact unaffected by a press on the button. Only the button's background selector is activated. (If I set "true", then neither selector gets activated when I press the button.) What I need is a way to link the press state of my button and its parent, or of my button and the adjacent textview.Coff
C
2

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!

Coff answered 11/1, 2012 at 7:32 Comment(0)
I
3

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.

Instrumental answered 6/1, 2012 at 8:46 Comment(5)
Hmm. I think i'd prefer to avoid strongly discouraged practices. I'm adding some more info about what I'm trying to accomplish as a comment in the question.Coff
That being said, something to consider: Android guidelines strongly encourage never blocking the UI thread for a reason. By placing the SystemClock.sleep() command, you are blocking the UI thread. If this is for theory testing, awesome! Please let us know your findings. If this is for a real world application, I would strongly consider you reanalyzing your program.Instrumental
The sleep is just for testing. Out of curiosity though, how would you recommend implementing a pause, say, between a click and the associated callback action?Coff
Well, that is one of the best ways to pause. I'm skeptical that it will help to enlighten you in whatever regard you are trying to enlighten yourself. Not because its not a valid test, but because the way the Android system draws is different from the way a lot of systems draw. There are similarities, of course, but Android specifically designed itself so that as little as possible could completely interrupt the UI, because on a small device, the User must know that SOMETHING is going on...Instrumental
Now, if you simply want to change the background, Android is fast enough that a few milliseconds should not matter (making the sleep an irrelevant test). If you want it changed immediately, the only way to do that is to force a draw, as indicated above, which requires either a) forceLayout() or b) draw(), both of which require a canvas and c) that the canvas be the canvas the UI is currently working with.Instrumental
C
2

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!

Coff answered 11/1, 2012 at 7:32 Comment(0)
S
1

make the changes and call listview.invalidate(); to reflect those changes on UI

Skaw answered 6/1, 2012 at 7:30 Comment(1)
I don't have direct access to the ListView itself in this code, since it is in the adapter class, not the activity. I am able to reference theListView using this line: mActivity.mListView.invalidate(); (mActivity is the calling activity pulled from the Context, mListView is a public field of the activity referencing the ListView), but inserting this line before the sleep still does not fix the problem.Coff

© 2022 - 2024 — McMap. All rights reserved.