please suggest a solution.
When i rotate my fragment it should change in to landscape mode and to display another layout.But screen is not rotating to landscape.
My code blow:
<activity
android:name=".activites.MainActivity"
android:launchMode="singleInstance"
android:configChanges="keyboardHidden|screenLayout|screenSize|orientation"
/>
This is main layout called dashboard and now it is in portrait mode:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view=View.inflate(getContext(), R.frag_dashboard,null);
changeview= (ShimmerTextView)view.findViewById(R.id.changeview);
return view;
}
when i rotate the screen this fragment changed to landscape mode and set another layout, and prayer_times is the new layout.
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(getContext(), "landscape", Toast.LENGTH_SHORT).show();
view=View.inflate(getContext(), R.layout.prayer_times,null);
}
}
and i create layout_land for prayer_times
If your fragment has no issue of reloading when orientation change you can simply reload.
Add two layout with same name in layout and layout-land folders.
This will show correct oriented layout when load, for change layout when device rotate
add following in onConfigarationChanged method inside fragment itself.
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE || newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
try {
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
ft.setReorderingAllowed(false);
}
ft.detach(this).attach(this).commit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
If the onCreateView function is called when you rotate the screen, you can do this in it:
if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE) {
......
} else if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT) {
.........
}
Late but this will help some one
Try this in V4 Fragment
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getFragmentManager() != null) {
getFragmentManager()
.beginTransaction()
.detach(this)
.attach(this)
.commit();
}
}
What you're trying to do is rather complicated. Android Fragments are not meant to be rotated.
I had the same problem and found a solution, though. In my case, I wanted to present a Fragment containing different menu pages that would rotate according to orientation.
Just create a Fragment that serves as a base and contains a simple LinearLayout (or any other layout type you want). This LinearLayout will serve as the canvas for our menu:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/llMenuCanvas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
Next, we want to code the base item fragment as an abstract class, that will be implemented by all menu item fragments:
public abstract class NavMenuItem extends Fragment {
static final String TAG = "yourTag"; // Debug tag
LinearLayout canvas;
View hView; // we'll keep the reference of both views
View vView;
// All we'll need to do is set these up on our fragments
abstract int getVerticalLayoutResource();
abstract int getHorizontalLayoutResource();
abstract void setupUI(); // assigns all UI elements and listeners
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.menu_base, container, false); // sets up the layout for this fragment
// keeping our references to both layout versions
hView = inflater.inflate(getHorizontalLayoutResource(), container, false);
vView = inflater.inflate(getVerticalLayoutResource(), container, false);
canvas = view.findViewById(R.id.llMenuCanvas); // this is the magic part: Our reference to the menu canvas
// returning our first view depending on orientation
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
canvas.addView(hView);
}else{
canvas.addView(vView);
}
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setupUI(); // here we set up our listeners for the first time
}
// Here we update the layout when we rotate the device
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
canvas.removeAllViews();
// Checking screen orientation
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
canvas.addView(hView);
}
else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
canvas.addView(vView);
}
setupUI(); // we always need to rebind our buttons
}
}
And here is an example of a menu item fragment that rotates according to the device's orientation.
public class NavMenuMain extends NavMenuItem{
static final String TAG = "yourTag"; // Debug tag
// Your layout references, as usual
ImageButton btnCloseMenu;
// here we set up the layout resources for this fragment
#Override
int getVerticalLayoutResource() { // vertical layout version
return R.layout.menu_main_port;
}
#Override
int getHorizontalLayoutResource() { // horizontal layout version
return R.layout.menu_main_land;
}
#Override
void setupUI(){
// Setup button listeners and layout interaction here
// REMEMBER: the names of your layout elements must match, both for landscape and portrait layouts. Ex: the "close menu" button must have the same id name in both layout versions
}
}
Hope it helps.
All you need to do is open a new layout-land folder inside your res folder and put there xml with the same name of your fragment's layout, the framework will know to look for that .xml on orientation changed.
Look here for details.
By default, the layouts in /res/layout are applied to both portrait and landscape.
If you have for example
/res/layout/main.xml
you can add a new folder /res/layout-land, copy main.xml into it and make the needed adjustments.
See also http://www.androidpeople.com/android-portrait-amp-landscape-differeent-layouts and http://www.devx.com/wireless/Article/40792/1954 for some more options.
When you change the orientation, your fragment destroyed and recreated again (See this for better understanding). So in onConfigurationChanged, you inflate your new layout but it's useless because when your fragment recreated, the onCreateView is called again; in other words, your old layout is inflated again. So better to do this in this way:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view;
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
view = View.inflate(getContext(), R.frag_dashboard,null);
changeview = (ShimmerTextView)view.findViewById(R.id.changeview);
} else(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
Toast.makeText(getContext(), "landscape", Toast.LENGTH_SHORT).show();
view = View.inflate(getContext(), R.layout.prayer_times,null);
}
return view;
}
Related
How to achieve to lock screen orientation for only one Fragment in Single Activity Architecture (only one Activity for whole app)?
Is there better solution than specify requested orientation in each Fragment?
I'm trying to avoid putting activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED to each unrelated Fragment
Let me start by saying that #madlymad has the right idea, and you should create a BaseFragment class if you haven't already.
However if for some reason you can't or don't want to, you can instead set the orientation in the Fragment's onAttached(), onCreate() or onCreateView()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
and then reset in their respective counterparts onDetached(), onDestroy() or onDestroyView()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
This will ensure that you only get different orientation in the correct fragment.
Add below line where you want to fragment in Landscape Mode:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
//ADD BELOW LINE
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
view = inflater.inflate(R.layout.fragment_dashboard, container, false);
return view;
}
If you add above code in fragment then all fragment are show in Landscape mode. But You want to a single fragment. so in Other Fragment add below code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
//ADD BELOW LINE
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
view = inflater.inflate(R.layout.fragment_inventory, container, false);
return view;
}
Note: No need to add Orientation in manifest.xml
I would extend the Fragment and create a BaseFragment for all my fragments. At this base fragment I would use the activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
public abstract class BaseFragment extends Fragment {
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
Activity a = getActivity();
if(a != null)
a.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
}
}
For the specific locked fragment I would just use the activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE.
public class LandscapeFragment extends BaseFragment {
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
Activity a = getActivity();
if(a != null)
a.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
}
The only change you would now have is to make all your fragments extend the BaseFragment instead of Fragment, but this can be beneficial in other shared features as well.
I am not familiar with Kotlin but Kotlin code may be like this:
abstract class BaseFragment : Fragment() {
override fun setUserVisibleHint(isVisibleToUser : Boolean){
super.setUserVisibleHint(isVisibleToUser)
if(isVisibleToUser) {
getActivity()?.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR)
}
}
}
class LandscapeFragment : BaseFragment() {
override fun setUserVisibleHint(isVisibleToUser : Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if(isVisibleToUser) {
getActivity()?.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
}
}
}
If it is activity you can specify in manifest file only for activity tag.
In case for fragment in activity means i guess you can you below code
getActivity().setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
in activity i.e write a method by passing boolean if true PORTRAIT else LANDSCAPE and call it from fragment. I mean if the activity contains different fragments which requires different orientations.
Orientation attribute is per activity so you can declare the orientation for only the activity that contains the fragment so that it is in landscape and the rest of the activities will remain as they are.
Use the following code line in the fragment where you want a specific (in this case portrait) orientation.
getActivity().setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
If you want to have an orientation in a fragment, that is based on the way the user holds his device, then use the following code line.
getActivity().setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
<activity
android:name=".user_interface.activities.MainActivity"
android:screenOrientation="landscape"
android:theme="#style/MyMaterialTheme"/>
I had the same scenarios as yours, I call all my fragments from my Activity. Which way i can have the code in one place.
int id = item.getFragmentId();
if (id == R.id.nav_camera) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
// Here you can have your fragment calls
getSupportFragmentManager().beginTransaction().replace(
R.id.content_main,
new YourFragmentForId(),
NotificationFragment.class.getSimpleName())
.commit();
I still think this is the best way to do it.
I have one activity with many fragments.
If I call fragment B from fragment A(A background, B foreground) and then change the orientation of my device, fragments are showed in different order: A is in foreground and B in background.
If I press back, fragment B is detached so I assume that the position into the backstack is fine.
How could I restore fragments in the right order?
I can't use android:configChanges="orientation|screenSize"
Thanks
By your post, I found that I have the exact problem as you.
The fragment will reload when the configuration change, so when the orientation change. Therefore you need to realod the view.
I used the post to find the solution.Change fragment layout on orientation change!
Here is the solution that works for me. Hope it will help you.
public class YourFragmentActivity extends Fragment {
private FrameLayout frameLayout;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
frameLayout = new FrameLayout(getActivity());
LayoutInflater inflater2 = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
frameLayout.addView(ReloadView(inflater2));
return frameLayout;
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
frameLayout. removeAllViews();
LayoutInflater inflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
frameLayout .addView(ReloadView(inflater));
}
private View ReloadView(LayoutInflater inflater) {
View v = inflater.inflate(R.layout.your_layout, null);
//do your staff, button, listener, etc
return v;
}
}
And dont forget to add that in you manifest, otherwise it will not work.
<activity android:name="com.example.YourFragmentActivity"
android:label="#string/app_name"
android:windowSoftInputMode="stateHidden"
android:configChanges="orientation|screenSize"/>
My Activity first displays a WebView Fragment which must have 0 padding so that the WebView touches the edges of the screen. It then transitions to a ScrollView Fragment which must have 12dp padding inside the ScrollView (and not the Activity) so that the scroll bars are at the edge but the content is padded. However if the ScrollView Fragment is part of a two-pane Activity layout, only the left side must be padded.
you could try this:
#Override
public void onStart() {
super.onStart();
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
//modify the padding
}
}
edit:
Let's say we have a first fragment showing a list of items, and second fragment showing the details, with this code, you will be reusing the DetailsFragment instance if exists.
public boolean multiPane() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if(multiPane()) {
DetailsFragment details = (DetailsFragment)getSupportFragmentManager().findFragmentByTag("DETAILS_FRAGMENT");
if (details != null) {
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
getSupportFragmentManager().beginTransaction().remove(details).commit();
getSupportFragmentManager().executePendingTransactions();
getSupportFragmentManager().beginTransaction().replace(R.id.multi_details, details, "DETAILS_FRAGMENT").commit();
} else {
//create and show a default details fragment(e.g. the first itme on the list idk)
}
} else {
MyListFragment lf = new MyListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.list_and_details, lf, "LIST_FRAGMENT").commit();
}
}
This part of the code is used to avoid an exception when changing the DetailsFragment from one FrameLayout container(portrait xml) to another(landscape xml).
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
getSupportFragmentManager().beginTransaction().remove(details).commit();
getSupportFragmentManager().executePendingTransactions();
The exception is:
...java.lang.IllegalStateException: Can't change container ID of fragment DetailsFragment...
It's a pretty basic example
I solved it by sending the padding as arguments from the Activity. If present, the arguments override the Fragment's default padding.
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_register, container, false);
if (this.getArguments() != null) {
view.setPadding(this.getArguments()
.getInt(ARG_PADDING_LEFT, view.getPaddingLeft()), view.getPaddingTop(), this.getArguments()
.getInt(ARG_PADDING_RIGHT, view.getPaddingRight()), view.getPaddingBottom());
}
return view;
}
So here is my code. 'currentFragment' is simply a field that tracks what is currently being displayed. This is in a class that itself is a Fragment (so I have a fragment showing a fragment).
private void selectNavBarItem(NavbarItem v)
{
Fragment fragmentToUse = null;
if (v == setpointsNavItem)
{
fragmentToUse = setpointsFragment;
}
else if (v == rapidSetupNavItem)
{
fragmentToUse = rapidSetupFragment;
}
else if (v == outdoorResetNavItem)
{
fragmentToUse = outdoorResetFragment;
}
else if (v == rampDelayNavItem)
{
fragmentToUse = rampDelayFragment;
}
if (fragmentToUse != null)
{
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (currentFragment != null)
{
ft.detach(currentFragment);
}
currentFragment = fragmentToUse;
if (currentFragment.isDetached())
{
ft.attach(currentFragment);
}
else
{
ft.add(R.id.setup_content_holder, currentFragment);
}
ft.addToBackStack(null);
ft.commit();
}
Everything looks great, but the views are getting recreated for all the fragments (onCreateView() and onViewCreated()). I was hoping that attaching and detaching would work, but it doesn't. The reason I want to maintain the view is so the user's selections are still there when they navigate back.
Another option is showing and hiding, but I don't know how to make that work because the fragment that owns this code has a FrameLayout (R.id.setup_content_holder) that holds the fragment I want to add, and I can't just add four fragments to it but hide three of them. There is an option to add a fragment with no container, but I have no idea how that is supposed to work.
So, any ideas?
Try this, this will solve your frgment view r-creating issue;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mFragmentView != null) {
((ViewGroup) mFragmentView.getParent()).removeView(mFragmentView);
return mFragmentView;
}
mFragmentView = inflater.inflate(R.layout.home_fragment, container, false);
..... // your remaining code
}
The OnCreateView methods are always called within a Fragment.
To solve the problem you're describing what you really need to do is save the state of the fragment, then when it returns the application will restore what you saved.
e.g. (within the fragment class in question):
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceStatePutString("userString", someTextView.getText().toString());
savedInstanceStatePutInt("userInt", userInt);
// etc...
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
someTextView.setText(savedInstanceState.getString("userString"));
userInt = savedInstanceState.getInt("userInt");
}
That should solve your problem while hopping between fragments; the application will call onSaveInstanceState and onRestoreInstanceState when a fragment gets pushed into or pulled out of the stack.
It will not save them forever, however! If you need more persistent storage, look into other options such as saving to a sqlite database.
OP here.
So I hacked something together here, and I'm wondering if it's a good idea. I have a Fragment holding a View. The View contains everything I want to save (in the short term, of course - this isn't supposed to be any more persistent than RAM). When the Fragment calls onCreateView() I simply return the already-created View.
Now, I ran into an issue where the View was not being removed by the fragment manager. I added a call in onPause() to make sure it's removed from the parent.
Everything seems to work fine, but I want to make sure I'm not doing something really bad. I know Android really really wants to manage its view lifecycles itself, but I do not want it recreating them every damn time. They are complicated and I don't want to deal with re-initializing all the subview text/image/state. Will I run into issues in my attempt to do a run-around Android's normal operating procedure?
EDIT: forgot the code:
public class OutdoorResetFragment extends Fragment
{
private OutdoorResetView view;
public OutdoorResetFragment()
{
}
public void onAttach(Activity activity)
{
if (view == null || view.getContext() != activity)
{
view = new OutdoorResetView(activity);
}
super.onAttach(activity);
}
public void onPause()
{
super.onPause();
ViewGroup container = (ViewGroup) view.getParent();
if (container != null)
{
container.removeAllViews();
}
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState)
{
return view;
}
public OutdoorResetView getView()
{
return view;
}
}
I have created the views to be added in dynamically to a layout from a button click, but when I rotate the device landscape, the views disappear. Could someone please look at my code and explain how to stop this from happening.
Here is the code:
public class TestContainerActivity extends Activity implements OnClickListener {
LinearLayout containerLayout;
Button testButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_container);
testButton = (Button)findViewById(R.id.testContainerButton1);
testButton.setOnClickListener(this);
containerLayout = (LinearLayout)findViewById(R.id.testContainerLayout);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.test_container, menu);
return true;
}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(v==testButton){
createNewLayout();
}
}
public void createNewLayout(){
LayoutInflater layoutInflater = (LayoutInflater) getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View addView = layoutInflater.inflate(R.layout.container, null);
TextView textviewTest = (TextView)addView.findViewById(R.id.containerTextView4);
textviewTest.setText("TextView");
containerLayout.addView(addView);
}
}
Add this line in your manifest.xml Tags.
android:configChanges="keyboardHidden|orientation|screenSize"
This will avoid your activity from recreating on orientation change.
It is better to try to recreate the layout in the new orientation, rather than just preventing the orientation change.
In your onCreate check whether there is a saved instance (as a result of the orientation change) e.g.
if (savedInstanceState == null) {
//create new button layout if previously clicked
}
else {
//normal start
}
You might need to retain some values (either in Shared Prefs or onSavedInstanceState).
This approach is more difficult than locking the orientation, but it is a better approach in the long run and is well worth the research effort.
Add the following line to the TestContainerActivity activity
android:ConfigChanges="keyboardHidden|orientation"