I am struggling to get my fragment child views to take ActionBar/Toolbar into account when displaying.
I have followed a sample from here which has very nicely partitioned the layout into activity layout and fragments with include tags for AppBar/Toolbar and, in my case, floating action button (fab). I have refactored my already working code from having the AppBar/Toolbar and fab on the activity layout with just the fragment being in a separate layout. But the approach of including the AppBar/Toolbar and fab on the fragment gives me the ability to have a clean activity which can accomodate any fragment, with or without AppBar/Toolbar or fab (or any other UI elements). Below is my basic layout setup. The problem I am straggling with is that my RecyclerView is obscured on the the by the Appbar/Toolbar.
activity_main.axml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Actual content of the screen -->
<FrameLayout
android:id="#+id/main_content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
</android.support.design.widget.CoordinatorLayout>
fragment_list.axml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/list_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include
layout="#layout/include_toolbar_actionbar" />
<MvvmCross.Droid.Support.V4.MvxSwipeRefreshLayout
android:id="#+id/listsRefresher"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:MvxBind="Refreshing IsLoading; RefreshCommand ReloadCommand">
<MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
android:id="#+id/listsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:MvxItemTemplate="#layout/item_list"
app:MvxBind="ItemsSource Lists; ItemClick ShowListItemsCommand" />
</MvvmCross.Droid.Support.V4.MvxSwipeRefreshLayout>
<include
layout="#layout/include_floatingactionbutton" />
</FrameLayout>
include_toolbar_actionbar.axml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/main_tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.ToolBar"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
include_floatingactionbutton.axml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.FloatingActionButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="#dimen/margin_medium"
android:src="#drawable/ic_add_white_24dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:layout_anchor="#id/main_content_frame"
app:layout_anchorGravity="bottom|right|end" />
MainActivity.cs
namespace List.Mobile.Droid.Activities
{
[Activity(
Label = "#string/applicationName",
Icon = "#drawable/ic_icon",
Theme = "#style/AppTheme.Default",
LaunchMode = LaunchMode.SingleTop,
ScreenOrientation = ScreenOrientation.Portrait,
Name = "list.droid.mobile.activities.MainActivity")]
public class MainActivity : MvxAppCompatActivity<MainViewModel>
{
...
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
ViewModel.ShowLists();
}
}
}
ListsFragment.cs
namespace List.Mobile.Droid.Views
{
[MvxFragmentPresentation(typeof(MainViewModel), Resource.Id.main_content_frame, true)]
[Register("list.mobile.droid.views.ListsFragment")]
public class ListsFragment : BaseFragment<ListsViewModel>, ActionMode.ICallback
{
...
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var swipeToRefresh = FragmentView.FindViewById<MvxSwipeRefreshLayout>(Resource.Id.refresher);
if (AppBar != null)
AppBar.OffsetChanged += (sender, args) => swipeToRefresh.Enabled = args.VerticalOffset == 0;
var listsRecyclerView = FragmentView.FindViewById<MvxRecyclerView>(Resource.Id.listsRecyclerView);
...
return FragmentView;
}
}
}
BaseFragment.cs
namespace List.Mobile.Droid.Views
{
public abstract class BaseFragment<T> : MvxFragment<T> where T : MvxViewModel
{
protected abstract int FragmentResourceId { get; }
protected View FragmentView { get; set; }
protected AppBarLayout AppBar { get; set; }
protected FloatingActionButton Fab { get; set; }
protected Toolbar Toolbar { get; set; }
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
FragmentView = this.BindingInflate(FragmentResourceId, null);
AppBar = FragmentView.FindViewById<AppBarLayout>(Resource.Id.main_app_bar);
Fab = FragmentView.FindViewById<FloatingActionButton>(Resource.Id.fab);
Toolbar = FragmentView.FindViewById<Toolbar>(Resource.Id.main_tool_bar);
AppCompatActivity parentActivity = ((AppCompatActivity)Activity);
parentActivity.SetSupportActionBar(Toolbar);
parentActivity.SupportActionBar.SetDisplayHomeAsUpEnabled(false);
parentActivity.SupportActionBar.SetHomeButtonEnabled(false);
return view;
}
}
}
If you combine the layout structure of activity+fragment+include you'll see something like this:
CoordinatorLayout
--FrameLayout -> id=main_content_frame
----FrameLayout -> id=list_frame
------AppBarLayout
--------Toolbar
------SwipeRefresh
--------Recycler
------Fab
That means, you have the toolbar and swipe/recycler in a FrameLayout, and this one on top of the other is exactly the expected behavior.
To fix you should make the AppBar+Swipe+Fab as children of the CoordinatorLayout (which is the layout that properly handles interactions between Toolbar/Fab/Scrolling Content. So change your activity to be just the FrameLayout and re-order the fragment to be:
CoordinatorLayout
--AppBarLayout
----Toolbar
--SwipeRefresh
----RecyclerView
--Fab
and don't forget to add app:layout_behavior="#string/appbar_scrolling_view_behavior" to the SwipeRefreshLayout, that's for the Coordinator to properly position it.
Related
I've followed the material documentation for top app bars and implemented a part of it in my app to be able to hide it, when scrolling down my list.
My Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/ddd"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".pkgTestforend.DriverListFragment">
<com.example.dochjavatestimplementation.pkgTestforend.CustomLinearLayout
android:layout_width="match_parent"
android:id="#+id/cusLL"
android:layout_height="match_parent"
app:elevation="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/listAllDrivers"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
</com.example.dochjavatestimplementation.pkgTestforend.CustomLinearLayout>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true" >
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="PageTitle"
app:menu="#menu/top_app_bar"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:navigationIcon="#drawable/baseline_menu_24"
style="#style/Widget.MaterialComponents.Toolbar.Primary"
/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And the corresponding fragment:
public class DriverListFragment extends Fragment implements View.OnClickListener {
public DriverListFragment() {
}
public static DriverListFragment newInstance(String param1, String param2) {
return new DriverListFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_driver_list, container, false);
setUpToolbar(view);
return view;
}
private void setUpToolbar(View view) {
Toolbar toolbar = view.findViewById(R.id.topAppBar);
AppCompatActivity activity = (AppCompatActivity) getActivity();
if (activity != null) {
activity.setSupportActionBar(toolbar);
}
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
menuInflater.inflate(R.menu.top_app_bar, menu);
super.onCreateOptionsMenu(menu, menuInflater);
}
ListView listViewDriver;
DriverListAdapter adapter;
RoomWithRxJavaViewModel viewModel;
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
listViewDriver = view.findViewById(R.id.listAllDrivers);
viewModel = new RoomWithRxJavaViewModel(getActivity().getApplication());
Disposable d = viewModel.getDrivers()
.subscribe(allusers ->
{
adapter = new DriverListAdapter(getContext(), (ArrayList<Driver>) allusers);
listViewDriver.setAdapter(adapter);
}, e -> {
//show err mes
}
);
}
}
The setup is pretty simple build.
I just have a custom linearlayout which I am planning to modify later. And yes, the relative layout for the listview is on purpose so I can easily modify my future button positions.
The result looks like this:
So the issue is that, altough the app bar at the top is visible, it wont hide when I scroll down my list (see picture above), eventough I am using app:liftOnScroll="true" and app:layout_scrollFlags="scroll|enterAlways|snap".
What am I missing exactly? Is it cause I use my custom linearlayout?
Putting the appbar before the custom linearlayout didn't change the output unfourntatly.
I am using a base activity as a parent of another activity "RecicpeActivity" as I have overridden the method setContentView in the base activity so that the child activity can use it and pass its layout to be inflated in the frame layout.
The Child activity "RecicpeActivity" uses DataBinding to set its views.
What I am doing is I am trying to inflate the layout of the child activity into a frame layout "container" in the base activity BUT the data binding is not being considered at all as I am having a white screen even though I have seen the layout of the child activity as a child of the frame layout while debugging.
I have tried two ways:
1- The first one I have tried to pass the layout of the child activity simply by calling setContentView and inflated the passed layout to the frame layout in the base activity.
2- The second on I have tried to use data binding in the base activity, But I don't think it would matter.
_ChildActivity
public class RecipeActivity extends BaseActivity {
private ActivityRecipeBinding mBinding;
private static final String RECIPE_INTENT_KEY = "recipe key";
private ScrollView mScrollView;
private RecipeViewModel recipeViewModel;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recipeViewModel =
ViewModelProviders.of(this).get(RecipeViewModel.class);
mBinding = DataBindingUtil.inflate(
getLayoutInflater(),
R.layout.activity_recipe, null, false);
setContentView(R.layout.activity_recipe);
// This method is implemented in the BaseActivity.
showProgressBar(true);
recipeViewModel.getRecipe().observe(this, new Observer<Recipe>() {
#Override
public void onChanged(Recipe recipe) {
if (recipe != null){
if (recipe.getRecipe_id().equals(
recipeViewModel.getRecipeId())){
mBinding.setRecipe(recipe);
mScrollView.setVisibility(View.VISIBLE);
showProgressBar(false);
}
}
}
});
recipeViewModel.getRecipeById(getIncomingIntentRecipeId());
}
private String getIncomingIntentRecipeId(){
if (getIntent().hasExtra(RECIPE_INTENT_KEY)){
String recipe_id = getIntent().getStringExtra(RECIPE_INTENT_KEY);
return recipe_id;
}
return null;
}
_BaseActivity
public abstract class BaseActivity extends AppCompatActivity {
public ProgressBar mProgressBar;
#Override
public void setContentView(int layoutResID) {
RelativeLayout mRelativeLayout =
(RelativeLayout) getLayoutInflater().inflate(
R.layout.activity_base, null);
FrameLayout frameLayout = mRelativeLayout.findViewById(
R.id.activity_content);
mProgressBar = mRelativeLayout.findViewById(R.id.progress_bar)
/**
* True means layoutResID should be inflated and made a part of
parent frameLayout
*/
getLayoutInflater().inflate(layoutResID, frameLayout, true);
super.setContentView(mRelativeLayout);
}
public void showProgressBar(boolean visibility){
mProgressBar.setVisibility(visibility ?
View.VISIBLE : View.INVISIBLE);
}
_ChildActivity Layout "activity_recipe"
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="recipe"
type="com.mustafa.foodapp.models.Recipe" />
<import type="com.mustafa.foodapp.util.StringUtils" />
</data>
<ScrollView
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/recipe_image"
android:layout_width="match_parent"
android:layout_height="#dimen/recipe_image_height"
android:scaleType="center"
app:imageUrl="#{recipe.image_url}" />
<TextView
android:id="#+id/recipe_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/recipe_image"
android:padding="7dp"
android:text="#{recipe.title}"
android:textColor="#000"
android:textSize="#dimen/recipe_title_text_size" />
<LinearLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/recipe_title"
android:orientation="horizontal"
android:padding="10dp"
android:weightSum="100">
<TextView
android:id="#+id/recipe_social_score"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="10"
android:gravity="center"
android:text="#{String.valueOf(
Math.round(recipe.social_rank))}"
android:textColor="#color/red"
android:textSize="#dimen/
recipe_publisher_text_size"/>
</LinearLayout>
<LinearLayout
android:id="#+id/ingredients_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/container"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{StringUtils.getStringIngredients(
recipe.ingredients)}" />
</LinearLayout>
</RelativeLayout>
</ScrollView>
</layout>
_BaseActivity Layout "activity_base"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/base_relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<ProgressBar
android:id="#+id/progress_bar"
style="#style/Widget.AppCompat.ProgressBar"
android:layout_width="125dp"
android:layout_height="125dp"
android:layout_centerInParent="true"
android:visibility="gone" />
</RelativeLayout>
_BaseActivity with data-binding "2nd way"
public abstract class BaseActivity extends AppCompatActivity {
public ProgressBar mProgressBar;
public ActivityBaseBinding baseBinding;
#Override
public void setContentView(int layoutResID) {
baseBinding = DataBindingUtil.inflate(
getLayoutInflater(), R.layout.activity_base, null, false);
mProgressBar = baseBinding.progressBar;
/**
* True means layoutResID should be inflated and made a part of the
parent frameLayout
*/
getLayoutInflater().inflate(layoutResID, baseBinding.activityContent,
true);
super.setContentView(baseBinding.getRoot());
}
public void showProgressBar(boolean visibility){
mProgressBar.setVisibility(visibility ?
View.VISIBLE : View.INVISIBLE);
}
Call layout.requestLayout() after inflating so it can adjust to the changes made after inflating.
getLayoutInflater().inflate(layoutResID, baseBinding.activityContent, true);
mRelativeLayout.requestLayout();
super.setContentView(baseBinding.getRoot());
public void requestLayout ()
Call this when something has changed which has invalidated the layout of this view. This will schedule a layout pass of the view tree.
If you can see the child in view heirarchy while debugging, then this should fix the issue.
https://developer.android.com/reference/android/view/View.html#requestLayout()
PART 2: Binding Doesn't work
You are inflating view two times, once in your child activity, and once in your BaseActivity:
mBinding = DataBindingUtil.inflate(
getLayoutInflater(),
R.layout.activity_recipe, null, false);
setContentView(R.layout.activity_recipe);
mBinding You inflated once for databinding, then you passed layout id to setContentView where it inflated again:
getLayoutInflater().inflate(layoutResID, frameLayout, true);
So the one in Databinding is a different view from the one you added to base layout.
Create an Overloaded version of setContentView that accepts view instead of id.
I am trying to implement a settings view invoked from a drawer menu.
Settings view is implemented using a Fragment inheriting from MvxPreferenceFragmentCompat. Code is below:
[MvxFragmentPresentation(typeof(MainViewModel), Resource.Id.main_content_frame)]
[Register(nameof(SettingsFragment))]
public class SettingsFragment : MvxPreferenceFragmentCompat<SettingsViewModel>
{
public override void OnCreatePreferences(Bundle savedInstanceState, string rootKey)
{
SetPreferencesFromResource(Resource.Xml.preferences, rootKey);
}
}
My preferences.xml is shown below:
<?xml version="1.0" encoding="utf-8" ?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="#string/pref_debug_info_title"
android:key="pref_key_debug_info">
<CheckBoxPreference
android:key="pref_key_provide_debug_info"
android:title="#string/pref_title_provide_debug_info"
android:summary="#string/pref_summary_provide_debug_info"
android:defaultValue="false" />
<CheckBoxPreference
android:key="pref_key_provide_debug_info_over_wifi"
android:title="#string/pref_title_debug_info_over_wifi"
android:summary="#string/pref_summary_debug_info_over_wifi"
android:defaultValue="true" />
</PreferenceCategory>
</PreferenceScreen>
Fragment layout is below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="#+id/fragment_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/include_toolbar_actionbar_addlistitem" />
<fragment
android:name="SettingsFragment"
android:id="#+id/settings_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include
layout="#layout/include_floatingactionbutton" />
</android.support.design.widget.CoordinatorLayout>
And finally, main activity XML is here:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_content_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true" />
<FrameLayout
android:id="#+id/navigation_frame"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="left|start" />
</android.support.v4.widget.DrawerLayout>
My preferences show up fine but I don't see the AppBar with a title (that I could set) and there is no arrow to navigate back to the Activity. When I press the back "hardware button" once, nothing happens. When I press it the second time, the app "minimizes" and when I bring it back, I am back at the main activity.
I have done this functionality with "regular" Fragments inheriting from MvxFragment but it seems like I am unable to do so with MvxPreferenceFragmentCompat.
Update 1
Based on input from Trevor, I have update fragment_settings.xml and SettingsFragment.cs as shown below:
fragment_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="#+id/fragment_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/include_toolbar_actionbar" />
<!-- Required ViewGroup for PreferenceFragmentCompat -->
<FrameLayout
android:id="#android:id/list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<include
layout="#layout/include_floatingactionbutton" />
</android.support.design.widget.CoordinatorLayout>
SettingsFragment.cs
[MvxFragmentPresentation(typeof(MainViewModel), Resource.Id.main_content_frame)]
[Register(nameof(SettingsFragment))]
public class SettingsFragment : MvxPreferenceFragmentCompat<SettingsViewModel>, View.IOnClickListener
{
#region properties
protected Toolbar Toolbar { get; set; }
protected AppCompatActivity ParentActivity { get; set; }
#endregion
#region Fragment lifecycle overrides
public override void OnCreatePreferences(Bundle savedInstanceState, string rootKey)
{
AddPreferencesFromResource(Resource.Xml.preferences);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
ParentActivity = ((MainActivity)Activity);
Toolbar = view.FindViewById<Toolbar>(Resource.Id.main_tool_bar);
ParentActivity.SetSupportActionBar(Toolbar);
ParentActivity.SupportActionBar.SetDisplayHomeAsUpEnabled(true);
ParentActivity.SupportActionBar.SetDisplayShowHomeEnabled(true);
...
// TODO: Pull it from a resource
Toolbar.Title = Resources.GetText(Resource.String.settings_view_title);
return view;
}
#endregion
#region View.IOnClickListener implementation
public void OnClick(View v)
{
ParentActivity.OnBackPressed();
}
#endregion
}
But I am still getting null on line ParentActivity.SupportActionBar.SetDisplayHomeAsUpEnabled(true) because my SupportActionBar is null (but Toolbar is not). How come?
Update 2
OK, I got a bit further. I got my AppBar and arrow back and the look and feel is like I have expected it. Still have a problem but more about it below.
What helped? This post.
In short, I needed to declare a custom style for my preference and in it, reference the layout of my preference view. Here are the bit:
Styles.xml
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="preferenceTheme">#style/AppTheme.Preference</item>
</style>
<!-- Custom Preference Theme -->
<style name="AppTheme.Preference"
parent="#style/PreferenceThemeOverlay.v14.Material">
<item name="preferenceFragmentCompatStyle">
#style/AppPreferenceFragmentCompatStyle
</item>
</style>
<!-- Custom Style for PreferenceFragmentCompat -->
<style name="AppPreferenceFragmentCompatStyle"
parent="#style/PreferenceFragment.Material">
<item name="android:layout">#layout/fragment_settings</item>
</style>
For me, this has been the missing link between the view declaration in the SettingsFragment and the layout resource.
The only problem that remains is ability to go back from this fragment back to main activity. From a "regular" MvxFragment (that is invoked from a drawer), I use OnClick method of the IOnClickListener to call ParentActivity.OnBackPressed().
SettingsFragment.cs
public class SettingsFragment : MvxPreferenceFragmentCompat<SettingsViewModel>, View.IOnClickListener
{
...
public void OnClick(View v)
{
ParentActivity.OnBackPressed();
}
}
But this does not work from this MvxPreferenceFragmentCompat. Still searching for that answer.
Update 3
Last missing piece of the puzzle was the AddToBackStack declaration atop the class as shown below. Once this was in place, OnBackPressed() worked correctly.
SettingsFragment.cs
[MvxFragmentPresentation(typeof(MainViewModel), Resource.Id.main_content_frame, AddToBackStack = true)]
[Register(nameof(SettingsFragment))]
public class SettingsFragment : MvxPreferenceFragmentCompat<SettingsViewModel>, View.IOnClickListener
You're missing some code to setup the AppCompatActivity.SupportActionBar like you would in any other fragment. Here is how I've solved the problem:
Here is the SettingsFragment Layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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/coordinator"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
tools:context=".Activities.MainActivity">
<include
layout="#layout/toolbar_actionbar" />
<!-- Required ViewGroup for PreferenceFragmentCompat -->
<FrameLayout
android:id="#android:id/list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
Here is the SettingsFragment implementation:
public class SettingsFragment : MvxPreferenceFragmentCompat<SettingsViewModel>,
View.IOnClickListener
{
private Toolbar _toolbar;
private View _view;
private MainActivity MainActivity => (MainActivity)Activity;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Since the view is created in PreferenceFragmentCompat's OnCreateView we don't use BindingInflate like a typical MvxFragment.
_view = base.OnCreateView(inflater, container, savedInstanceState);
// TODO: Setup MvvmCross Databinding manually since we didn't use BindingInflate like a typical MvxFragment.
_toolbar = _view.FindViewById<Toolbar>(Resource.Id.toolbar);
if (_toolbar != null)
{
MainActivity.SetSupportActionBar(_toolbar);
MainActivity.SupportActionBar.SetDisplayHomeAsUpEnabled(true);
MainActivity.SupportActionBar.SetDisplayShowHomeEnabled(true);
_toolbar.SetNavigationOnClickListener(this);
// TODO: Bind the Toolbar.Title
}
return _view;
}
public override void OnCreatePreferences(Bundle savedInstanceState, string rootKey)
{
AddPreferencesFromResource(Resource.Xml.preferences);
}
public async void OnClick(View v)
{
// Toolbar was clicked
await ViewModel.CloseCommand.ExecuteAsync().ConfigureAwait(false);
}
}
I'm using StarWarsEaxmple from MvvmCross repository and I can't make it work. The output of MvvmCross ,The problem in my opinion is either in different versions of MvvmCross or in Presenter. In Samples version is 5.1.1 and in my project is 5.4.2. And demonstrates weird behavior.
I can see empty drawer when I don't involve MvvmCross NavigationService. However, when I navigating to both ViewModels sequently (as in the example) I can see only Menu Page without drawer and other page frame is even doesn't invoked.
Reference to MvvmCross Sample
Main Activity
[Activity(Icon = "#drawable/icon",
Theme = "#style/AppTheme", LaunchMode = LaunchMode.SingleTop,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : MvxCachingFragmentCompatActivity<MainViewModel>
{
public DrawerLayout DrawerLayout;
//This method is invoked
protected override void OnCreate(Bundle bundle)
{ base.OnCreate(bundle);
SetContentView(Resource.Layout.activity_main);
DrawerLayout = FindViewById<DrawerLayout>(Resource.Id.drawerLayout);
ViewModel.ShowDefaultMenuItem();
}
....
Menu Fragment
[MvxFragment(typeof(MainViewModel), Resource.Id.navigationFrame)]
[Register("VacationManager.Droid.Activities.MenuFragment")]
public class MenuFragment : MvxFragment<MenuViewModel>, NavigationView.IOnNavigationItemSelectedListener
{
private NavigationView _navigationView;
private IMenuItem _previousMenuItem;
//This method is invoked too
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.menu_view, null);
_navigationView = view.FindViewById<NavigationView>(Resource.Id.navigation_view);
_navigationView.SetNavigationItemSelectedListener(this);
return view;
}
}
Main Part of Page
[MvxFragment(typeof(MainViewModel), Resource.Id.bodyFrame, false)]
[Register("VacationManager.Droid.Activities.VacationRequestListFragment")]
public class VacationRequestListFragment : BaseFragment<VacationRequestListViewModel> // You can find BaseFragment in sample
{
protected override int FragmentId => Resource.Layout.fragment_list;
//It is never invoked
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
return this.BindingInflate(Resource.Layout.fragment_list, container, false);
}
}
MainPage Layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/warning">
<!-- Center Side -->
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainFrame">
<include layout="#layout/toolbar" />
<FrameLayout
android:id="#+id/bodyFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="16dp"
android:src="#drawable/bullseye"
local:layout_anchor="#id/bodyFrame"
local:layout_anchorGravity="bottom|right|end" />
</android.support.design.widget.CoordinatorLayout>
<!-- Left Side -->
<FrameLayout
android:id="#+id/navigationFrame"
android:layout_height="match_parent"
android:layout_width="240dp"
android:layout_gravity="left|start"
android:clickable="true" />
</android.support.v4.widget.DrawerLayout>
MainPage Layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.NavigationView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="#+id/navigation_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:theme="#style/ThemeToolbarNavigationView"
android:background="#color/colorPrimary"
local:itemTextColor="#color/light_gray"
local:itemIconTint="#color/light_gray"
local:headerLayout="#layout/navigation_header"
local:menu="#menu/navigation_drawer" />
Main ViewModel
public void ShowDefaultMenuItem()
{
NavigationService.Navigate<VacationRequestListViewModel>();
NavigationService.Navigate<MenuViewModel>();
}
Seems I'm losing small detail... Any help would be appreciated.
The problem was first of all in namespaces of attributes over the activities. They should be MvvmCross.Droid.Views.Fragments . And also instead of MvxFragmentAttribute we need to use MvxFragmentPresentationAttribute. Then it works.
I'm trying to adapt the strategy for hiding / showing a toolbar (or any visual element) from the well explained and great article:
http://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling%28part1%29/
But in my case I'm using a Fragment to hold the recycleview instead of the activity. My problem is that the padding is not being applied so the first element is under the toolbar, and I have also another strange behavior, as the toolbar is also under the statusbar. I don't know what is happening here.
The following are my "moving pieces":
BasicActivity.java: based on the one given on the previous post, but moving away the recycleview part as is going to be on the Fragment piece. Also it exposes the show and hide methods to allow the fragment to access it:
public class BasicActivity extends ActionBarActivity {
private Toolbar mToolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_basic);
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container,new RecycleFragment())
.commit();
overridePendingTransition(0, 0);
initToolbar();
}
private void initToolbar() {
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
setTitle(getString(R.string.app_name));
mToolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
}
public void hideViews() {
mToolbar.animate().translationY(-mToolbar.getHeight()).setInterpolator(new AccelerateInterpolator(2));
}
public void showViews() {
mToolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2));
}
}
My activiy_basic.xml is the following:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<include layout="#layout/toolbar_actionbar" />
</FrameLayout>
The layout toolbar_actionbar.xml
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:clipToPadding="false"/>
The Fragment RecycleFragment.java:
public class RecycleFragment extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recycler, container, false);
return view;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initRecyclerView(view);
}
private void initRecyclerView(View view) {
RecyclerView recyclerView = (RecyclerView)view.findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
RecyclerAdapter recyclerAdapter = new RecyclerAdapter(createItemList());
recyclerView.setAdapter(recyclerAdapter);
recyclerView.setOnScrollListener(new HidingScrollListener() {
#Override
public void onHide() {
((BasicActivity)getActivity()).hideViews();
}
#Override
public void onShow() {
((BasicActivity)getActivity()).showViews();
}
});
}
private List<String> createItemList() {
List<String> itemList = new ArrayList<>();
for(int i=0;i<20;i++) {
itemList.add("Item "+i);
}
return itemList;
}
}
And the layout for the fragment is just a recyclerview fragment_recycler.xml:
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
The adapter and the viewholder for the recycler are the same as the article, and they doesn't affect the behavior.
What is wrong with the code?
UPDATE:
A MichaĆ Z. below pointed out. What was missing is the paddingTop and clipptoPadding on the Recyclerview view
So the final xml should be:
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
android:clipToPadding="false"/>
And to solve the statusbar overlapping problem, it is needed to add a "fitsystemwindows" = "true" element on the activity layout. So it must be as the following:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<include layout="#layout/toolbar_actionbar" />
</FrameLayout>
UPDATE2
The fitSystemWindows is only needed if the theme is setting the statusbar as translucent
Your fragment_recycler.xml file is missing paddingTop and clipToPadding attributes.
It should look like this:
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
android:clipToPadding="false"/>
And also remove clipToPadding from your toolbar_actionbar.xml.