问题
What I want to achieve:
FragmentOne is under Tab1 and FragmentTwoPortrait is under Tab2. I want to save the state of the recyclerview of FragmentTwoPortrait, then restore the fragment state along with the last scroll position of the recyclerview. Similar operation is intended when I rotate the device from landscape to portrait orientation.
In my posted code here, Fragment transaction through the instance FragmentTwoLandscape is done inside a FrameLayout at the beginning to create the initial state of FragmentTwo for landscape orientation. Later on, the view of last saved state of FragmentTwoPortrait is tried to be inflated. The FrameLayout is hidden in portrait orientation, and will be visible only when the device is in Landscape orientation.
Due to certain reasons, I have to handle the device orientation change manually through overriding onConfigurationChanged() from inside the parent activity. Parent activity's life-cycle states need to be handled accordingly if needed.
I have tried out so many solutions over the last few days. I have applied solutions provided on stackoverflow and also tried some of my own. But nothing seems to work! I think I have made a silly mistake somewhere for the code not to return the desired behavior.
P.S: Please keep in mind while proposing a solution: 1. The tab fragments are inside a viewpager 2. FragmentTwo is not allowed to call onSaveInstanceState() [there are obvious reasons behind that] 3. I cannot preserve the header value of the StickyHeaderRecyclerView library (https://github.com/TellH/RecyclerStickyHeaderView) using basic recycler view methods.
activity_main.xml:
<RelativeLayout 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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.sakib.viewpagerfragmentrestoration.Activity.MainActivity">
<FrameLayout
android:background="@android:color/white"
android:id="@+id/fragment_container"
android:layout_width="480dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:visibility="gone"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toStartOf="@+id/fragment_container"
android:layout_toLeftOf="@+id/fragment_container"
android:orientation="vertical">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="200dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:tabGravity="fill"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/colorAccent"
app:tabTextColor="#FFF" />
<com.example.sakib.viewpagerfragmentrestoration.ViewPager.NoScrollViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
MainActivity.java:
public class MainActivity extends AppCompatActivity {
NoScrollViewPager viewPager;
FragmentOne fragmentOne;
FragmentTwo fragmentTwoPortrait, fragmentTwoLandscape;
ViewPagerAdapter adapter;
TabLayout tabLayout;
FrameLayout fragmentTwoLandscapeContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (NoScrollViewPager) findViewById(R.id.pager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
fragmentTwoLandscapeContainer = (FrameLayout) findViewById(R.id.fragment_container);
setViewPagerAdapter();
loadFragmentForLandscape();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
fragmentTwoLandscape.adjustScrollPosition(fragmentTwoPortrait.getRecyclerView());
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
fragmentTwoPortrait.adjustScrollPosition(fragmentTwoLandscape.getRecyclerView());
}
adapter.notifyDataSetChanged();
setVisibility(newConfig);
}
private void setVisibility(Configuration newConfig){
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
fragmentTwoLandscapeContainer.setVisibility(View.VISIBLE);
} else {
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
fragmentTwoLandscapeContainer.setVisibility(View.GONE);
}
}
private void setViewPagerAdapter() {
fragmentOne = new FragmentOne();
fragmentTwoPortrait = new FragmentTwo();
//Add Fragments to adapter one by one
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(fragmentOne, "FRAG1");
adapter.addFragment(fragmentTwoPortrait, "FRAG2");
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
viewPager.setCurrentItem(1);
}
}
private void loadFragmentForLandscape() {
fragmentTwoLandscape = new FragmentTwo();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_container, fragmentTwoLandscape);
ft.commit();
}
// Adapter for the viewpager using FragmentPagerAdapter
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
}
FragmentTwo.java:
public class FragmentTwo extends Fragment {
private RecyclerView rv;
private StickyHeaderViewAdapter adapter;
private Random random = new Random(System.currentTimeMillis());
public FragmentTwo() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_two, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// add your code here which executes after the execution of onCreateView() method.
initView(view);
initData();
}
private void initView(View view) {
rv = (RecyclerView) view.findViewById(R.id.recyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
rv.setLayoutManager(linearLayoutManager);
}
private void initData() {
Gson gson = new Gson();
Result result = gson.fromJson(User.dataSource, Result.class);
List<User> userList = result.getItems();
Collections.sort(userList, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.getLogin().compareToIgnoreCase(o2.getLogin());
}
});
List<DataBean> userListBak = new ArrayList<>();
String currentPrefix = userList.get(0).getLogin().substring(0, 1).toUpperCase();
userListBak.add(new ItemHeader(currentPrefix));
for (User user : userList) {
if (currentPrefix.compareToIgnoreCase(user.getLogin().substring(0, 1)) == 0)
userListBak.add(user);
else {
currentPrefix = user.getLogin().substring(0, 1).toUpperCase();
userListBak.add(new ItemHeader(currentPrefix));
userListBak.add(user);
}
}
adapter = new StickyHeaderViewAdapter(userListBak)
.RegisterItemType(new UserItemViewBinder())
.RegisterItemType(new ItemHeaderViewBinder());
rv.setAdapter(adapter);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_main, menu);
super.onCreateOptionsMenu(menu,inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add_view:
User user = new User("Sticky View", 123, "https://avatars.githubusercontent.com/u/15800681?v=3");
user.setShouldSticky(random.nextBoolean());
adapter.append(user);
break;
case R.id.action_clear_all:
adapter.clear(rv);
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
public void adjustScrollPosition(RecyclerView recyclerView) {
if(recyclerView == null) {
return;
}
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int position = layoutManager.findFirstVisibleItemPosition();
int positionOffset = (recyclerView.getChildAt(0) == null) ? 0 : (recyclerView.getChildAt(0).getTop() - recyclerView.getChildAt(0).getPaddingTop());
LinearLayoutManager targetLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
targetLayoutManager.scrollToPositionWithOffset(position, positionOffset);
}
public RecyclerView getRecyclerView() {
if(rv == null) {
return null;
}
return rv;
}
}
回答1:
First of all get current visible position of your RecyclerView
by adding addOnScrollListener()
as follows :
private int position=0;
myRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE){
position = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
}
}
});
Now save the current visible position in onSaveInstanceState()
method as follows :
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("visiblePosition", position);
}
Then in Your onActivityCreated()
get current visible position from savedInstanceState
and set scroll position as follows:
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(savedInstanceState.getInt("visiblePosition")>0)
myFriendsRecyclerView.scrollToPosition(savedInstanceState.getInt("visiblePosition"));
}
I hope its work for you thank you
回答2:
If you want same behaviour in both portrait and landscape mode then try not to save any configuration
In AndroidManifest for this particular activity
android:configChanges="keyboardHidden|orientation"
来源:https://stackoverflow.com/questions/51354479/scroll-state-of-recyclerview-inside-viewpager-tab-fragment-not-saved-when-device