I am creating an activity that as a dialog format: it does not cover the whole screen, but only part of it. What I did was in the onCreate() method of this activity, after calling setContentView(), I call:
window.setLayout(windowWidthInDp, LayoutParams.WRAP_CONTENT);
And it does not work. I need to do the following instead to make it work:
window.getDecorView().post(new Runnable() {
#Override
public void run() {
window.setLayout(windowWidthInDp, LayoutParams.WRAP_CONTENT);
}
});
Again, this is AFTER setContentView() is called.
Why do I have to pose it into the message queue instead of calling it directly?
Thanks!
Setting the content view just gives the layout to the Android framework. The layout hasn't yet been fully configured. This doesn't happen until the Android framework gets control back (ie: in the next event loop). This won't happen until the onCreate() method ends.
By posting your code to a Handler, you delay the execution of that code until after the Android framework has fully configured the layout.
Related
How do run a function after the view and its subviews have finished loading like in iOS (viewDidAppear / viewDidLayoutSubviews)
I basically want to run a UI animation after the program loads but as my programs too heavy(and has many subviews) the animation doesn't run at all(I tried handler.postDelayed and it worked fine but I wanted to know the more efficient way to do this(for performance reason as a phone I tested on is quite old and takes 4s to load while another takes just a second, etc.).
I think you can use the ViewTreeObserver for that as stated here.
You will have something like:
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// do something here but make sure you unregister if you just want to get notified only once..
}
});
The developers page is stating the following: Register a callback to be invoked when the global layout state or the visibility of views within the view tree changes
You can then add this listener on the root view of your activity for example in onCreate and inside that method you can execute your animation.
The method gets called whenever the layout state or the visibility of the view gets changed. It is not like viewDidAppear but it is something similar to "viewDidLayoutSubviews".
You can also try to run the animation in onResume but that will run the animation whenever the activity resumes not only the first time you create it and I am not sure if you want or not that.
First thing! I do know about ViewTreeObserver.onGlobalLayoutListener.
What made me ask this question is the following notice on Android developer documentation website:
The snippet below does the following:
Gets the parent view and posts a Runnable on the UI thread. This ensures that the parent lays out its children before calling the
getHitRect() method. The getHitRect() method gets the child's hit
rectangle (touchable area) in the parent's coordinates.
Snippet itself is:
parentView.post(new Runnable() {
// Post in the parent's message queue to make sure the parent
// lays out its children before you call getHitRect()
#Override
public void run() {
/// do UI stuff
}
});
(you can look at the full article)
So is this a wrong statement or is it true?
I am asking because posting a runnable seems easier and more convenient compared to doing all that register-listener/handle-event/unregister-listener dance with ViewTreeObserver :)
UPDATE: One more question to bring clarity to the whole subject:
If all this is nice and Runnable can actually be posted instead of using a global layout listener, then why do we have this ViewTreeObserver.onGlobalLayoutListener mechanism at all? When is it better to use it rather than posting a Runnable and what the difference is between this methods?
I like the question too. It forced me to dig into Android source code once again. I believe this works because post() gets called after setContentView().
Method setContentView() ends up in calling ViewGroup.addView() of the top view, and addView() call always triggers requestLayout(). In turn, requestLayout() posts a task to the main thread to be executed later. This task will execute measure and layout on the view hierarchy. Now if you post another task it will be put into the queue after layout task and, as the result, always executed after measure and layout happen. Thus you will always have valid sizes.
I need a way to run some code at the exact moment in which the activity is fully loaded, laid out, drawn and ready for the user's touch controls. Which method/listener does that?
Commonsware is right, without explaining what your are trying to do and why, it's not possible to answer your question and I suspect, with detail, you are probably thinking about it the wrong way.
However, I do have some code where I needed to do some very funky layout stuff after everything had been measured.
I could have extended each of the view classes in the layout and overriden onMeasure() but that would have been a lot of work. So, I ended up doing this. Not great, but it works.
mainMenuLayout is the layout I needed to get funky with. The onGlobalLayout callback is called when the layout has completed drawing. Utils.setTitleText() is where the funkiness takes place and as I pass mainMenuLayout to it, it has access to the position and size of all of the child views.
mainMenuLayout.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// only want to do this once
mainMenuLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// set the menu title, the empty string check prevents sub-classes
// from blanking out the title - which they shouldn't but belt and braces!
if (!titleText.equals("")){
Utils.setTitleText(_context,mainMenuLayout,titleText);
}
}
});
I've found that if I post a Runnable to the message queue, it will run after the content for the activity has been drawn. For example, if I want the width and height of a View, I would do this:
view.post( new Runnable() {
#Override
public void run() {
int width = view.getWidth(); // will be non-zero
int height = view.getHeight(); // will be non-zero
}
} );
I've found success with this anytime after I call setContentView().
onRestoreInstanceState method is the one called to restore UI state which is called after onResume .I think you can use this onRestoreInstanceState method.. and put your code after restoring UI state from the savedInstanceState...
Try onPostResume() called after onResume() at this moment the Activity instance should be visible and all underlying Views are rendered. In many situations this is true when onResume() is called as well.
Maybe it little helps:
#Override
public void onAttachedToWindow(){}
In my main activity, I would like to have it set up, so that I first get met by a contentView just showing a background and some text. After X seconds, I want to change to my other view (GLSurfaceView).
This is obviously something I am doing completely wrong.
This is how I've imagined it could've been done (it's all in the onCreate method):
setContentView(R.layout.main);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
viewer = new Viewer(this);
setContentView(viewer);
Where layout Main is what I want to show at the beginning and Viewer is my GLSurfaceView class.
What happens is that it just goes black for 10 seconds and then it starts loading the objects I've got that is shown through OpenGLES.
There's nothing wrong with the layout Main, since it works if I just erase the lines under where the Thread.sleep takes action. Though, nothing happens before the Thread.sleep is over...
With that said, my questions are following:
Why is the contentView not changing until after Thread.sleep is done?
What would be an appropiate solution to what I want to achieve?
I'm assuming this in your onCreate() and thats why you are seeing nothing.
The way I would implement this is to start a thread using AsyncTask sleep in the doInBackground and in the onPostExecute set up the new view.
Don't make sleep the main thread(UI thread).Use a threads,AsynkTask or TimerTask for that type of works instead.
You're not sleeping the UI thread in the way you think you are.
The simplest thing for what you're looking to achieve is to separate the views into separate activities and let Android handle the transition between the views. It adds another file to your codebase, but it's fairly straightforward. Let's say your initial, plain view (R.layout.main) is for a SplashActivity activity, and your post-splash view goes into PostSplashActivity. Then you could do something like this:
public class SplashActivity extends Activity {
private static long DELAY = 10000; //milliseconds;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new Handler().postDelayed(
new Runnable() {
#Override
public void run() {
Intent postSplash = new Intent(SplashActivity.this, PostSplashActivity.class);
SplashActivity.this.startActivity(postSplash);
SplashActivity.this.finish();
}
}, DELAY);
}
}
This will draw your R.layout.main layout, and then puts a startActivity call for your PostSplashActivity on the message queue and tells the queue to wait DELAY milliseconds to execute it.
It seems like you are making the main thread sleep. This may be why the code is running tell after.
It sounds like you want something like a splash screen. I like to think of these as separate to the following screen, so always use a separate activity rather than calling setContentView twice. You'd still need to sleep in a thread.
Just personal preference though...
I have a layout containing a WebView, and a ProgressBar centred on top of it. The progress bar needs to be shown and hid programatticaly (as web content loads). However, setting the ProgressBar to be visible using loading.setVisibility(View.VISIBLE);, causes a force close. If the ProgressBar is visible by default it works fine. I will paste all appropriate code if needed, but I suspect I'm doing something fundamentally and simply wrong.
(should have force-close tag but neither it nor forceclose exists and I can't create it.)
Okay, code. The setVisibility is simply:
public void nowLoading() {
loading.setVisibility(View.VISIBLE);
}
nowLoading is called... via javascript, with addJavascriptInterface on the WebView. Ahh... I imagine the WevView is in a different Thread. How do I solve that?
You are probably changing the visibility from the wrong thread. Are you doing the visibility change in a new Thread you started with Thread.start()?
EDIT: use a Handler (see http://developer.android.com/reference/android/os/Handler.html). Send a Message to the Handler and then do the visibility change from the Handler. Or use View.post (see http://developer.android.com/reference/android/view/View.html#post%28java.lang.Runnable%29).
try this :
YourActivity.runOnUIThread(new Runnable(){
#Override
public void run(){
loading.setVisibility(View.VISIBLE);
}
});