Change Toolbar alpha on scroll android - android

I want to implement an effect similar to the wikipedia and netflix apps, where the toolbar is transparent in the details activity but fades in as the user scrolls down the content. The toolbar's menu items are visible all the time.
This is my layout file:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="#+id/detail_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:fitsSystemWindows="true"/>
</android.support.design.widget.CoordinatorLayout>
The activity contents are placed inside of the NestedScrollView using a fragment transaction.
This is my details activity:
public class DetailActivity extends SearchBaseActivity {
private static final String RESOURCE = "resource";
#BindView(R.id.detail_view_container)
NestedScrollView mContainer;
#BindView(R.id.toolbar)
Toolbar mToolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
ButterKnife.bind(this);
Media media = getIntent().getParcelableExtra(RESOURCE);
setSupportActionBar(mToolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(null);
}
}
public static Intent getStartIntent(Context context, Media media) {
return new Intent(context, DetailActivity.class).putExtra(RESOURCE, media);
}
}
Can you help me to figure this out, please?
Thank you!

I am trying to find a nice way to get the same transition from translucent to opaque status bar when scrolling the content by simply using the CoordinatorLayout but so far I haven't find it.
I did a really basic implementation by adding a scrolllistener to the NestedScrollView:
NestedScrollView nsv = (NestedScrollView) findViewById(R.id.detail_view_container);
nsv.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
int color = Color.parseColor("#FF2D2D2D"); // ideally a global variable
if (scrollY < 256) {
int alpha = (scrollY << 24) | (-1 >>> 8) ;
color &= (alpha);
}
toolbar.setBackgroundColor(color);
}
});
This implementation starts adding opacity to the toolbar as the Y-scroll on the NestedScrollView increases.

Related

How to hide bottom nav bar in fragment

I have bottom nav bar define in the Main activity. I have three fragments linked with BottomNavigation bar in fragments I have recycler view so I want to hide BottomNavigation bar when RecyclerView scrolls down and shows when RecyclerView scrolls up.
My problem is how can I access BottomNavigation bar in fragments because it is defined in MainActivity.
This is my code:
activity_main.xml
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay"
app:elevation="0dp"
android:background="#color/colorPrimary"
android:paddingBottom="7dp"
android:fitsSystemWindows="true">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways|snap">
<Spinner
android:layout_width="110dp"
android:layout_height="50dp"
android:id="#+id/chooseLocation"
app:backgroundTint="#android:color/white"/>
</android.support.v7.widget.Toolbar>
<EditText
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:id="#+id/search"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:paddingRight="6dp"
android:paddingLeft="12dp"
android:hint="Search here"
android:textColorHint="#9e9e9e"
android:textColor="#000"
tools:ignore="HardcodedText"
android:background="#drawable/search_edit_text"
android:paddingEnd="6dp"
android:paddingStart="12dp"/>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
<android.support.design.widget.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/bottomBar"
android:layout_gravity="bottom"
app:menu="#menu/bottom_menu"
android:background="#fff"
app:itemIconTint="#drawable/nav_check"
app:itemTextColor="#drawable/nav_check"/>
</android.support.design.widget.CoordinatorLayout>
fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Tab1Fragment"
android:background="#fff">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/purchasedBook"/>
</RelativeLayout>
This is how my fragments are defined as there is no bottom nav bar in any fragments so how can I access bottom nav bar in fragments.
Someone, please let me know any help would be appreciated.
THANKS
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.id == R.id.full_screen_destination) {
bottomNavigationView.visibility = View.GONE
} else {
bottomNavigationView.visibility = View.VISIBLE
}
}
Do this in the main activity.
Here R.id.full_screen_destination is id of the fragment in navigation fragment.
To access your BottomNavigationView from within the fragments use the following code:
BottomNavigationView navBar = getActivity().findViewById(R.id.bottomBar);
As the fragment is always inside an activity and you can call getActivity() in fragment to access objects that already exist in the activity. So you can do this:
Activity
public class MainActivity extends Activity {
//...
Toolbar toolbar;
//...
public Toolbar getNav() {
return toolbar;
}
//...
}
Fragment
//...
if(getActivity() != null && getActivity instanceOf MainActivity)
((MainActivity)getActivity()).getNav().setVisiblity(View.GONE);
//...
Try this,
Add this line in the BottomNavigationView in Xml
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
And Implement this BottomNavigation behavior using CoOrdinator Layout and you can hide or show the view using the scroll listeners.
public class BottomNavigationViewBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {
private int height;
#Override
public boolean onLayoutChild(CoordinatorLayout parent, BottomNavigationView child, int layoutDirection) {
height = child.getHeight();
return super.onLayoutChild(parent, child, layoutDirection);
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout coordinatorLayout,
BottomNavigationView child, #NonNull
View directTargetChild, #NonNull View target,
int axes, int type)
{
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull BottomNavigationView child,
#NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed,
#ViewCompat.NestedScrollType int type)
{
if (dyConsumed > 0) {
slideDown(child);
} else if (dyConsumed < 0) {
slideUp(child);
}
}
private void slideUp(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(0).setDuration(200);
}
private void slideDown(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(height).setDuration(200);
}
}
Add this line code to your Activity where it contains bottom navigation
bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_nav);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams)
bottomNavigationView .getLayoutParams();
layoutParams.setBehavior(new BottomNavigationViewBehavior());
Try this and let me know Digvijay.Happy Coding.
Fragment has onAttach() method which give you context. So you have to create instance of activity using,
MainActivity mainActivity;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mainActivity = (MainActivity)context;
}
Now make method with boolean param which hide and show bottom bar.
public void visibilityOfBottom(boolean isScroll){
if(isScroll){
// hide bottom bar
} else{
// show bottom bar
}
}
Now access above method in fragment using MainActivity context by,
mainActivity.visibilityOfBottom(false);
Kotlin
Access the navigation view from a fragment
val navBar: BottomNavigationView = activity!!.findViewById(R.id.bottomBar)
The only way I found for hiding bottom navigator in V6
was making structure like this -
Stack Nav {
Bottom Nav
Screen 1
Screen 2
Screen 3
}
Add this code to your fragment. will hide the Bottom nav.
AppCompatActivity activity = (AppCompatActivity) view.getContext();
chipNavigationBar = activity.findViewById(R.id.chipNavigation);
chipNavigationBar.animate().translationY(chipNavigationBar.getHeight()).setDuration(1000);
Use this code :
when Scrolling down the Recyclerview to your fragment will hide the bottom navigation.
then when Scrolled Up it will show the Bottom nav.
private View view;
private AppCompatActivity activity;
private ChipNavigationBar chipNavigationBar;
//...............................................
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (view == null) {
view = inflater.inflate(R.layout.list_fragment, container, false);
hide_NavigationBar_adwhen_Scrolling();
}
return view;
}
//...........................................................
private void hide_NavigationBar_adwhen_Scrolling() {
activity = (AppCompatActivity) view.getContext();
chipNavigationBar = activity.findViewById(R.id.chipNavigation);
RecyclerView recyclerView = view.findViewById(R.id.recylerView);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) {//on_Scrolled_down
// chipNavigationBar.animate().translationY(200).setDuration(500);
chipNavigationBar.animate().translationY(banner_ad_card_1.getHeight()).setDuration(1000);
} else {//on_Scrolled_up
chipNavigationBar.setVisibility(View.VISIBLE);
chipNavigationBar.animate().translationY(0).setDuration(1000);
// chipNavigationBar.setItemSelected(R.id.home, true);
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
}
Add
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
to
BottomNavigationView
app:layout_behavior="#string/appbar_scrolling_view_behavior"
to
RecyclerView

CollapsingToolbarLayout that contains only custom views (layouts)

I have a fragment where I want to use CollapsingToolbarLayout
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="350dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleTextAppearance="#android:color/transparent"
android:fitsSystemWindows="true">
<ImageView
android:id="#+id/header_image"
android:layout_width="match_parent"
android:layout_height="350dp"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
android:contentDescription="#string/app_name"
android:src="#drawable/festival"
app:layout_collapseMode="parallax"/>
<include layout="#+id/custom_layout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
//...custom view /> ...
When collapsing_toolbar is expanded I want to have the image displayed and when collapsed I want to have only the #+id/custom_layout. The custom_layout is a relative layout with a textview and an imageview.
I want to have exactly the same behavior as if I had the following:
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_collapseMode="pin" />
instead of the custom layout.
Why this is not working?
Even though the CollapsingToobarLayout is expanded i see both the ImageView and the custom layout.
!!Note I do have an activity with a toolbar defined. I don't want to touch that part of the code. When I m scrolling up the fragment, I want the #+id/custom_layout to be aligned below the existing toolbar defined in the activity.
I add the following in onViewCreated() method inside the fragment:
RelativeLayout headerLayout = view.findViewById(R.id.custom_layout);
AppBarLayout mAppBarLayout = view.findViewById(R.id.app_bar_layout);
mAppBarLayout.addOnOffsetChangedListener(new
AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (verticalOffset == 0) {
//fully expanded
headerLayout.setVisibility(View.GONE)
} else {
//fully collapsed
headerLayout.setVisibility(View.Visible);
//ISSUE HERE!!!: Only when ImageView has height = 0, the headerLayout pops up.
//!!The transition is not smoothly.
// I would like the headerLayout to be visible when the ImageView height reaches the headerLayout height.
}
}
});
You can do it programatically.In your activity add this listener in OnCreate() method
ImageView headerImage = view.findViewById(R.id.header_image);
AppBarLayout mAppBarLayout = view.findViewById(R.id.app_bar_layout);
mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
boolean isShow = false;
int scrollRange = -1;
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (scrollRange == -1) {
scrollRange = appBarLayout.getTotalScrollRange();
}
if (scrollRange + verticalOffset == 0) {
isShow = true;
headerImage.setVisibility(View.VISIBLE);
} else if (isShow) {
isShow = false;
headerImage.setVisibility(View.GONE);
}
}
});
EDIT to why you can't get the same effect as having the actual Toolbar
The docs state CollapsingToolbarLayout is a wrapper for Toolbar which implements a collapsing app bar. It is designed to be used as a direct child of a AppBarLayout. So it was designed to be used with Toolbar by Google. You can simple create some sort of a workaround to use your custom layout
You can hide the imageView when the collapsingToolbar is collapsed and show again when it is expanded. In your activity class, use onOffsetChangedListener :
AppBarLayout appBarLayout = (AppBarLayout) view.findViewById(R.id.app_bar_layout);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (Math.abs(verticalOffset) == appBarLayout.getTotalScrollRange()) {
// If collapsed, then do this
imageViewHeaderImage.setVisibility(View.GONE);
} else if (verticalOffset == 0) {
// If expanded, then do this
imageViewHeaderImage.setVisibility(View.VISIBLE);
}
}
}););

Toolbar in AppBarLayout is scrollable although RecyclerView has not enough content to scroll

Is it really intended that the Toolbar in a AppBarLayout is scrollable although the main container with the "appbar_scrolling_view_behavior" has not enough content to really scroll?
What I have tested so far:
When I use a NestedScrollView (with "wrap_content" attribute) as main container and a TextView as child, the AppBarLayout works properly and does not scroll.
However, when I use a RecyclerView with only a few entries and the "wrap_content" attribute (so that there is no need to scroll), the Toolbar in the AppBarLayout is scrollable even though the RecyclerView never receives a scroll event (tested with a OnScrollChangeListener).
Here's my layout code:
<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/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="#style/ToolbarStyle" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
With the following effect that the toolbar is scrollable although it's not necessary:
I've also found a way to deal with this by checking if all RecyclerView items are visible and using the setNestedScrollingEnabled() method of the RecyclerView.
Nevertheless, it does seem more like a bug as intended to me. Any opinions? :D
EDIT #1:
For people who are might be interested in my current solution, I had to put the setNestedScrollingEnabled() logic in the postDelayed() method of a Handler with 5 ms delay due to the LayoutManager which always returned -1 when calling the methods to find out whether the first and the last item is visible.
I use this code in the onStart() method (after my RecyclerView has been initialized) and every time after a content change of the RecyclerView occurs.
final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//no items in the RecyclerView
if (mRecyclerView.getAdapter().getItemCount() == 0)
mRecyclerView.setNestedScrollingEnabled(false);
//if the first and the last item is visible
else if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0
&& layoutManager.findLastCompletelyVisibleItemPosition() == mRecyclerView.getAdapter().getItemCount() - 1)
mRecyclerView.setNestedScrollingEnabled(false);
else
mRecyclerView.setNestedScrollingEnabled(true);
}
}, 5);
EDIT #2:
I just played around with a new app and it seems that this (unintended) behavior has been fixed in support library version 23.3.0 (or even earlier). Thus, there is no need for workarounds anymore!
Edit 2:
Turns out the only way to ensure Toolbar is not scrollable when RecyclerView is not scrollable is to set setScrollFlags programmatically which requires to check if RecyclerView's is scrollable. This check has to be done every time adapter is modified.
Interface to communicate with the Activity:
public interface LayoutController {
void enableScroll();
void disableScroll();
}
MainActivity:
public class MainActivity extends AppCompatActivity implements
LayoutController {
private CollapsingToolbarLayout collapsingToolbarLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
collapsingToolbarLayout =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
final FragmentManager manager = getSupportFragmentManager();
final Fragment fragment = new CheeseListFragment();
manager.beginTransaction()
.replace(R.id.root_content, fragment)
.commit();
}
#Override
public void enableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
| AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
);
collapsingToolbarLayout.setLayoutParams(params);
}
#Override
public void disableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
collapsingToolbarLayout.setLayoutParams(params);
}
}
activity_main.xml:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<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_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/root_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="fill_vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.DrawerLayout>
Test Fragment:
public class CheeseListFragment extends Fragment {
private static final int DOWN = 1;
private static final int UP = 0;
private LayoutController controller;
private RecyclerView rv;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
controller = (MainActivity) getActivity();
} catch (ClassCastException e) {
throw new RuntimeException(getActivity().getLocalClassName()
+ "must implement controller.", e);
}
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rv = (RecyclerView) inflater.inflate(
R.layout.fragment_cheese_list, container, false);
setupRecyclerView(rv);
// Find out if RecyclerView are scrollable, delay required
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
controller.enableScroll();
} else {
controller.disableScroll();
}
}
}, 100);
return rv;
}
private void setupRecyclerView(RecyclerView recyclerView) {
final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());
recyclerView.setLayoutManager(layoutManager);
final SimpleStringRecyclerViewAdapter adapter =
new SimpleStringRecyclerViewAdapter(
getActivity(),
// Test ToolBar scroll
getRandomList(/* with enough items to scroll */)
// Test ToolBar pin
getRandomList(/* with only 3 items*/)
);
recyclerView.setAdapter(adapter);
}
}
Sources:
Change scroll flags programmatically
Original code by Chris Banes
Need a postDelayed to ensure RecyclerView children are ready for calculations
Edit:
You should CollapsingToolbarLayout to control the behaviour.
Adding a Toolbar directly to an AppBarLayout gives you access to the enterAlwaysCollapsed and exitUntilCollapsed scroll flags, but not the detailed control on how different elements react to collapsing.
[...] setup uses CollapsingToolbarLayout’s app:layout_collapseMode="pin" to ensure that the Toolbar itself remains pinned to the top of the screen while the view collapses.http://android-developers.blogspot.com.tr/2015/05/android-design-support-library.html
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="#+id/drawer_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
Add
app:layout_collapseMode="pin"
to your Toolbar in xml.
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_collapseMode="pin"
app:theme="#style/ToolbarStyle" />
So, proper credit, this answer almost solved it for me https://stackoverflow.com/a/32923226/5050087. But since it was not showing the toolbar when you actually had an scrollable recyclerview and its last item was visible (it would not show the toolbar on the first scroll up), I decided to modify it and adapt it for an easier implementation and for dynamic adapters.
First, you must create a custom layout behavior for you appbar:
public class ToolbarBehavior extends AppBarLayout.Behavior{
private boolean scrollableRecyclerView = false;
private int count;
public ToolbarBehavior() {
}
public ToolbarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
updatedScrollable(directTargetChild);
return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
#Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
return scrollableRecyclerView && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
private void updatedScrollable(View directTargetChild) {
if (directTargetChild instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) directTargetChild;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter != null) {
if (adapter.getItemCount()!= count) {
scrollableRecyclerView = false;
count = adapter.getItemCount();
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager != null) {
int lastVisibleItem = 0;
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
lastVisibleItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
lastVisibleItem = Math.abs(lastItems[lastItems.length - 1]);
}
scrollableRecyclerView = lastVisibleItem < count - 1;
}
}
}
} else scrollableRecyclerView = true;
}
}
Then, you only need to define this behavior for you appbar in your layout file:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_behavior="com.yourappname.whateverdir.ToolbarBehavior"
>
I haven't tested it for screen rotation so let me know if it works like this. I guess it should work as I don't think the count variable is saved when the rotation happens, but let me know if it doesn't.
This was the easiest and cleanest implementation for me, enjoy it.
It is not a bug, all the events in a viewGroup are handled this way. Because your recyclerview is a child of coordinatorLayout so whenever the event is generated, it is first checked for parent and if parent is not interested only then it is passed down to child.
See google documentation
Something like this in a LayoutManager subclass seems to result in the desired behavior:
#Override
public boolean canScrollVertically() {
int firstCompletelyVisibleItemPosition = findFirstCompletelyVisibleItemPosition();
if (firstCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
int lastCompletelyVisibleItemPosition = findLastCompletelyVisibleItemPosition();
if (lastCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
if (firstCompletelyVisibleItemPosition == 0 &&
lastCompletelyVisibleItemPosition == getItemCount() - 1)
return false;
return super.canScrollVertically();
}
The documentation for canScrollVertically() says:
/**
* Query if vertical scrolling is currently supported. The default implementation
* returns false.
*
* #return True if this LayoutManager can scroll the current contents vertically
*/
Notice the wording of "can scroll the current contents vertically", which I believe implies that the current state should be reflected by the return value.
However, that is not done by any of the LayoutManager subclasses provided through the v7 recyclerview library (23.1.1), which makes me somewhat hesitant whether it is a correct solution; it might cause undesired effects in other situations than the one discussed in this question.
I've implemented it using my own Behavior class which might be attached to AppBarLayout:
public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {
private RecyclerView recyclerView;
private int additionalHeight;
public CustomAppBarLayoutBehavior(RecyclerView recyclerView, int additionalHeight) {
this.recyclerView = recyclerView;
this.additionalHeight = additionalHeight;
}
public boolean isRecyclerViewScrollable(RecyclerView recyclerView) {
return recyclerView.computeHorizontalScrollRange() > recyclerView.getWidth() || recyclerView.computeVerticalScrollRange() > (recyclerView.getHeight() - additionalHeight);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
if (isRecyclerViewScrollable(mRecyclerView)) {
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
return false;
}
}
And below is the code how to set this behavior:
final View appBarLayout = ((DrawerActivity) getActivity()).getAppBarLayoutView();
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(recyclerView, getResources().getDimensionPixelSize(R.dimen.control_bar_height)));
I suggested you try this sample that for support desing library elements.
this a layout like your layout in the sample.
<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_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
Thanks, I created a custom class of RecyclerView but the key is still using setNestedScrollingEnabled(). It worked fine on my side.
public class RecyclerViewCustom extends RecyclerView implements ViewTreeObserver.OnGlobalLayoutListener
{
public RecyclerViewCustom(Context context)
{
super(context);
}
public RecyclerViewCustom(Context context, #Nullable AttributeSet attrs)
{
super(context, attrs);
}
public RecyclerViewCustom(Context context, #Nullable AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
/**
* This supports scrolling when using RecyclerView with AppbarLayout
* Basically RecyclerView should not be scrollable when there's no data or the last item is visible
*
* Call this method after Adapter#updateData() get called
*/
public void addOnGlobalLayoutListener()
{
this.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout()
{
// If the last item is visible or there's no data, the RecyclerView should not be scrollable
RecyclerView.LayoutManager layoutManager = getLayoutManager();
final RecyclerView.Adapter adapter = getAdapter();
if (adapter == null || adapter.getItemCount() <= 0 || layoutManager == null)
{
setNestedScrollingEnabled(false);
}
else
{
int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
boolean isLastItemVisible = lastVisibleItemPosition == adapter.getItemCount() - 1;
setNestedScrollingEnabled(!isLastItemVisible);
}
unregisterGlobalLayoutListener();
}
private void unregisterGlobalLayoutListener()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else
{
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
}
I would like to add a little to user3623735's answer. The following code is absolutely incorrect.
// Find out if RecyclerView are scrollable, delay required
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
controller.enableScroll();
} else {
controller.disableScroll();
}
}
}, 100);
And even when it works - it doesn't cover all cases. There is absolutely no guarantee that a data will be displayed in 100 ms, and the data can stretch the height of the view in the process of working with it, not only in the onCreateView method. That's why you should use next code and track changes in view height:
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if(bottom != oldBottom)
{
mActivity.setScrollEnabled(view.canScrollVertically(0) || view.canScrollVertically(1));
}
}
});
Moreover no need to create two separated method to control scrolling status, you should use one setScrollEnabled method:
public void setScrollEnabled(boolean enabled) {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
mToolbar.getLayoutParams();
params.setScrollFlags(enabled ?
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS : 0);
mToolbar.setLayoutParams(params);
}
In your Toolbar remove the scroll flag, leaving only the enterAlways flag and you should get the effect you intended. For completeness, your layout should look like:
<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/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="enterAlways"
app:theme="#style/ToolbarStyle" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>

How to make the Toolbar snap into view or out of view when using Google Design Library?

I am trying to achieve an effect like WhatsApp has, where the Toolbar (when scrolled) will clip into view magnetlike, or out of view magnetlike.
What I have im my MainActivity XML:
DrawerLayout - Base Layout
CoordinatorLayout - Layout for the Appbar and Toolbar and Tabs
AppBarLayout - For holding Toolbar and Tabs
Toolbar - has THIS flag: app:layout_scrollFlags="scroll|enterAlways"
SlidingTabLayout - Displays tabs
ViewPager - For tabs
RecyclerView - For coordinatorlayout
Now dont get me wrong, it works, when I scroll down the toolbar gets pushed out of view but say I stop scrolling halfway, then the toolbar just sits there half hidden out of view and the other half in view..
How can I approach solving this problem, as I want it to either snap out of view or into view.
This feature has been added in 23.1.0 version of android support library.
From release notes:
Added edge snapping support to the AppBarLayout class by adding the
SCROLL_FLAG_SNAP constant. When scrolling ends, if the view is only
partially visible, the view is snapped and scrolled to its closest
edge.
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways|snap" />
-----
-----
For more info: http://android-developers.blogspot.in/2015/10/android-support-library-231.html
EDIT: as of support 23.1.0 this is no longer needed. See this answer instead.
One possible way to solve this is customizing the Behavior set to your AppBarLayout.
<android.support.design.widget.AppBarLayout
app:layout_behavior="com.myapp.AppBarLayoutSnapBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
Your AppBarLayoutSnapBehavior would change the default behavior of AppBarLayout.Behavior, by adding the snap logic when the scroll stops.
Hopefully, the code below is self explanatory.
package com.myapp;
public class AppBarLayoutSnapBehavior extends AppBarLayout.Behavior {
private ValueAnimator mAnimator;
private boolean mNestedScrollStarted = false;
public AppBarLayoutSnapBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes) {
mNestedScrollStarted = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
if (mNestedScrollStarted && mAnimator != null) {
mAnimator.cancel();
}
return mNestedScrollStarted;
}
#Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
if (!mNestedScrollStarted) {
return;
}
mNestedScrollStarted = false;
int scrollRange = child.getTotalScrollRange();
int topOffset = getTopAndBottomOffset();
if (topOffset <= -scrollRange || topOffset >= 0) {
// Already fully visible or fully invisible
return;
}
if (topOffset < -(scrollRange / 2f)) {
// Snap up (to fully invisible)
animateOffsetTo(-scrollRange);
} else {
// Snap down (to fully visible)
animateOffsetTo(0);
}
}
private void animateOffsetTo(int offset) {
if (mAnimator == null) {
mAnimator = new ValueAnimator();
mAnimator.setInterpolator(new DecelerateInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
setTopAndBottomOffset((int) animation.getAnimatedValue());
}
});
} else {
mAnimator.cancel();
}
mAnimator.setIntValues(getTopAndBottomOffset(), offset);
mAnimator.start();
}
}
The only thing is, the scroll view (in my case a RecyclerView) snaps along with the Toolbar. I actually like it this way, but I'm not sure that's what you want.
I just hided action bar layout in main activity and set span for CollapsingToolbarLayout.
it works for me.
in main activity
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().hide();
CollapsingToolbarLayout collapsingToolbar =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbar.setTitle("Name");
loadBackdrop();
and layout_activity_main
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/detail_backdrop_height"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|snap"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp">
<ImageView
android:id="#+id/backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

Creating a Preference Screen with support (v21) Toolbar

I was having trouble using the new Material Design toolbar in the support library on a Preference screen.
I have a settings.xml file as below:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="#string/AddingItems"
android:key="pref_key_storage_settings">
<ListPreference
android:key="pref_key_new_items"
android:title="#string/LocationOfNewItems"
android:summary="#string/LocationOfNewItemsSummary"
android:entries="#array/new_items_entry"
android:entryValues="#array/new_item_entry_value"
android:defaultValue="1"/>
</PreferenceCategory>
</PreferenceScreen>
The strings are defined elsewhere.
Please find the GitHub Repo: Here
A bit late to the party, but this is my solution that I am using as a work around continuing to use PreferenceActivity:
settings_toolbar.xml :
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/toolbar"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:navigationContentDescription="#string/abc_action_bar_up_description"
android:background="?attr/colorPrimary"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="#string/action_settings"
/>
SettingsActivity.java :
public class SettingsActivity extends PreferenceActivity {
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
Toolbar bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
bar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
}
Result :
UPDATE (Gingerbread Compatibility) :
As per the comments, Gingerbread Devices are returning NullPointerException on this line:
LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
FIX:
SettingsActivity.java :
public class SettingsActivity extends PreferenceActivity {
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Toolbar bar;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
} else {
ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
ListView content = (ListView) root.getChildAt(0);
root.removeAllViews();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
int height;
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}else{
height = bar.getHeight();
}
content.setPadding(0, height, 0, 0);
root.addView(content);
root.addView(bar);
}
bar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
}
Any issues with the above let me know!
UPDATE 2: TINTING WORKAROUND
As pointed out in many dev notes PreferenceActivity does not support tinting of elements, however by utilising a few internal classes you CAN achieve this. That is until these classes are removed. (Works using appCompat support-v7 v21.0.3).
Add the following imports:
import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
import android.support.v7.internal.widget.TintRadioButton;
import android.support.v7.internal.widget.TintSpinner;
Then override the onCreateView method:
#Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// Allow super to try and create a view first
final View result = super.onCreateView(name, context, attrs);
if (result != null) {
return result;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
// standard framework versions
switch (name) {
case "EditText":
return new TintEditText(this, attrs);
case "Spinner":
return new TintSpinner(this, attrs);
case "CheckBox":
return new TintCheckBox(this, attrs);
case "RadioButton":
return new TintRadioButton(this, attrs);
case "CheckedTextView":
return new TintCheckedTextView(this, attrs);
}
}
return null;
}
Result:
AppCompat 22.1
AppCompat 22.1 introduced new tinted elements, meaning that there is no longer a need to utilise the internal classes to achieve the same effect as the last update. Instead follow this (still overriding onCreateView):
#Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// Allow super to try and create a view first
final View result = super.onCreateView(name, context, attrs);
if (result != null) {
return result;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
// standard framework versions
switch (name) {
case "EditText":
return new AppCompatEditText(this, attrs);
case "Spinner":
return new AppCompatSpinner(this, attrs);
case "CheckBox":
return new AppCompatCheckBox(this, attrs);
case "RadioButton":
return new AppCompatRadioButton(this, attrs);
case "CheckedTextView":
return new AppCompatCheckedTextView(this, attrs);
}
}
return null;
}
NESTED PREFERENCE SCREENS
A lot of people are experiencing issues with including the Toolbar in a nested <PreferenceScreen /> however, I have found a solution!! - After a lot of trial and error!
Add the following to your SettingsActivity:
#SuppressWarnings("deprecation")
#Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
super.onPreferenceTreeClick(preferenceScreen, preference);
// If the user has clicked on a preference screen, set up the screen
if (preference instanceof PreferenceScreen) {
setUpNestedScreen((PreferenceScreen) preference);
}
return false;
}
public void setUpNestedScreen(PreferenceScreen preferenceScreen) {
final Dialog dialog = preferenceScreen.getDialog();
Toolbar bar;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
LinearLayout root = (LinearLayout) dialog.findViewById(android.R.id.list).getParent();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
} else {
ViewGroup root = (ViewGroup) dialog.findViewById(android.R.id.content);
ListView content = (ListView) root.getChildAt(0);
root.removeAllViews();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
int height;
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}else{
height = bar.getHeight();
}
content.setPadding(0, height, 0, 0);
root.addView(content);
root.addView(bar);
}
bar.setTitle(preferenceScreen.getTitle());
bar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
}
});
}
The reason that PreferenceScreen's are such a pain is because they are based as a wrapper dialog, so we need to capture the dialog layout to add the toolbar to it.
Toolbar Shadow
By design importing the Toolbar does not allow for elevation and shadowing in pre-v21 devices, so if you would like to have elevation on your Toolbar you need to wrap it in a AppBarLayout:
settings_toolbar.xml :
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
.../>
</android.support.design.widget.AppBarLayout>
Not forgetting to add the add the Design Support library as a dependency in build.gradle file:
compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'
Android 6.0
I have investigated the reported overlapping issue and I cannot reproduce the issue.
The full code in use as above produces the following:
If I am missing something please let me know via this repo and I will investigate.
You can use a PreferenceFragment, as an alternative to PreferenceActivity. So, here is the wrapping Activity example:
public class MyPreferenceActivity extends ActionBarActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pref_with_actionbar);
android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(uk.japplications.jcommon.R.id.toolbar);
setSupportActionBar(toolbar);
getFragmentManager().beginTransaction().replace(R.id.content_frame, new MyPreferenceFragment()).commit();
}
}
And here is the layout file (pref_with_actionbar):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="#dimen/action_bar_height"
android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:theme="#style/ToolbarTheme.Base"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
<FrameLayout
android:id="#+id/content_frame"
android:layout_below="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
And finally the PreferenceFragment:
public static class MyPreferenceFragment extends PreferenceFragment{
#Override
public void onCreate(final Bundle savedInstanceState){
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
}
}
Completely new update.
With some experimentation, I seem to have found the working AppCompat 22.1+ solution for nested preference screens.
First, as it's mentioned in many answers (including one here), you'll need to use the new AppCompatDelegate. Either
use the AppCompatPreferenceActivity.java file from the support demos
(https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java) and simply extend from it, or copy the relevant functions into your own PreferenceActivity. I'll show the first approach here:
public class SettingsActivity extends AppCompatPreferenceActivity {
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.settings, target);
setContentView(R.layout.settings_page);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar bar = getSupportActionBar();
bar.setHomeButtonEnabled(true);
bar.setDisplayHomeAsUpEnabled(true);
bar.setDisplayShowTitleEnabled(true);
bar.setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
bar.setTitle(...);
}
#Override
protected boolean isValidFragment(String fragmentName) {
return SettingsFragment.class.getName().equals(fragmentName);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
}
return super.onOptionsItemSelected(item);
}
}
The accompanying layout is rather simple and usual (layout/settings_page.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:orientation="vertical"
android:padding="0dp">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="#style/..."/>
<ListView
android:id="#id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
The preferences themselves are defined as usual (xml/settings.xml):
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="com.example.SettingsFragment"
android:summary="#string/..."
android:title="#string/...">
<extra
android:name="page"
android:value="page1"/>
</header>
<header
android:fragment="com.example.SettingsFragment"
android:summary="#string/..."
android:title="#string/...">
<extra
android:name="page"
android:value="page2"/>
</header>
...
</preference-headers>
No real difference to solutions on the net until this point. Actually, you can use this even if you don't have nested screens, no headers, just a single screen.
We use a common PreferenceFragment for all deeper pages, differentiated by the extra parameters in the headers. Each page will have a separate XML with a common PreferenceScreen inside (xml/settings_page1.xml et al.). The fragment uses the same layout as the activity, including the toolbar.
public class SettingsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTheme(R.style...);
if (getArguments() != null) {
String page = getArguments().getString("page");
if (page != null)
switch (page) {
case "page1":
addPreferencesFromResource(R.xml.settings_page1);
break;
case "page2":
addPreferencesFromResource(R.xml.settings_page2);
break;
...
}
}
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.settings_page, container, false);
if (layout != null) {
AppCompatPreferenceActivity activity = (AppCompatPreferenceActivity) getActivity();
Toolbar toolbar = (Toolbar) layout.findViewById(R.id.toolbar);
activity.setSupportActionBar(toolbar);
ActionBar bar = activity.getSupportActionBar();
bar.setHomeButtonEnabled(true);
bar.setDisplayHomeAsUpEnabled(true);
bar.setDisplayShowTitleEnabled(true);
bar.setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
bar.setTitle(getPreferenceScreen().getTitle());
}
return layout;
}
#Override
public void onResume() {
super.onResume();
if (getView() != null) {
View frame = (View) getView().getParent();
if (frame != null)
frame.setPadding(0, 0, 0, 0);
}
}
}
Finally, a quick summary of how this actually works. The new AppCompatDelegate allows us to use any activity with AppCompat features, not only those extending from the activities actually in AppCompat. This means that we can turn the good old PreferenceActivity into a new one and add the toolbar as usual. From that point on, we can stick to the old solutions regarding preference screens and headers, without any deviation from the existing documentation. There is just one important point: don't use onCreate() in the activity because it will lead to errors. Use onBuildHeaders() for all operations like adding the toolbar.
The only real difference is, and that's what makes it work with nested screens is that you can use the same approach with the fragments. You can use their onCreateView() the same way, inflating your own layout instead of the system one, adding the toolbar the same way as in the activity.
If you want to use PreferenceHeaders you can use the following approach:
import android.support.v7.widget.Toolbar;
public class MyPreferenceActivity extends PreferenceActivity
Toolbar mToolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
LinearLayout content = (LinearLayout) root.getChildAt(0);
LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.activity_settings, null);
root.removeAllViews();
toolbarContainer.addView(content);
root.addView(toolbarContainer);
mToolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar);
}
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
// Other methods
}
layout/activity_settings.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:theme="#style/AppTheme"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
</LinearLayout>
You can use whatever layout you prefer here, just make sure you adjust it in the Java code as well.
And finally, your file with headers (xml/pref_headers.xml)
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="com.example.FirstFragment"
android:title="#string/pref_header_first" />
<header
android:fragment="com.example.SecondFragment"
android:title="#string/pref_header_second" />
</preference-headers>
With the release of the Android Support Library 22.1.0 and the new AppCompatDelegate, here you can find a nice sample of an implementation of the PreferenceActivity with material support with backwards compatibility.
Update
It works on nested screens too.
https://android.googlesource.com/platform/development/+/marshmallow-mr3-release/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java
While the above answers seem elaborate, if you want a quick fix solution to use Toolbar with support API 7 and up all the while extending PreferenceActivity, I got help from this project below.
https://github.com/AndroidDeveloperLB/ActionBarPreferenceActivity
activity_settings.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/app_theme_light"
app:popupTheme="#style/Theme.AppCompat.Light"
app:theme="#style/Theme.AppCompat" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="#dimen/padding_medium" >
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
SettingsActivity.java
public class SettingsActivity extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
addPreferencesFromResource(R.xml.preferences);
toolbar.setClickable(true);
toolbar.setNavigationIcon(getResIdFromAttribute(this, R.attr.homeAsUpIndicator));
toolbar.setTitle(R.string.menu_settings);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
private static int getResIdFromAttribute(final Activity activity, final int attr) {
if (attr == 0) {
return 0;
}
final TypedValue typedvalueattr = new TypedValue();
activity.getTheme().resolveAttribute(attr, typedvalueattr, true);
return typedvalueattr.resourceId;
}
}
I too have been looking for a solution to adding the v7 support toolbar (API 25) to the AppCompatPreferenceActivity (that is automatically created by AndroidStudio when adding a SettingsActivity). After reading several solutions and trying each of them out I struggled to get the generated PreferenceFragment examples to display with a toolbar as well.
A modified solution that sort of worked was from "Gabor".
One of the caveats I faced was 'onBuildHeaders' only fires once. If you turn a device (like a phone) sideways, the view recreates and the PreferenceActivity is left without a toolbar again, however the PreferenceFragments would retain theirs.
I tried using 'onPostCreate' to call 'setContentView', while this worked to recreate the toolbar when the orientation changed, PreferenceFragments would then be rendered blank.
What I have come up with leverages just about every tip and answer I could read about this subject. I hope others find it useful as well.
We'll start with the Java
First in (the generated) AppCompatPreferenceActivity.java I modified 'setSupportActionBar' like so:
public void setSupportActionBar(#Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
ActionBar bar = getDelegate().getSupportActionBar();
bar.setHomeButtonEnabled(true);
bar.setDisplayHomeAsUpEnabled(true);
}
Second, I created a new class named AppCompatPreferenceFragment.java (it is current an unused name, although it may not stay that way!):
abstract class AppCompatPreferenceFragment extends PreferenceFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_settings, container, false);
if (view != null) {
Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar_settings);
((AppCompatPreferenceActivity) getActivity()).setSupportActionBar(toolbar);
}
return view;
}
#Override
public void onResume() {
super.onResume();
View frame = (View) getView().getParent();
if (frame != null) frame.setPadding(0, 0, 0, 0);
}
}
This is the portion of Gabor's answer that worked.
Last, To get consistency we need to make some changes to SettingsActivity.java:
public class SettingsActivity extends AppCompatPreferenceActivity {
boolean mAttachedFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
mAttachedFragment = false;
super.onCreate(savedInstanceState);
}
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
#Override
public void onAttachFragment(Fragment fragment) {
mAttachedFragment = true;
super.onAttachFragment(fragment);
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
//if we didn't attach a fragment, go ahead and apply the layout
if (!mAttachedFragment) {
setContentView(R.layout.activity_settings);
setSupportActionBar((Toolbar)findViewById(R.id.toolbar_settings));
}
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends AppCompatPreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
setHasOptionsMenu(true);
bindPreferenceSummaryToValue(findPreference("example_text"));
bindPreferenceSummaryToValue(findPreference("example_list"));
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
startActivity(new Intent(getActivity(), SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
}
Some code has been left out of the activity for brevity. The key components here are 'onAttachedFragment', 'onPostCreate', and that the 'GeneralPreferenceFragment' now extends the custom 'AppCompatPreferenceFragment' instead of PreferenceFragment.
Code Summary: If a fragment is present, the fragment injects the new layout and calls the modified 'setSupportActionBar' function. If the fragment is not present, SettingsActivity injects the new layout on 'onPostCreate'
Now on to the XML (very simple):
activity_settings.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/app_bar_settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
app_bar_settings.xml:
<?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/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".SettingsActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.NoActionBar.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar_settings"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.NoActionBar.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_settings" />
</android.support.design.widget.CoordinatorLayout>
content_settings.xml:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context=".SettingsActivity"
tools:showIn="#layout/app_bar_settings">
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
End Result:
I have a new (possibly neater) solution, that uses the AppCompatPreferenceActivity from the Support v7 samples. With this code in hand I created my own layout that includes a toolbar:
<?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:layout_width="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" tools:context="edu.adelphi.Adelphi.ui.activity.MainActivity">
<android.support.design.widget.AppBarLayout android:id="#+id/appbar"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar android:id="#+id/toolbar"
android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" app:popupTheme="#style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<FrameLayout android:id="#+id/content"
android:layout_width="match_parent" android:layout_height="match_parent"/>
</android.support.design.widget.CoordinatorLayout>
Then, in my AppCompatPreferenceActivity, I altered setContentView to create a my new layout, and place the provided layout inside my FrameLayout:
#Override
public void setContentView(#LayoutRes int layoutResID) {
View view = getLayoutInflater().inflate(R.layout.toolbar, null);
FrameLayout content = (FrameLayout) view.findViewById(R.id.content);
getLayoutInflater().inflate(layoutResID, content, true);
setContentView(view);
}
Then I just extend AppCompatPreferenceActivity, allowing me to call setSupportActionBar((Toolbar) findViewById(R.id.toolbar)), and inflate menu items in the toolbar as well. All while keeping the benefits of a PreferenceActivity.
Let's keep it simple & clean here, without breaking any in-built layout
import android.support.design.widget.AppBarLayout;
import android.support.v4.app.NavUtils;
import android.support.v7.widget.Toolbar;
private void setupActionBar() {
Toolbar toolbar = new Toolbar(this);
AppBarLayout appBarLayout = new AppBarLayout(this);
appBarLayout.addView(toolbar);
final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
final ViewGroup window = (ViewGroup) root.getChildAt(0);
window.addView(appBarLayout, 0);
setSupportActionBar(toolbar);
// Show the Up button in the action bar.
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onBackPressed();
}
});
}
I found this simple solution while working on this.
First we need to create a layout for settings activity.
activity_settings.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.my.package">
<android.support.v7.widget.Toolbar
android:id="#+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:elevation="#dimen/appbar_elevation"
app:navigationIcon="?attr/homeAsUpIndicator"
app:navigationContentDescription="#string/abc_action_bar_up_description"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
<ListView
android:id="#android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/tool_bar" />
</RelativeLayout>
Make sure you add a list view with android:id="#android:id/list", otherwise it will throw NullPointerException
Next step is to add (Override) onCreate method in your settings activity
Settings.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
Toolbar toolbar = (Toolbar) findViewById(R.id.tool_bar);
toolbar.setTitle(R.string.action_settings);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
Make sure you import android.suppoer.v7.widget.Toolbar. This should work pretty much on all APIs above 16 (Jelly Bean and up)
I would like to continue the marked solution of James Cross, since after that there's a problem of closing only the active nested screen (PreferenceFragment) in the way to not close the SettingsActivity as well.
Actually it does work on all nested screens (so I don't understand the solution of Gábor that I tried without success, well it works until a certain point but it's a mess of multiple toolbars), because when the user click a sub preference screen, only the fragment is changed (see <FrameLayout android:id="#+id/content_frame" .../>) not the Toolbar that remains always active and visible, but a custom behavior should be implemented to close each fragment accordingly.
In the main class SettingsActivity that extends ActionBarActivity the following methods should be implemented. Note that private setupActionBar() is called from onCreate()
private void setupActionBar() {
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
//Toolbar will now take on default Action Bar characteristics
setSupportActionBar(toolbar);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStackImmediate();
//If the last fragment was removed then reset the title of main
// fragment (if so the previous popBackStack made entries = 0).
if (getFragmentManager().getBackStackEntryCount() == 0) {
getSupportActionBar()
.setTitle(R.string.action_settings_title);
}
} else {
super.onBackPressed();
}
}
For the title of the chosen nested screen you should get the reference of your Toolbar and set the appropriate title with toolbar.setTitle(R.string.pref_title_general); (for instance).
There's no need to implement the getSupportActionBar() in all PreferenceFragment since only the view of the fragment is changed at every commit, not the Toolbar;
There's no need to create a fake ToolbarPreference class to add in each preference.xml (see Gábor's answer).
Here's a library I've made that is based on AOSP code, which adds tinting to both the preferences and the dialogs, adds an action bar, and supports all versions from API 7 :
https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary
Well, this is still an issue for me today (18 Nov 2015). I have tried all the solutions from this thread but there were two main things I couldn't solve:
Nested preference screens appeared without toolbar
Preferences didn't have the Material look on pre-Lollipop devices
So I ended up creating a library with a more complicated solution. Basically, I had to internally apply styles to the preferences if we are using a pre-Lollipop device and I also handled the nested screens using a custom fragment (restoring all the nested hierarchy taking advantage of the PreferenceScreen key).
The library is this one: https://github.com/ferrannp/material-preferences
And if you are interested in the source code (too long to post it here), this is basically the core of it: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/java/com/fnp/materialpreferences/PreferenceFragment.java

Categories

Resources