I've written an android app with a view within an activity that relies on a callback to the view's wrapper's getHeight() function using an OnGlobalLayoutListener attached to the view's wrapper's viewTreeObserver to determine the amount of space it has to work with, during onCreate. Usually this is less than the 400px requested by chordDisplay in the xml below. This works perfectly in the android emulator, for a variety of screen sizes using android 2.1 and 4.03. However, on my kindle fire, the callback does not initially return the correct height when I launch the app in landscape (less than 400px are available) until I switch the orientation to portrait and back to landscape either manually or by code. This means the contents of my view aren't sized correctly initially.
sample logcat output when I initially launch the app in landscape:
04-22 17:31:28.249: D/onGlobalLayout(12979): chordDisplay.getHeight(): 400
then I switch to portrait and back to landscape:
04-22 17:32:44.546: D/onGlobalLayout(12979): chordDisplay.getHeight(): 350
I don't understand how this could be happening considering that all that happens during an orientation change is another call to onCreate() in the app, right? Which is the same thing that happens when I start the app.
Does anyone have any experience with similar orientation switching / layout bugs in their apps?/Have any ideas why this could be happening? Any help would be appreciated... Thanks.
code:
OnGlobalLayoutListener thelistener;
RelativeLayout chordDisplayWrapper;
TableRow.LayoutParams lp;
private ChordAdapter chordAdapter;
private HorizontalListView chordDisplay
thelistener = new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
chordDisplayWrapper.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.d("onGlobalLayout", "chordDisplay.getHeight(): " + chordDisplay.getHeight());
lp = new TableRow.LayoutParams(chordDisplay.getHeight()/6 -2,chordDisplay.getHeight()/6 -2); //6 just works. I shrink it a tiny bit to compensate for some weird bug.
chordAdapter = (new ChordAdapter(mcontext, R.layout.row, magicbundle, lp));
chordDisplay.setAdapter(chordAdapter);
}
};
chordDisplayWrapper.getViewTreeObserver().addOnGlobalLayoutListener(thelistener);
xml layout for views involved:
<RelativeLayout
android:id="#+id/chordDisplayWrapper"
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:gravity="center" >
<berry.chordsfree.HorizontalListView
android:id="#+id/testerList"
android:layout_width="wrap_content"
android:layout_height="400px"
android:layout_centerInParent="true"
android:gravity="center" />
</RelativeLayout>
You may have solved this by now, but posting for posterity for those that may come across this after -
I'm having the same problem on my Nexus One, running a custom 2.3.7 ROM, but the code works on my Nexus Galaxy on stock 4.1.1.
Similar to you, I'm trying to resize certain views during onCreate using ViewTreeObserver. I've tried manually calling dispatchGlobalLayout and have ascertained it isn't an issue with onGlobalLayout not firing. And I played around with moving elements into the activity's onWindowFocusChanged without success (but I want to avoid having much resizing done there). This has cost me more than a day of work so I'm just using a quick/dirty fix for now. It appears normally if I just tweak the layout/view in question, after onCreate finishes:
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (!reset) {
LinearLayout frameItem = (LinearLayout) findViewById(R.id.frameItem);
frameItem.setVisibility(LinearLayout.GONE);
frameItem.setVisibility(LinearLayout.VISIBLE);
reset = true;
}
}
Related
I took the source I found here:
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/Renderers/MasterDetailRenderer.cs
And created a custom renderer in a Xamarin Forms Android project. I got the project to build and the menu to open / close as expected as well as display the starting detail page.
The detail page is a "NavigaionPage" in Xamarin.Forms which is actually just a ViewGroup with a bunch of code to make it work with Push / Pop functions.
When I push a new Page, one that has a custom renderer and native RelativeLayout as a subview, the Page appears blank white until I rotate orientation.
After some research in the code, I realized both OnMeasure and OnLayout was not being called in the Page which was being pushed to the NavigationPage / detail page (with the pushed page's size stuck at 0x0 until orientation change).
I created another custom renderer but this time for the NavigationPage which looks like this:
public class MasterDetailContentNavigationPageRenderer : NavigationPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
if(e.OldElement != null)
{
e.OldElement.Pushed -= handleOnPushed;
}
if(e.NewElement != null)
{
e.NewElement.Pushed += handleOnPushed;
}
}
private void handleOnPushed(object sender, EventArgs e)
{
var w = MeasureSpec.MakeMeasureSpec(MeasuredWidth, MeasureSpecMode.Exactly);
var h = MeasureSpec.MakeMeasureSpec(MeasuredHeight, MeasureSpecMode.Exactly);
Measure(w, h);
Layout(Left, Top, Right, Bottom);
}
}
This custom renderer for the detail page / NavigaionPage worked for 2 out of 3 of my pushed Pages.
The main / most important page did not fully add all of the UI elements.
The Page that was pushed and missing UI has SOME of the UI. The following is a list of how the page is displayed:
--- SongViewerLayout : RelativeLayout
------ RelativeLayout
--------- ViewerTopBar (draws all buttons except one)
--------- DocumentView (blank white spot between top bar and pagination)
--------- ViewerPagination (draws background color but not page buttons)
If I change orientations the missing button appears in the top bar, the page numbers show up and the document view sort of draws but it's off.
If I go back to using Xamarin.Forms provided MasterDetailPage the UI just loads as expected.
I have studied the code in the Xamarin.Forms repo but none of it really applies to the inner workings of the elements that are added. In other words, the detail page (which in my case is a NavigationPage) gets added on SetElement(...) and some handlers are assigned ... but nothing I could tell that looks for Pages being pushed and then responding. I would assume / expect the NavigationPage just to work as expected now that it's a subview of the MasterDetailPage / DrawerLayout.
Note: the views that seems to have the most issues are added programmatically and given "LayoutParamaters". The views found in the AXML seem to just show up.
Is there something I am missing to "initialize" a layout in Android? I have made native apps in Android and not had to do anything special.
Why would the UI above the NavigationPage cause the NavigationPage not to do standard initialization?
What am I missing???
Edit:
I created a working example of this issue on my GitHub account which you can clone here:
https://github.com/XamarinMonkey/CustomMasterDetail
Build and run CustomMasterDetail.Droid (Notice the UI is a MasterDetailPage)
Tap any item
The bottom blue bar has no page numbers.
Rotate orientation the page numbers display.
Stop the build
Comment out the entire class "MainMasterDetailPageRenderer.cs"
Rebuild / run the app
Tap any item
Page numbers are shown right away as expected just because it is now using the default MasterDetailPage!
Give suggestions please!
Edit #2:
Thanks to some feedback, it does not seem to be an issue in 6.0 devices. My testing device is a "Samsung Galaxy Tab Pro" with v5.1.1. And unfortunately I need this app to go back to 4!
Edit #3:
The issue has to be adding a view programmatically in Android 5.1.1 to a native RelativeLayout (which is pushed to a NavigationPage detail page ) after it is inflated in Xamarin.Forms. Calling Measure / Layout manually seems to solve the issue. But I need a more automated solution.
I came up with a solution that works. It might be expensive but it seems to layout fast on devices I have tested.
Basicly, I read up on Android and found a way to listen for layout changes using the ViewTreeObserver.GlobalLayout event combined with calling Measure / Layout on the detail and master ViewGroups.
public void MeasureAndLayoutNative()
{
if(_childView != null)
{
IVisualElementRenderer renderer = Platform.GetRenderer(_childView);
if(renderer.ViewGroup != null)
{
var nativeView = renderer.ViewGroup;
var w = MeasureSpec.MakeMeasureSpec(nativeView.MeasuredWidth, MeasureSpecMode.Exactly);
var h = MeasureSpec.MakeMeasureSpec(nativeView.MeasuredHeight, MeasureSpecMode.Exactly);
nativeView.Measure(w, h);
nativeView.Layout(nativeView.Left, nativeView.Top, nativeView.Right, nativeView.Bottom);
}
}
}
I updated my code example above with the working changes!
i wrote an application that run on round and square watches, with two layouts and a watchViewStub.
Since the moto 360 is sold over Europe i have many reports that Moto 360 doesn't load the rounded layout. With emulator, i can't reproduce this behavior even if i change locale as explain here : https://developer.android.com/guide/topics/resources/localization.html#testing
So i guess it's my code ...
Here's is how i've implemented it :
<android.support.wearable.view.WatchViewStub
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/watch_view_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rectLayout="#layout/rect_activity_camera"
app:roundLayout="#layout/round_activity_camera">
Then in a fragment i inflate the WatchViewStub
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final WatchViewStub stub = (WatchViewStub) getView().findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
#Override
public void onLayoutInflated(WatchViewStub stub) {
...
}
}
The fragment is used in a FragmentViewPager/FragmentGridPagerAdapter.
Until i get a Moto 360, any ideas on how i can debug this ?
Edit
WatchViewStub sample in Wear Sdk has two different behavior depends on language settings so i fill this issue : https://code.google.com/p/android/issues/detail?id=77642
Important
This issue is discussed on different posts. to avoid repeating the updated information only the issue on google code will be updated, and i'll complete this posts once the issue will be solved :
https://code.google.com/p/android/issues/detail?id=77642
There are many problems with inflating layouts that are caused by not correctly using WatchViewStub. I don't see enough code above to know exactly, but one common issue is when you register a listener for watch insets:
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
#Override
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
// Need to also call the original insets since we have overridden the original
// https://developer.android.com/reference/android/view/View.OnApplyWindowInsetsListener.html
stub.onApplyWindowInsets(windowInsets);
return windowInsets;
}
});
I see cases where people forget to return the windowInsets, or forget to call stub.onApplyWindowInsets(). This has the effect of giving a square layout instead of a round layout.
Other issues are caused by forgetting to put the same XML elements into the round.xml as the square.xml.
What is the error message you are getting in adb logcat?
I have some information for you about this bug.
The problem is triggered by the watch switching languages when pairing with a phone after a factory reset. You can avoid the issue by selecting the same language on the wearable as you are using on the phone, so that no change occurs afterwards when the devices are paired.
Instructions to fix the problem:
Factory reset the Moto 360.
When the wearable restarts, it will ask what language you would like to use. Select the same language that you are using on the phone (do not select the default of English)
On the phone, start the Android Wear companion app, and select from the overflow menu the option "Pair with a new wearable".
Pair the phone with the Moto 360.
I already have a good understanding of the OrientationEventListener, and detecting screen orientation. But, how do I rotate or adjust the actual content (buttons, images, tabs, any objects)? Right now, when the user rotates the phone, the content doesn't move or rotate, and it appears sideways to the user.
NOTE: I am using the Droid 2 for testing, and I have noticed that if i I slide out the keyboard, the screen DOES rotate, which is kind of strange in my opinion.
Any help is greatly appreciated!
EDIT: Here is my code:
OrientationEventListener ole = new OrientationEventListener(this) {
#Override
public void onOrientationChanged(int arg0) {
TabHost ll = (TabHost) findViewById(android.R.id.tabhost);
try {
ll.forceLayout();
} catch (NullPointerException e) {
System.exit(0);
e.printStackTrace();
}
}
};
ole.enable();
Unfortunately, when the program reaches ll.forceLayout(), it throws a NullPointerException and causes the system to shut down.
SOLVED: I had to edit the manifest such that android:screenOrientation="sensor". Thanks for all your help!
but I have no idea of how to tell android to switch to the second view
when the screen rotates
You don't have to tell it to. If you just create a layout-land folder in your project the system will know to pull layout files from that folder if the device goes in to landscape mode.
Try to call forceLayout () for your root view in the onOrientationChanged.
Just leave that android:screenOrientation="sensor" out of your manifest. It's the default.
I'm currently fighting against the OnLongClickListener on Android Api Lvl 8.
Take this code:
this.webView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
System.out.println("long click");
return true;
}
});
It works perfectly. I can press anywhere on the WebView and the event triggers every time.
Now take a look at this one:
this.webView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final EditText editText = getUrlTextField();
switch (editText.getVisibility()) {
case View.VISIBLE:
editText.setVisibility(View.GONE);
return true;
case View.GONE:
editText.setVisibility(View.VISIBLE);
return true;
default:
return false;
}
}
});
Assuming the URL EditText components is currently visible, it gets gone from the display and should be shown again when another long click event is triggered.
But if you run this, the event just works once (!) when one performs a long click on any position on the WebView. To make things complicated, the long click works again when it is performed on a link on the website...
Can anyone explain if it is a bug in the sdk and/or if there is a mistake in my thinking how the OnLongClickListener is working?!? :/
EDIT:
I've run now several different scenarios on a Nexus One and come to following conclussion: Changing the layout on runtime more or less kills the OnLongClickListener... I haven't found a way to get it work reliably at all...
I would really appreciate if anyone could give me a hint... I'm at my wits end :(
Personnally, I ended up by re-setting the listener after each relayout.
I've run into this issue as well. It seems that if the view layout changes in a way that child view bounds need to be modified (i.e. TextView is wrap_content width and you set its text to something longer/shorter than it was before), views in the hierarchy will have their onStartTemporaryDetach method called (most likely due to a layout pass, although I haven't dug deep enough to find out for sure). If you look at the source for View that onStartTemporaryDetach ultimately unsets the pressed state of the view.
Changing the views in your layout that will be updated periodically to have bounds that will not change regardless of the value you set, will fix the issue. Although, that is still not awesome.
The following problem seems unique to 2.1, happens both on an emulator and on a nexus. The same example works fine on other platforms I've tested (1.5, 1.6 and 2.0 emulators).
I've added created gestureListener as described in this post.
The difference is that I've added the listener on a TextView which also has a contextMenu registered, i.e. sth like the following:
onCreate(...) {
...
// Layout contains a large TextView on which I want to add a context menu
tv = findViewById(R.id.text_view);
tv.registerForContextMenu(this);
// create the gestureListener according above mentioned post.
gestureListener = ...
// set the listener on the text-view
tv.setOnTouchListener(gestureListener);
...
}
When testing it, the correct gesture is recognized alright, but every other time it also causes the context menu to be opened.
As the same example is working on non 2.1 platforms, I've got a feeling it is not my code that is the problem...
Thankful for any suggestions.
Update:
Seems that the return value is flipped somewhere. If I let onFling() return the "wrong" value, i.e. true when the event is skipped and false when it was consumed, it works correctly in 2.1. But of course, that doesn't work on the other platforms. Seems like its time for an ugly workaround...
Thanks for the link steelbytes. I implemented the cancel-and-return-false solution in the last comment (Dec 27, 2010) but just for my onFling event and it appears to work on 1.6 as well as 2.x devices.