Reload a View on Orientation Change - android

In my Activity I have a View that "depends" from screen orientation. If it's in landscape mode, i use a layout under layout-large-hand, but if it's in portrait mode, I use the layouts under the layouts folder.
The Activity shows also a Map with some markers and informations.
In my AndroidManifest i have
android:configChanges="orientation|screenSize"
But the activity shows only the layout on portrait mode.
How can I fix this?
EDIT 3/12:
I have a layout like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment" />
<include
android:id="#+id/my_view"
layout="#layout/my_view"
android:visibility="gone" />
</RelativeLayout>
and i want that my_view changes the layout, but map remains with all markers, zoom level, position, ecc ecc...
my_view is visible when i click on a Button created dinamically in the activity.
EDIT 2:
Like SweetWisher ツ says, i was trying to setup a custom behaviour for the views. But when i rotate the device, the map disappear. This is part of my code in the activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initUI();
}
private void initUI() {
setContentView(R.layout.my_layout);
if (mp == null) {
mp = MapFragment.newInstance();
}
getFragmentManager().beginTransaction().replace(R.id.placeholder, mp).commit();
initUIStuff()
}
private void initUIStuff(){
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
initUI();
}
#Override
protected void onStart() {
super.onStart();
initGMap();
}
#Override
protected void onResume() {
super.onResume();
initGMap();
}
private void initGMap() {
if (mp != null {
//Initialize Map
}
}

If you use android:configChanges="orientation" in your Manifest you're preventing Android from doing default reset of view hierarchy on screen orientation change. You have to manually change your views in onConfigurationChanged method and by that I mean inflate your desired layout and replace your old layout with it. If you want to take advantage of Android automatic view hierarchy reset don't use android:configChanges="orientation".
Edit:
Use following code to add map fragment through XML:
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
You also don't need now 'onConfigurationChange' method so delete it.

Related

Exception in Scrollview

In my android application, I'm facing a IllegalStateException. I can't reproduce this exception later. This is the stacktrace
Non-fatal Exception: java.lang.IllegalStateException: ScrollView can host only one direct child
at android.widget.ScrollView.addView(ScrollView.java:397)
at android.support.design.widget.BaseTransientBottomBar.showView(BaseTransientBottomBar.java:436)
at android.support.design.widget.BaseTransientBottomBar$1.handleMessage(BaseTransientBottomBar.java:178)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:146)
at android.app.ActivityThread.main(ActivityThread.java:5679)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
at dalvik.system.NativeStart.main(NativeStart.java)
Anyone please help me!
I examined the question carefully, and I logically found that it is related to showing Snackbar on a fragment or activity, taken as a reference in another class as global object, when it is invalidated in onStop(), onDestroy() life cycle callback stage respectively:
https://stackoverflow.com/a/52019719/787399
Inside your ScrollView you have to host a child (e.g. a Linear Layout) which at it's time will host all the UI elements from that scrollview. You can't have, for example, 2 textviews added directly to a ScrollView. You need to have something to hold those UI elements inside the scrollview.
In ScrollView you can have only one View (in View I mean TextView, Button and cetera, but ViewGroup is child of View too) or ViewGroup. So if you have multiple Views put them in a proper ViewGroup and it will work fine.
Scrollview can only have one direct child
example:
This is valid-->
ScrollView
LinearLayout
Other Views
....
....
LinearLayout
ScrollView
This is not-->
ScrollView
LinearLayout
Other Views
....
....
LinearLayout
LinearLayout
Other Views
....
....
LinearLayout
ScrollView
I got the same issue. and I have reproduced using the below steps.
Step 1: make you Fragment Or Activity Layout with a parent of Scrollview.
Step 2: show Snackbar in onPause(), onStop(), onDestroy() like belove.
#Override
public void onPause() {
super.onPause();
Snackbar.make(button, "onPause", Snackbar.LENGTH_LONG).show();
}
#Override
public void onStop() {
super.onPause();
Snackbar.make(button, "onStop", Snackbar.LENGTH_LONG).show();
}
#Override
public void onDestroy() {
super.onDestroy();
Snackbar.make(button, "onDestroy", Snackbar.LENGTH_LONG).show();
}
Now run an app and check in logcat. when you click on back button you will get same error mention in question.
Solution:
make a common Snackbar like below.
#Override
public void onPause() {
super.onPause();
showSnackbar("onPause");
}
#Override
public void onStop() {
super.onPause();
showSnackbar("onStop");
}
#Override
public void onDestroy() {
super.onDestroy();
showSnackbar("onDestroy");
}
private void showSnackbar(String message) {
if (isValidContext(getActivity())) {
Snackbar.make(btnConsume, message, Snackbar.LENGTH_LONG).show();
}
}
public static boolean isValidContext(final Context context) {
if (context == null) {
return false;
}
if (context instanceof Activity) {
final Activity activity = (Activity) context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return !activity.isDestroyed() && !activity.isFinishing();
} else {
return !activity.isFinishing();
}
}
return true;
}
ScrollView will not accept many child's, Use only one Layout inside ScrollView like LinearLayout, RelativeLayout,FrameLayout or ConstraintLayout(Based on your needs)and then add all your child's.
See this for your reference
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
//Add your Views here
</android.support.constraint.ConstraintLayout>
</ScrollView>
Scroll view allow only one child directly like below :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!--Here scroll view allow only one direct child-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--Here your other xml code-->
</LinearLayout>
</ScrollView>
</LinearLayout>

AndroidPlot graph disappears on landscape view in Fragment

I am using AndroidPlot for displaying dynamic data on a graph in my android App. The graph is displayed in a fragment, which is a part of a tabbed screen (view pager). The graph needs to be displayed in landscape and portrait mode.
When the graph is displayed on the portrait mode, I can see it properly. However, when the phone is turned into landscape mode, the graph disappears completely. This happens only when the AndroidPlot graph is used in a Fragment. When AndroidPlot graph is used in an activity directly without fragment, the graph is displayed correctly in landscape and portrait mode.
Has anyone experienced this? Any help in resolving this issue is appreciated.
My layout.xml file is as follows:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context="com.monitrahealth.mhsmartmct.TestFragment" android:id="#+id/fragment_test">
<com.androidplot.xy.XYPlot
android:id="#+id/mplot"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="4"
androidplot.renderMode="use_background_thread"
androidPlot.titleWidget.labelPaint.textSize="#dimen/title_font_size"
androidPlot.domainLabelWidget.visible="false"
androidPlot.domainLabelWidget.labelPaint.textSize="#dimen/domain_label_font_size"
androidPlot.rangeLabelWidget.visible="false"
androidPlot.rangeLabelWidget.labelPaint.textSize="#dimen/range_label_font_size"
androidPlot.graphWidget.marginTop="0dp"
androidPlot.graphWidget.marginLeft="0dp"
androidPlot.graphWidget.marginBottom="0dp"
androidPlot.graphWidget.marginRight="0dp"
androidPlot.graphWidget.rangeLabelPaint.textSize="#dimen/range_tick_label_font_size"
androidPlot.graphWidget.rangeOriginLabelPaint.textSize="#dimen/range_tick_label_font_size"
androidPlot.graphWidget.domainLabelPaint.textSize="#dimen/domain_tick_label_font_size"
androidPlot.graphWidget.domainOriginLabelPaint.textSize="#dimen/domain_tick_label_font_size"
/>
And my Fragment code is as follows:
public class TestFragment extends android.support.v4.app.Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View ecgGraphView = inflater.inflate(R.layout.fragment_ecggraph, container, false);
XYPlot dynamicPlot = (XYPlot) ecgGraphView.findViewById(R.id.dynamicXYPlot);
return ecgGraphView;
}
public void onPause() {
super.onPause();
}
public void onDestroy(){
super.onDestroy();
}
#Override
public void onResume() {
super.onResume();
}}

Start new activity on orientation change

I want to load a new activity when the orientation changes from potrait to landscape for which I am using:
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
{
startActivity(new Intent(this, ABCDActivity.class));
}
if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
{
super.onConfigurationChanged(newConfig);
finish();
}
}
But it doesn't change my activity with the change in orientation. Have I missed something?
Try this,
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
{
startActivity(new Intent(this, ABCDActivity.class));
}
}
The onConfigurationChanged() method will not be called unless you declared the respective attributes under the in AndroidManifest.xml:
android:configChanges="orientation"
In your Manifest Activity tag for each activity you wish to configChanges for, you must place this line:
android:configChanges="orientation|screenSize"
As an app design note... Starting a new activity on orientation change is not advised...
Why would you want to start a new Activity when your orientation changed?
It's better to just provide different resources for different configurations! Or use Fragments inside 1 Activity.
More information can be found here:
Supporting Multiple Screens
Designing for Multiple Screens
EDIT:
You can just create one activity that recreates itself on orientation change. So remove any configChanges="screenSize..." you have in your AndroidManifest.xml.
In the xml files for the activity, you can just link to another Fragment.
And again, more information about this can be found in the above links.
MyActivity.java
public class MyActivity extends Activity
{
#Override
public void onCreate(Bundle cycle)
{
super.onCreate(cycle);
this.setContentView(R.layout.myactivity);
}
}
res/layout/myactivity.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
class="com.example.myapp.fragments.TimeFrameGraphFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
And res/layout-land/myactivity.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
class="com.example.myapp.fragments.AllDataGraphFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

Setting landscape mode only to Android Google Map Fragment

I have a MapFragment class, which will take up the full screen upon calling it.
From this question, I understand that forcing a horizontal layout will require changes in the Android Manifest. However, how can I achieve this if it's for a MapFragment class?
This is my xml for my map display, if it helps:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<fragment
android:id="#+id/fragment_map"
android:name=".com.fragments.MapFragment"
class="com.fragments.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="tag_fragment_map" />
</FrameLayout>
You can extend the MapFragment class. When onAttach() is invoked store the current orientation of an activity ( by calling getActivity().getRequestedOrientation() ) to an integer then set the parent's orientation to landscape. This is temporary. If your MapFragment is no longer used it will set the parent's old orientation.
public class DummyMapFragment extends MapFragment
{
private int activityOrientation;
#Override
public void onAttach(Activity arg0) {
super.onAttach(arg0);
activityOrientation = arg0.getRequestedOrientation();
arg0.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
#Override
public void onStop() {
Activity parent = getActivity();
if(parent != null)
getActivity().setRequestedOrientation(activityOrientation);
super.onStop();
}
}
Try this:
getActivity().setRequestedScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
Also check this link.
define activity in manifest file like this
<activity
android:name=".yourMapActivity"
android:label="#string/title_activity_edit_profile"
android:screenOrientation="landscape" >

How to add a sliding drawer a MapView/View that's created programmatically

I've tried to approach this from different angles without luck. Maybe asking a general question can help.
Technically I'm using osmdroid's MapView implementation, not Google's Maps API, but I think this question is a more general programmatic Views vs main_activity.xml defined views in onCreate.
Basically in my MainActivity if I onCreate a View, like MapView, then set it as the ContentView programmatically, I have to also programmatically add in any other Views I want to display in my app:
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mapView = new MapView(this, 256);
...
this.setContentView(this.mapView);
}
If I attempt to set the ContentView as activity_main, the MapView can't be adjusted onCreate. Maybe I'm missing something: (note that I have methods that handle loading a custom offline tile set, and place markers on the map, etc...)
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.mapView = (MapView)findViewById(R.id.mapview);
...
this.intializeMapTiles();
this.mapView.setBuiltInZoomControls(true);
this.mapView.setMultiTouchControls(true);
this.mapController.setCenter(new GeoPoint((int)(50.349622 * 1E6), (int)(-71.823700 *1E6)));
...
this.mapView.invalidate();
}
Here's my activity_main.xml in this case:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<org.osmdroid.views.MapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="false"/>
</RelativeLayout>
When trying to get the MapView from the ContentView (acitivty_main), none of my method changes to it make any affect. It's as if I don't have access the exact MapView that's being rendered. I've tried invalidating my MapView but it doesn't matter. I get a default looking and behaving MapView.
The reason I'm trying to solve this is naturally I'm looking for my app to include more than a single MapView. I would like to include a SlidingDrawer, or some method of displaying a View with buttons that is only displayed when you long press on a map marker. (note that I have Toast pop ups being displayed on map marker long presses, so I'm good in this regard)
I'd have to add these other Views (SlideingDrawer, etc...) programmatically and not from the main_activity.xml. Even that has a catch-22, where the SlidingDrawer constructor needs an AttributeSet from xml that's painful to build yourself. (I tried) Then you also have to worry about the layout as well.
Anyone have any suggestions? General or otherwise? Thanks!
This might actually be useful to others for a number of reasons.
If you're stuck having to use a View that can only be configured the way to need via its programmatic constructor (e.g. you could just include the View from your activity_main.xml, but the View isn't what you need it to be unless you construct it yourself, like with offline tile maps using OSMDroid's MapView) then you're stuck extending that View and implementing that View's constructor that includes the AttributeSet. The AttributeSet is basically a structure parsed from the activity_main.xml xml for that view. That constructor will be called automatically in Activity when you this.setContentView(R.layout.activity_main) from onCreate(). So any custom constructor stuff needs to go in that constructor for your extended View.
For example, I had to extend the OSMDroid MapView, then implement my offline map tile source from the super entirely. NOTE you have to super() the 1st line in an extended constructor because object methods aren't available until after the inherited constructor is complete, so any super() method calls have to be to static methods.
public class FieldMapView extends MapView {
public FieldMapView(Context context, AttributeSet attrs) throws Exception {
super(
context,
256,
new DefaultResourceProxyImpl(context),
FieldMapView.getOfflineMapProvider(context, MainActivity.mapTileArchiveFilename),
null,
attrs);
this.setUseDataConnection(false);
this.setBuiltInZoomControls(false);
this.setMultiTouchControls(true);
}
Then in my activity_main.xml I point to the extended version of the View: (e.g. FieldMapView)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.test.FieldMapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="false"/>
So now I have my extended View taking care of any programmatic style requirements, how did I get a SlidingDrawer working with it? I created a SlidingDrawer with a 0dip height View as the handle. I then included a LinearLayout that contains buttons, whatever you want, etc...
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.test.FieldMapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="false"/>
<SlidingDrawer
android:layout_width="wrap_content"
android:id="#+id/slidingDrawerActions"
android:content="#+id/action_content"
android:padding="10dip"
android:layout_height="75dip"
android:handle="#+id/handle2"
android:layout_alignBottom="#id/mapview"
android:orientation="vertical"
android:clickable="false">
<LinearLayout
android:layout_width="wrap_content"
android:id="#+id/action_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="10dip"
android:background="#FF999999"
android:layout_height="wrap_content"
android:clickable="false">
<View
android:id="#id/handle2"
android:layout_width="0dip"
android:layout_height="0dip" />
<ImageButton
android:id="#+id/chatActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dip"
android:src="#drawable/ic_action_edit"
android:text="Chat">
</ImageButton>
</LinearLayout>
</SlidingDrawer>
</RelativeLayout>
In my MainActivity onCreate() I just find the SlidingDrawer View and assign any listeners accordingly. For opening the drawer when long pressing a Marker in the MapView (this is getting more OSMDroid specific now) I naturally have an OnItemGestureListener to open the drawer:
class NodeGestureListener implements OnItemGestureListener<NodeOverlayItem> {
#Override
public boolean onItemLongPress(int index, NodeOverlayItem node) {
if(slidingDrawerActions.isOpened() || slidingDrawerActions.isMoving()) {
return false;
}
slidingDrawerActions.animateOpen();
return false;
}
The tricky part is I wanted to close it via a click on the MapView (not by touching a close button that takes up space) so I had to assign SlidingDrawer.OnDrawerOpenListener and OnDrawerCloseListener classes. They simply flipped a boolean indicating if the drawer was open or closed. I then set a simple onClickListener for the MapView that closed the drawer if it was open based on the isActionDrawerOpen set by the SlidingDrawer listeners.
public void onCreate(final Bundle savedInstanceState) {
...
this.mapView.setOnClickListener(new MapViewClickListener());
...
this.slidingDrawerActions = (SlidingDrawer)findViewById(R.id.slidingDrawerActions);
this.slidingDrawerActions.setOnDrawerOpenListener(new SlidingDrawerOpenListener());
this.slidingDrawerActions.setOnDrawerCloseListener(new SlidingDrawerCloseListener());
...
}
...
private boolean isActionDrawerOpen = false;
class SlidingDrawerOpenListener implements SlidingDrawer.OnDrawerOpenListener {
#Override
public void onDrawerOpened() {
isActionDrawerOpen = true;
}
}
class SlidingDrawerCloseListener implements SlidingDrawer.OnDrawerCloseListener {
#Override
public void onDrawerClosed() {
isActionDrawerOpen = false;
}
}
private boolean skippedMapViewClickListener = false;
class MapViewClickListener implements OnClickListener {
public void onClick(View view) {
if(isActionDrawerOpen) {
if(skippedMapViewClickListener) {
slidingDrawerActions.animateClose();
skippedMapViewClickListener = false;
} else {
skippedMapViewClickListener = true;
}
}
}
}
Note the skippedMapViewClickListener boolean. The problem I had was that the MapView OnClickListener would be called immediately after the SlidingDrawer listener when long pressing the Marker. Meaning the long press would be considered a MapView click, plus the long press itself would open the drawer before OnClickListener was called, so OnClickListener would always see the drawer as open, and would close it. What I did was effectively skip the 1st onClick this way, so the drawer would stay open until you clicked on the MapView. Seems to work great.
I hope this helps someone. There are like 4 problems I solved with this approach.

Categories

Resources