该项目是可在Android Studio中找到的主/明细流模板的扩展。不同之处在于此应用程序使用单个Activity和一个管理三个Fragment的ViewPager。第三个片段是主(列表)片段,其中包含可单击的RecycleView。单击列表项后,它将用“子”(详细)片段切换“片段”。
在项目运行时,我想避免在清单中使用android:configChanges= "orientation|keyboardHidden|screenSize"
。我应该怎么做?
如果删除了该属性,则旋转时ItemListFragment中的mListener会与ItemFragmentList一起销毁,但在重新创建ItemListFragment时不会重新创建。在“人像”模式下单击“列表”项目时,这不会导致任何事情发生。
我最初的解决方案是手动覆盖配置更改处理,这意味着旋转屏幕时不会破坏ItemListFragment。添加了onConfigurationChanged()
和populateViewForOrientation()
以重新填充布局。当然,有比手动覆盖配置处理更好的解决方案。
项目位于:https://github.com/lukeallison/ViewPagerMasterDetail,
视频:http://tinypic.com/r/1zltyeq/9
BaseFragment.java
public class BaseFragment extends Fragment {
public Bridge mBridget;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mBridget = (MainActivity) getActivity();
}
}
接口:Bridge.java
public interface Bridge {
abstract void onBack();
}
ItemListFragment.java
public class ItemListFragment extends BaseFragment{
private boolean mTwoPane = false;
private PageFragmentListener mListener;
public static ItemListFragment newInstance(PageFragmentListener listener) {
ItemListFragment fragment = new ItemListFragment();
fragment.mListener = listener;
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_item_list, container, false);
initLayout(root);
return root;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
LayoutInflater inflater = LayoutInflater.from(getActivity());
populateViewForOrientation(inflater, (ViewGroup) getView());
}
private void populateViewForOrientation(LayoutInflater inflater, ViewGroup viewGroup) {
viewGroup.removeAllViewsInLayout();
View subview = inflater.inflate(R.layout.fragment_item_list, viewGroup);
initLayout(subview);
}
public void initLayout(View root) {
View recyclerView = root.findViewById(R.id.item_list);
mTwoPane = false;
if (root.findViewById(R.id.item_detail_container) != null) { // R.layout.list_item is located "layout", "layout-land"..
mTwoPane = true; // currently loaded "layout-land/list_item". landscape mode
}
Toolbar toolbar = (Toolbar) root.findViewById(R.id.toolbar);
((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);
toolbar.setTitle("List");
assert recyclerView != null;
setupRecyclerView((RecyclerView) recyclerView);
}
private void setupRecyclerView(RecyclerView recyclerView) {
recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(DummyContent.ITEMS));
}
public class SimpleItemRecyclerViewAdapter
extends RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder> {
private final List<DummyContent.DummyItem> mValues;
public SimpleItemRecyclerViewAdapter(List<DummyContent.DummyItem> items) {
mValues = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_list_content, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mItem = mValues.get(position);
holder.mIdView.setText(mValues.get(position).id);
holder.mContentView.setText(mValues.get(position).content);
// One row of List. define click event
holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mTwoPane) { // landscape mode
Bundle arguments = new Bundle();
arguments.putString(Constants.ARG_ITEM_ID, holder.mItem.id);
ItemTwoDetailFragment fragment = ItemTwoDetailFragment.newInstance();
fragment.setArguments(arguments);
// show detail fragment to right side of screen
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.item_detail_container, fragment)
.commit();
} else { // portrait mode
if (mListener!=null)
mListener.onSwitchToNextFragment(holder.mItem.id); // switch detail fragment
}
}
});
}
@Override
public int getItemCount() {
return mValues.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final TextView mIdView;
public final TextView mContentView;
public DummyContent.DummyItem mItem;
public ViewHolder(View view) {
super(view);
mView = view;
mIdView = (TextView) view.findViewById(R.id.id);
mContentView = (TextView) view.findViewById(R.id.content);
}
@Override
public String toString() {
return super.toString() + " '" + mContentView.getText() + "'";
}
}
}
}
ItemOneDetailFragment.java
/**
* A fragment representing a single Item detail screen.
* on handsets.
*/
public class ItemOneDetailFragment extends BaseFragment {
private DummyContent.DummyItem mItem;
/*
Listener for switch fragment
*/
private PageFragmentListener mListener;
public static ItemOneDetailFragment newInstance(PageFragmentListener listener) {
ItemOneDetailFragment fragment = new ItemOneDetailFragment();
fragment.mListener = listener;
return fragment;
}
public ItemOneDetailFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(Constants.ARG_ITEM_ID)) {
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(Constants.ARG_ITEM_ID)); // Get selected Item ID to show details
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_item_one_detail, container, false);
// Show the dummy content as text in a TextView.
if (mItem != null) {
Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.detail_toolbar);
toolbar.setTitle(mItem.content);
toolbar.setNavigationIcon(R.drawable.ic_ab_back_material);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mBridget.onBack();
}
});
((TextView) rootView.findViewById(R.id.item_detail)).setText(mItem.details); // show details
}
return rootView;
}
}
ItemTwoDetailFragment.java
// for landscape orientation
public class ItemTwoDetailFragment extends BaseFragment {
private DummyContent.DummyItem mItem;
public static ItemTwoDetailFragment newInstance() {
return new ItemTwoDetailFragment();
}
public ItemTwoDetailFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(Constants.ARG_ITEM_ID)) {
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(Constants.ARG_ITEM_ID)); // Get selected item to show details
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_item_two_detail, container, false);
// Show the dummy content as text in a TextView.
if (mItem != null) {
((TextView) rootView.findViewById(R.id.item_detail)).setText(mItem.details); // show details
}
return rootView;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements Bridge{
private ViewPager viewPager = null;
private MyAdapter mAdapter;
PageChangeListener mListener = new PageChangeListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setOnPageChangeListener(mListener); // Page Change Listener
mAdapter = new MyAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
// Show the Up button in the action bar.
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
@Override
public void onBackPressed() {
if (mListener.getCurrentPage()==2 && mAdapter.mFragment instanceof ItemOneDetailFragment) { // current page is Tab-3, current fragment is detail fragment
mAdapter.mListener.onSwitchToNextFragment("0"); // show List fragment
return; // prevent to finish app.
}
super.onBackPressed();
}
// Do the same thing as the back button - go back to ItemListFragment
// Only when in ItemOneDetailFragment
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (mListener.getCurrentPage() == 2 && mAdapter.mFragment instanceof ItemOneDetailFragment) { // current page is Tab-3, current fragment is detail fragment
mAdapter.mListener.onSwitchToNextFragment("0"); // show List fragment
// return; // prevent to finish app.
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBack() {
onBackPressed();
}
}
class PageChangeListener extends ViewPager.SimpleOnPageChangeListener {
private int currentPage;
@Override
public void onPageSelected(int position) {
currentPage = position; // current selected page
}
public final int getCurrentPage() {
return currentPage;
}
}
/**
* Fragment Page Adapter
*/
class MyAdapter extends FragmentPagerAdapter{
private final FragmentManager mFragmentManager;
public BaseFragment mFragment;
/**
* PageFragmentListener for switching fragment.
*/
public PageFragmentListener mListener = new PageFragmentListener() {
@Override
public void onSwitchToNextFragment(final String id) {
mFragmentManager.beginTransaction().remove(mFragment).commit();
if (mFragment instanceof ItemListFragment){ // current fragment is List Fragment
Bundle arguments = new Bundle();
arguments.putString(Constants.ARG_ITEM_ID, id); // selected item id
mFragment = ItemOneDetailFragment.newInstance(mListener); // switch detail fragment
mFragment.setArguments(arguments);
}else{ // DetailFragment
mFragment = ItemListFragment.newInstance(mListener); // => switch list fragment
}
notifyDataSetChanged(); // notify changes
}
};
public MyAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
@Override
public Fragment getItem(int position) {
if (position == 0) // Tab-1
return FragmentA.newInstance();
if (position == 1) // Tab-2
return FragmentB.newInstance();
if (position == 2) { // Tab-3
if (mFragment==null) // first time => create list fragment
mFragment = ItemListFragment.newInstance(mListener);
return mFragment;
}
return null;
}
@Override
public CharSequence getPageTitle(int position) {
if (position == 0) { // Tab-1
return "Tab 1";
}
if (position == 1) { // Tab-2
return "Tab 2";
}
if (position == 2) { //Tab-3
return "Tab 3";
}
return null;
}
@Override
public int getCount() { // Count of Tabs
return 3;
}
@Override
public int getItemPosition(Object object) {
Log.i("Adapter", "ItemPosition>>>" + object.toString());
if (object instanceof ItemListFragment && mFragment instanceof ItemOneDetailFragment) { // fragment changed
return POSITION_NONE;
}
if (object instanceof ItemOneDetailFragment && mFragment instanceof ItemListFragment) { // fragment changed
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
}
接口:PageFragmentListener.java
public interface PageFragmentListener {
void onSwitchToNextFragment(String id);
}
日志:将属性添加到Manifest.xml
First instantiation
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: newInstance
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreate()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreateView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onBindViewHolder
ClickItem
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onClick(): single pane
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: removed fragment
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: is instance of List Fragment
03-16 .../com.allison.viewpagermasterdetail D/ItemOneDetailFragment: newInstance
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>FragmentB{41dd3810 #1 id=0x7f0c0069 android:switcher:2131492969:1}
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>ItemListFragment{41df1528 #2 id=0x7f0c0069 android:switcher:2131492969:2}
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onStop()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroyView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroy()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
Back
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: removed fragment
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: is instance of Detail Fragment
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: newInstance
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>FragmentB{41dd3810 #1 id=0x7f0c0069 android:switcher:2131492969:1}
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>ItemOneDetailFragment{41e896a8 #2 id=0x7f0c0069 android:switcher:2131492969:2}
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreate()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreateView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
Rotate
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onConfigurationChanged
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: populateViewForOrientation
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onBindViewHolder
ClickItemLand
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onClick(): mTwoPane
Rotate
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onConfigurationChanged
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: populateViewForOrientation
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onBindViewHolder
ClickIemPortrait
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onClick(): single pane
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: removed fragment
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: is instance of List Fragment
03-16 .../com.allison.viewpagermasterdetail D/ItemOneDetailFragment: newInstance
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>FragmentB{41dd3810 #1 id=0x7f0c0069 android:switcher:2131492969:1}
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>ItemListFragment{41ea8b68 #2 id=0x7f0c0069 android:switcher:2131492969:2}
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onStop()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroyView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroy()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
日志:无属性
First instantiation
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: newInstance
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreate()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreateView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onBindViewHolder
ClickItem
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onClick(): single pane
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: removed fragment
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: is instance of List Fragment
03-16 .../com.allison.viewpagermasterdetail D/ItemOneDetailFragment: newInstance
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>FragmentB{41dd4758 #1 id=0x7f0c0069 android:switcher:2131492969:1}
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>ItemListFragment{41ded998 #2 id=0x7f0c0069 android:switcher:2131492969:2}
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onStop()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroyView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroy()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
Back
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: removed fragment
03-16 .../com.allison.viewpagermasterdetail D/MyAdapter: is instance of Detail Fragment
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: newInstance
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>FragmentB{41dd56b8 #1 id=0x7f0c0069 android:switcher:2131492969:1}
03-16 .../com.allison.viewpagermasterdetail I/Adapter: ItemPosition>>>ItemOneDetailFragment{41e830b8 #2 id=0x7f0c0069 android:switcher:2131492969:2}
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreate()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreateView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onBindViewHolder
Rotate
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onSaveInstanceState()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onStop()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroyView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroy()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreate()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreateView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
ClickItemLand
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onClick(): mTwoPane
Rotate
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onSaveInstanceState()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onStop()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroyView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onDestroy()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onPause()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreate()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onCreateView()
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: initLayout
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: setupRecyclerView()
03-16 .../com.allison.viewpagermasterdetail E/ViewRootImpl: sendUserActionEvent() mView == null
03-16 .../com.allison.viewpagermasterdetail D/ItemListFragment: onBindViewHolder
ClickIemPortrait
// nothing happens
最佳答案
您似乎有几个问题需要解决。
首先,重新创建活动时,您到mListener
中SimpleItemRecyclerViewAdapter
的链接断开。因此,您需要在重新创建活动之后恢复该连接。为此,您需要进行以下修复。
主要活动
将PageFragmentListener
声明为属性,以使外部可以访问
public class MainActivity extends AppCompatActivity implements Bridge {
...
PageChangeListener mListener = new PageChangeListener();
//keep a reference to listener, need to access this from fragment
PageFragmentListener mPageFragmentListener;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mAdapter = new MyAdapter(getSupportFragmentManager());
//set the listener
mPageFragmentListener = mAdapter.mListener;
....
}
}
ItemListFragment
覆盖
onActivityCreated
中的ItemListFragment
方法并通过从活动中访问mListener
来还原它public class ItemListFragment extends BaseFragment{
...
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Activity activity = getActivity();
if(activity instanceof MainActivity) {
mListener = ((MainActivity)activity).mPageFragmentListener;
}
...
}
现在,重新创建活动时,您的
mListener
将始终正确设置。但是,您还需要进行一些其他修复才能使操作更流畅。您的
MyAdapter
始终引用名为mFragment
的片段实例。重新创建活动后,您还需要还原此变量。因此,您需要按以下方式修改MyAdapter
构造函数。public MyAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
List<Fragment> fragments = fm.getFragments();
if(fragments != null) {
for (Fragment f : fragments) {
if (f instanceof ItemListFragment || f instanceof ItemOneDetailFragment) {
mFragment = (BaseFragment) f;
}
}
}
}
此时,您的代码应该可以工作了。但是当您在查看列表项的详细信息时旋转设备时,它将崩溃。发生这种情况是因为您使用活动的
ItemListFragment
直接从FragmentManager
将子片段添加到活动中。而是使用Fragment本身的childFragmentManager。holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mTwoPane) { // landscape mode
...
// show detail fragment to right side of screen
getChildFragmentManager().beginTransaction()
.replace(R.id.item_detail_container, fragment)
.commit();
} else { // portrait mode
...
}
}
});