This project is an extension of the Master/Detail flow template that can be found in Android Studio. The difference is that this application uses a single Activity and a ViewPager that manages three Fragments. The third Fragment is the Master (list) Fragment, which contains a clickable RecycleView. When a list item is clicked, it switches the Fragment with the Child (detail) Fragment.
While the project works, I'd like to avoid using android:configChanges= "orientation|keyboardHidden|screenSize"
in the manifest. How should I do this?
If the attribute is removed, mListener in ItemListFragment is destroyed along with ItemFragmentList when rotating but is never re-created when ItemListFragment is re-created. This results in nothing happening when clicking on a List item in Portrait mode.
My initial solution was to manually override the configuration change handling, which meant ItemListFragment was not destroyed when rotating the screen. onConfigurationChanged()
and populateViewForOrientation()
were added to re-inflate the layout. Surely there is a better solution than to manually override configuration handling.
Project available at: https://github.com/lukeallison/ViewPagerMasterDetail,
Video: 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();
}
}
Interface: 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 PageChang