Reverse shared element transition on back - android

I am currently using the following code to transition a block on the right side of the screen to a shared element on the left:
FragmentDetail newFragment = FragmentDetail.newInstance(id);
setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.trans_move));
setExitTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));
View block = view.findViewById(R.id.blocks);
block.setTransitionName("block");
newFragment.setSharedElementEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.trans_move));
newFragment.setEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));
newFragment.setTransitionId(block.getTransitionName());
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.container, newFragment);
trans.addToBackStack(null);
trans.addSharedElement(block, block.getTransitionName());
trans.commit();
This works exactly how I want, but I would like to reverse the effect upon pressing the back button, animating the item back in. As is, the explode animation plays, but the transition does not.
Any help is greatly appreciated.
Thanks
Josh

KOTLIN with Android Navigation Component
For anyone who's here looking for the answer to this question when you're using the Android Navigation component, you can make the reverse transition animation work by adding these lines to the onViewCreated function of the starting fragment:
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
You would generally use this if you are opening the second fragment by clicking on a RecyclerView item.

Let's say you have two fragments, A and B, and A commits a fragment transaction to start fragment B.
Then that means the exit and reenter transitions should be set on A and the enter and return transitions should be set on B.
It looks like you are calling setSharedElementReturnTransition on the calling fragment instead of the called fragment (newFragment, in this case), which might be causing the problem.
BTW, you should consider calling the set*****Transition() and setSharedElement*****Transition() methods in your fragment's onCreate() method instead of immediately before a fragment transaction is committed. If a fragment is destroyed and recreated, these transitions will be forgotten... so setting them in onCreate() is much safer.

switch from
trans.replace(R.id.container, newFragment);
to
trans.hide(oldFragment).add(R.id.container, newFragment).show(newFragment)
and it should work (as in my case).
reverting a shared fragment transition seems to only work if you hide the old one, instead of replacing it.

I have met the same problem with you.Buy I have found the solution.
You know, there are many causes for this problem. I just show my way.
Hope that can help you.
there are two fragments.
one have a RecyclerView widget:
ListFragment.java
public class ListFragment extends Fragment implements RecyclerItemInter {
#Bind(R.id.recycler_view)
RecyclerView recyclerView;
private OnListItemClickListener onListItemClickListener;
public void setOnListItemClickListener(ListFragment.OnListItemClickListener onListItemClickListener) {
this.onListItemClickListener = onListItemClickListener;
}
public ListFragment() {
}
public static ListFragment newInstance() {
ListFragment fragment = new ListFragment();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, container, false);
ButterKnife.bind(this, view);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
RecyclerAdapter2 adapter = new RecyclerAdapter2(BEAUTY_BEANS);
recyclerView.setAdapter(adapter);
adapter.setItemInter(this);
return view;
}
private static final BeautyBean[] BEAUTY_BEANS = {
new BeautyBean("Avril Lavigne1", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne2", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne3", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne4", "Avril was born in Canada, the Canadian singer, songwriter creators, actors."),
new BeautyBean("Avril Lavigne5", "Avril was born in Canada, the Canadian singer, songwriter creators, actors.")
};
#Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
#Override
public void onItemClick(View view, int position) {
}
#Override
public void onIvClick(RecyclerAdapter2.ViewHolder holder, int position) {
OtherFragment otherFragment = OtherFragment.newInstance();
otherFragment.setSharedElementEnterTransition(new CustomTransition());
otherFragment.setSharedElementReturnTransition(new CustomTransition());
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
otherFragment.setEnterTransition(new Fade());
setExitTransition(new Fade());
}*/
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame_layout, otherFragment)
.addToBackStack(null)
.addSharedElement(holder.getPicIv(), getString(R.string.transition_img))
.commit();
}
then you should set the TransitionName to every ImageView in the RecyclerView:
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
if (items[position].getImageId() != 0) {
holder.getPicIv().setImageResource(items[position].getImageId());
}
ViewCompat.setTransitionName(holder.getPicIv(), String.valueOf(position) + "_beauty");
holder.getTitleTv().setText(items[position].getName());
holder.getDescTv().setText(items[position].getDesc());
holder.getLinearLayout().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (null != itemInter) {
itemInter.onItemClick(holder.itemView, position);
}
}
});
holder.getPicIv().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (null != itemInter) {
itemInter.onIvClick(holder, position);
}
}
});
}
click the list jump to the OtherFragment.
OtherFragment.java
public class OtherFragment extends Fragment {
public OtherFragment() {
}
public static OtherFragment newInstance() {
OtherFragment fragment = new OtherFragment();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_other, container, false);
}
}
fragment_other.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jacksen.supportlibrarydemo.fragment.OtherFragment">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/bg_detail_header"
android:transitionName="#string/transition_img" />
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_backup_white_36dp"
android:transitionName="#string/transition_fab"
app:borderWidth="0dp"
app:elevation="5dp"
app:pressedTranslationZ="10dp"
app:rippleColor="#color/color_gray" />
the crux of the problem is in this xml.
at the beginning, i set the attribute "transitionName" on and its father layout.
Actually we don't need to add the attribute on father Layout.
Just add transitionName to what you want to transform.
OK, that is my solution.

The Joe Muller answer is correct, I wrote it for Java
#Override
public void onViewCreated(#NonNull final View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
postponeEnterTransition();
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
startPostponedEnterTransition();
view.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
This resolve issue if the start transition is inside an adapter

Related

RecyclerViewClickListener onClick not working in fragment when several fragments included in an activity

This one is a bit hard to explain and demonstrate. I will try my best.
I have two fragments ItemListFragment and ItemViewFragment : a fragment with a recycleview and listing inside the recycleview and a fragment displaying a single item respectively.
In the ItemListFragment, there is RecyclerViewClickListener to handle clicks on items of the list.
The implementation is as followed:
public class ItemListFragment extends Fragment {
private OnFragmentListClickListener onClickListener = null;
public interface OnFragmentListClickListener {
void OnFragmentListClick(ItemModel Item);
}
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
class RecyclerViewClickListenerImpl implements RecyclerViewClickListener {
#Override
public void onClick(View view, int position) {
if (onClickListener != null) {
onClickListener.OnFragmentListClick(adapter.getItem(position));
}
}
adapter = new ItemListAdapter(getActivity(), ItemModelList, new RecyclerViewClickListenerImpl());
mRecyclerView = getActivity().findViewById(R.id.recycler_view);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(getActivity(), 2);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(adapter);
.
.
.
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
Toast.makeText(getActivity(), "onAttach", Toast.LENGTH_SHORT).show();
if (context instanceof OnFragmentListClickListener) {
onClickListener = (OnFragmentListClickListener) context;
}
}
.
.
.
}
The list contains thumbnail (loaded with Glide) and a text for each time.
When I add this fragment in my activity implementing the ItemListFragment.OnFragmentListClickListener interface, everything works fine using the code below:
public class MainActivity extends AppCompatActivity
implements ItemListFragment.OnFragmentListClickListener {
.
.
.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ItemListFragment fragment_list = ItemListFragment.newInstance();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment_list);
fragmentTransaction.commit();
}
#Override
public void OnFragmentListClick(CameraModel camera) {
// handle the list click
}
.
.
.
}
Things get weird when I try to add a second fragment (ItemViewFragment) in my activity as followed:
When there is a second fragment, the RecyclerViewClickListener's onClick on the ItemListFragment is not called anymore:
class RecyclerViewClickListenerImpl implements RecyclerViewClickListener {
#Override
public void onClick(View view, int position) {
if (onClickListener != null) {
onClickListener.OnFragmentListClick(adapter.getItem(position));
}
}
Also, certain thmbnails in the ItemListFragment stopped working when a second fragment has been added in the activity.
This is a weird one... I tried using fragmentTransaction.add instead of fragmentTransaction.add also and all kind of combinations...
You want put your onClick in item of recycle view. You should do every thing of item (like: load image, make event for view,... ) in ViewHolder class. Beacause that's easier to view source code in your activity or fragment. You can references my ViewHolder class with this link. It will help you.

How can I replace a Fragment from within onTouch() of another Fragment?

I need to display a Fragment when the user touches the TextView of another Fragment. Here's what I have:
public class FindPeopleHelpFragment extends Fragment {
public FindPeopleHelpFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_find_people_help, container, false);
((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(R.string.find_people_help);
TextView welcome_home = (TextView) view.findViewById(R.id.find_people_help);
welcome_home.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent evt) {
if (evt.getAction() == MotionEvent.ACTION_DOWN) {
EditProfileFragment editProfileFragment = new EditProfileFragment();
FragmentManager manager = getActivity().getSupportFragmentManager();
manager.beginTransaction().replace(R.id.fragment_container, editProfileFragment, editProfileFragment.getTag()).commit();
}
return true;
}
});
// Inflate the layout for this fragment
return view;
}
}
The issue is that onTouch() returns a boolean, but when replacing the Fragment, I need to return a View. Otherwise I get the following.
10-05 15:00:51.057 25152-25152/wegrok.chiaramail.com E/AndroidRuntime: FATAL EXCEPTION: main
Process: wegrok.chiaramail.com, PID: 25152
java.lang.IllegalArgumentException: No view found for id 0x7f0f00b1 (wegrok.chiaramail.com:id/fragment_container) for fragment FindPeopleHelpFragment{c052157 #1 id=0x7f0f00b1}
Anyone know how to deal with this?
You need to make these Fragment transactions from your Activity. The Activity that hosted the Fragment should have a public function for handling the fragment switching so that when the TextView of your Fragment1 is clicked, the public method of the Activity will be called to make the Fragment switch.
So you might consider having an Activity like this.
public class YourActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
// The Fragment 1 will be loaded by default
switchToFragment1();
}
public void switchToFragment1() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new Fragment1()).commit();
}
public void switchToFragment2() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new Fragment2()).commit();
}
}
Now from your Fragment1, you need to implement an onClick listener in to the TextView, so that when the TextView is clicked, Fragment1 switches to Fragment2.
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((YourActivity) getActivity()).switchToFragment2();
}
});)
Hope that helps.

Does FragmentStatePagerAdapter save fragment state on orientation change?

I have 3 fragments that need to be in a ViewPager. These fragment will hold dynamic information retrieved from a database. I understand that on an orientation change, the activity and fragments are destroyed and recreated. But I was under the impression by its name, that the FragmentStatePagerAdapter will save the state of the fragment. Apparently, I was wrong because every time I did something to the fragment, then change orientation, the fragment is reverted back to how it was laid out in the layout xml file.
As I was debugging, I noticed that on orientation change, the Adapter's getItem() method was never invoked - meaning that it wasn't recreated. So then how come the fragment state reverted back to its original state?
How do I save the fragment state using the FragmentStatePagerAdapter?
Please note that I have been following this tutorial and used their version of the SmartFragmentStatePagerAdapter.java class to manage the fragment dynamically.
And the following are my sample codes.
PageLoader.java - This interface allows MainActivity to manage the loading of the fragment pages dynamically at run time.
public interface PageLoader {
void loadPage(int from, int target);
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements PageLoader {
MyPagerAdapter adapter;
DirectionalViewPager pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the ViewPager and set it's PagerAdapter so that it can display items
pager = (DirectionalViewPager) findViewById(R.id.vpPager);
adapter = new MyPagerAdapter(getSupportFragmentManager());
pager.setOffscreenPageLimit(5);
pager.setAdapter(adapter);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int position = pager.getCurrentItem();
if (position > 0) pager.setCurrentItem(position - 1);
return true;
}
#Override
public void loadPage(int from, int target) {
PageLoader fragment = (PageLoader) adapter.getRegisteredFragment(target);
fragment.loadPage(from, target);
}
}
MyPagerAdapter.java
public class MyPagerAdapter extends SmartFragmentStatePagerAdapter {
private static final int NUM_ITEMS = 4;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
// Returns total number of pages
#Override
public int getCount() {
return NUM_ITEMS;
}
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0 - This will show Frag1
return Frag1.newInstance(position, Frag1.class.getSimpleName());
case 1: // Fragment # 0 - This will show Frag1 different title
return Frag1.newInstance(position, Frag1.class.getSimpleName());
case 2: // Fragment # 1 - This will show Frag2
return Frag2.newInstance(position, Frag2.class.getSimpleName());
default:
return Frag3.newInstance(position, Frag3.class.getSimpleName());
}
}
// Returns the page title for the top indicator
#Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
}
Frag1.java Frag2.java Frag3.java - these are all the same, except for the numbering.
public class Frag1 extends Fragment implements PageLoader {
// Store instance variables
private String title;
private int page;
private TextView txtView;
// newInstance constructor for creating fragment with arguments
public static Frag1 newInstance(int page, String title) {
Frag1 fragmentFirst = new Frag1();
Bundle args = new Bundle();
args.putInt("someInt", page);
args.putString("someTitle", title);
fragmentFirst.setArguments(args);
return fragmentFirst;
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
page = getArguments().getInt("someInt", 0);
title = getArguments().getString("someTitle");
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag1, container, false);
txtView = (TextView) view.findViewById(R.id.txt_frag1);
txtView.setText(page + " - " + title);
Button btn = (Button) view.findViewById(R.id.btn_frag1);
btn.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick(View v) {
PageLoader activity = (PageLoader) getActivity();
activity.loadPage(page, page+1);
}
});
return view;
}
#Override
public void loadPage(int from, int target) {
txtView.setText(txtView.getText() + "\nThis message was created from" + from + " to " + target);
}
}
activity_main.xml
<?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">
<com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager
android:id="#+id/vpPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager>
</LinearLayout>
frag1.xml frag2.xml frag3.xml - again these are all the same except for the numbering
<?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"
android:background="#cc2">
<TextView
android:id="#+id/txt_frag1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="txt_frag1"
/>
<Button
android:id="#+id/btn_frag1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="btn_frag1"
android:textSize="26dp" />
</LinearLayout>
PLEASE tell me how I can use the FragmentStatePagerAdapter to save the "State" of my fragments. I've been scouring the internet from 9am to 9pm today... 12 hours... I really need some help figuring this out. Thanks in advance!
EDIT Try this:
Add another instance variable to your fragment:
private String text; // this is part of saved state
Set this variable in loadPage:
#Override
public void loadPage(int from, int target) {
text = txtView.getText().toString() + "\nThis message was created from" + from + " to " + target;
txtView.setText(text);
}
Override onSaveInstanceState to save this variable:
#Override
public void onSaveInstanceState(Bundle outState);
outState.putString("text", text);
super.onSaveInstanceState(outState);
}
Then restore the the TextView state using this variable:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null) {
// not null means we are restoring the fragment
text = savedInstanceState.getString("text");
} else {
text = "" + page + " - " + title;
}
View view = inflater.inflate(R.layout.frag1, container, false);
txtView = (TextView) view.findViewById(R.id.txt_frag1);
txtView.setText(text);
Button btn = (Button) view.findViewById(R.id.btn_frag1);
btn.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick(View v) {
PageLoader activity = (PageLoader) getActivity();
activity.loadPage(page, page+1);
}
});
return view;
}
Any time you want something in your fragment to stay the same when things like configuration changes occur, this is how you would track the state, then save and restore it.
This is where you would use onSaveInstanceState for fragments and activities.
This is the method you would override to save any necessary state. Anything you change in your fragment that you want to have recreated on configuration change must be saved and then restored during onCreate or onCreateView.
So if you're trying to restore the text created in loadPage, you would create a class-level String for the text, set it in loadPage, save that in the onSaveInstanceState override, and then restore in in onCreateView from the savedInstanceState parameter.
Now here's the kicker: You are noticing that getItem on your adapter isn't called after a config change. But did you notice that your fragment is still there (even though it wasn't how you left it)? Keep in mind that the activity has a FragmentManager that is managing the fragments and their transactions. When the activity goes to config change, it saves its state. The FragmentManager and all of the active fragments are part of that state. Then the fragments are restored in such a way that adapter.getItem isn't called.
Turns out, that SmartFragmentPagerAdapter isn't so smart. It can't recreate its registeredFragments array after a configuration change, so it's really not very useful. I would discourage you from using it.
So how do you send events to off-page fragments when the ViewPager has appropriated the fragment's tag for its own use?
The technique I use is to define event listener interfaces, and have the fragments register as listeners with the activity. When I fire an event, it's by calling a method on the activity that notifies its active listeners. I give a pretty complete example of this in this answer.

Show dialog above view pager that has nested fragments

I have setup a very simple test project https://github.com/ArtworkAD/ViewPagerDialogTest to evaluate following situation: the main activity has a view pager which hosts a single fragment using support fragment manager:
public class MainActivity extends AppCompatActivity {
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
// ...
tabLayout.setupWithViewPager(viewPager);
}
#Override
protected void onResume() {
super.onResume();
MainActivity.CustomDialog dialog = (MainActivity.CustomDialog) getSupportFragmentManager().findFragmentByTag(MainActivity.CustomDialog.TAG);
if (dialog == null) {
new MainActivity.CustomDialog().show(getSupportFragmentManager().beginTransaction(), MainActivity.CustomDialog.TAG);
}
}
// ...
}
When the activity is resumed a dialog fragment is shown inside the main activity.
The single fragment inside the view pager is defined like this:
public class RootFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.root_fragment, container, false);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
}
return root;
}
}
This root fragment allows us to stack other fragments on the "root_frame". So we stack another and another:
public class FirstLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
View root = inflater.inflate(R.layout.first_level_fragment, container, false);
root.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SecondLevelFragment f = (SecondLevelFragment) getActivity().getSupportFragmentManager().findFragmentByTag("NESTED");
if (f == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
}
}
});
return root;
}
public static class SecondLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
return inflater.inflate(R.layout.second_level_fragment, container, false);
}
}
}
This works great! The stacking idea is taken from https://stackoverflow.com/a/21453571/401025 . However when dialog is shown and the users goes to the second level fragment and rotates the screen I get following exception:
E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity
ComponentInfo{de.azzoft.viewpagerdialogtest/de.azzoft.viewpagerdialogtest.MainActivity}:
java.lang.IllegalArgumentException: No view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
E/AndroidRuntime: Caused by: java.lang.IllegalArgumentException: No
view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
Full stack trace: https://github.com/ArtworkAD/ViewPagerDialogTest/blob/master/README.md
Without the dialog appearing everything works great. You can test it by downloading the test project.
It seems that the dialog, which is actually a fragment, messes up fragment hierarchy when it is added to the activity. Any ideas how to fix this?
It is important that the second fragment is retained.
No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment
When Activity recreates on rotate, the Activity FragmentManger tries to add the SecondLevelFragment into R.id.root_frame . But the root_frame view is not in Activity layout, its in FirstLevelFragment layout. Thats why the app crashes.
You have to make two changes to fix this issue.
Add the FirstLevelFragment into the RootFragment using the getChildFragmentManager
getChildFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
Add the SecondLevelFragment using FragmentManager
getFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
Finally remove the setRetainInstance from FirstLevelFragment and SecondLevelFragment as nested fragments doesn't required to set retain.
If you need to pop back the SecondLevelFragment on back press you need to pass the back press the event to RootFragment and pop from back stack.
Override the back press on activity
#Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.viewpager);
if(fragment instanceof RootFragment){
boolean handled = ((RootFragment)fragment).onBackPressed();
if(handled){
return;
}
}
super.onBackPressed();
}
And handle the back press on RootFragment
public boolean onBackPressed() {
int count = getChildFragmentManager().getBackStackEntryCount();
if(count > 0){
getChildFragmentManager().popBackStackImmediate();
return true;
}
return false;
}
I created a Pull request to your repository . please check
https://github.com/ArtworkAD/ViewPagerDialogTest/pull/1
Let me know if any questions.
If you override onDismiss so resolved crash. enjoy it.
#Override
protected void onResume() {
super.onResume();
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TAG);
if(dialog == null){
CustomDialog.newInstance().show(getSupportFragmentManager(), TAG);
}
}
public static class CustomDialog extends DialogFragment {
public static CustomDialog newInstance() {
CustomDialog d = new CustomDialog();
return d;
}
#Override
public void onDismiss(DialogInterface dialog) {
// super.onDismiss(dialog);
Toast.makeText(getActivity(), "onDismiss", Toast.LENGTH_LONG).show();
}
#Override
public void onCancel(DialogInterface dialog) {
// super.onCancel(dialog);
Toast.makeText(getActivity(), "onCancel", Toast.LENGTH_LONG).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Dialog");
builder.setMessage("This is a message!");
builder.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
return builder.show();
}
}
If you want to keep the state of your Fragments you should use a FragmentStatePagerAdapter.
From the docs:
Implementation of PagerAdapter that uses a Fragment to manage each
page. This class also handles saving and restoring of fragment's
state.
If you use this you can also remove the setRetainInstance(true) calls.
Well, I had downloaded your Test app and it seems that I have fixed the problem.
In your FirstLevelFragment class, comment the following line
//if (nestedNestedFragment == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
//}
And
Comment setRetainInstance(true); in SecondLevelFragment
I think you missed setContentView() in onCreate() of your Activity. See your Fragment can not be added without a View hierarchy. Your Fragment is hosted by an activity. So you need to set the content to the activity first.
Hope this Helps,
Thanks.

How to make ViewPager destroy items right after I've scrolled to next page

If we have 4 pages in a View pager and swipe from positions 0 do 3
Item at position 0 is destroyed when we get to position 2, item at position 1 is destroyed when we get to position 4.
I need them to be destroyed right away, because I want the view to be recreated if I go back to it, which doesnt happen at the moment.
public class ViewPagerTutorialAdapter extends FragmentStatePagerAdapter {
public ViewPagerTutorialAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Bundle args = new Bundle();
args.putInt("page", position);
TutorialPageFragment fragment = new TutorialPageFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return 4;
}
}
public class TutorialPageFragment extends android.support.v4.app.Fragment {
private ImageView ivTutorialPage;
private TransitionDrawable myTransitionDrawable;
private View rootView;
public TutorialPageFragment() {
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_tutorial_page, container, false);
return rootView;
}
#Override
public void onResume() {
super.onResume();
ivTutorialPage = (ImageView) rootView.findViewById(R.id.ivTutorialPage);
ivTutorialPage.setImageResource(R.drawable.fadein);
myTransitionDrawable = (TransitionDrawable) ivTutorialPage.getDrawable();
new Timer().schedule(new TimerTask() {
#Override
public void run() {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
myTransitionDrawable.startTransition(1000);
}
});
}
}, 1000);
}
}
While this is not exactly answer to asked question, a better practice is not recreate Views if not necessary because it can be expensive to do on more complicated layouts + cause stuttering / extra lag which makes user experience worse.
In this case (running animations when fragment becomes visible) it's better to override setUserVisibleHint in fragment with code to run animation which will trigger when user visits the screen. The main advantage is of course no need to recreate Views and allows Android to perform it's internal optimization naturally.
Try viewPager.setOffscreenPageLimit(1);. This should save only one page near selected one. There is not way to only load selected item at a time according to this.

Categories

Resources