Hi I want to make scrollable toolbar with searchbox. on scrolling toolbar should be hide toolbar and show search box. Give me some suggestion that how i can build this kind of UI for my application.
Here i want something like this.
Here i am posting my xml design that is doing functionality near by this design but still it need furnishing.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"
app:statusBarScrim="?attr/colorAccent">
<androidx.constraintlayout.motion.widget.MotionLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/collapsing_toolbar">
<View
android:id="@+id/toolbars"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="#efefef" />
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="56dp"
android:layout_marginEnd="16dp"
android:background="@android:color/white"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.motion.widget.MotionLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#efefef"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginStart="16dp"
android:layout_marginEnd="?android:attr/actionBarSize"
android:background="@android:color/white"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="56dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
res/xml/collapsing_toolbar.xml
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/end">
<OnSwipe
motion:dragDirection="dragDown"
motion:touchAnchorId="@id/toolbars"
motion:touchAnchorSide="bottom" />
<OnSwipe />
</Transition>
<ConstraintSet android:id="@+id/start">
/** Very simple animation no need for start state*/
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="24dp"
android:elevation="4dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="56dp" />
</ConstraintSet>
below code placed in on Create Mehod
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
boolean isSearchViewVisible = isVisible(v);
if (isSearchViewVisible){
searchView.setVisibility(View.GONE);
searchViewMain.setVisibility(View.VISIBLE);
} else{
searchView.setVisibility(View.VISIBLE);
searchViewMain.setVisibility(View.GONE);
}
}
});
}
private boolean isVisible(View view){
Rect scrollBounds = new Rect();
nestedScrollView.getDrawingRect(scrollBounds);
float top = 0f;
View view1 = view;
while (view1 instanceof NestedScrollView){
top += (view1).getY();
view1 = (View)view1.getParent();
}
float bottom = top + view.getHeight();
return scrollBounds.top < bottom && scrollBounds.bottom >top;
}
You could use two searchviews (one inside toolbar and the other below toolbar) to achieve this. In your layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".ScrollSearchActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#efefef"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginStart="16dp"
android:layout_marginEnd="?android:attr/actionBarSize"
android:background="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:padding="4dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="56dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then in your activity, toggle the visibility of the search in the toolbar when the searchview below it is scrolled in and out of the screen.
public class ScrollSearchActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroll_search_2);
final SearchView mainSearchView = findViewById(R.id.search_view);
final SearchView toolbarSearchView = findViewById(R.id.search_view_2);
final NestedScrollView nestedScrollView = findViewById(R.id.nestedScrollView);
nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
boolean isSearchViewVisible = isSearchViewVisible(v,mainSearchView);
if (isSearchViewVisible){
toolbarSearchView.setVisibility(View.GONE);
mainSearchView.setVisibility(View.VISIBLE);
} else{
toolbarSearchView.setVisibility(View.VISIBLE);
mainSearchView.setVisibility(View.GONE);
}
}
});
}
private boolean isSearchViewVisible(NestedScrollView nestedScrollView, View view){
Rect scrollBounds = new Rect();
nestedScrollView.getDrawingRect(scrollBounds);
float top = 0f;
View view1 = view;
while (!(view1 instanceof NestedScrollView)){
top += (view1).getY();
view1 = (View)view1.getParent();
}
float bottom = top + view.getHeight();
return scrollBounds.top < bottom && scrollBounds.bottom >top;
}
}
You should get something like this:
EDIT
Use this in place of your isVisible() method
private boolean isViewVisible(NestedScrollView nestedScrollView, View view){
Rect scrollBounds = new Rect();
nestedScrollView.getDrawingRect(scrollBounds);
float top = 0f;
View view1 = view;
while (!(view1 instanceof NestedScrollView)){
top += (view1).getY();
view1 = (View)view1.getParent();
}
float bottom = top + view.getHeight();
return scrollBounds.top < bottom && scrollBounds.bottom >top;
}
And call it this way boolean isSearchViewVisible = isSearchViewVisible(nestedScrollView,mainSearchView);
If you want to achieve an animation like in the gif and actually even better, you can use the new MotionLayout
the gist of it is that you create your two states and swipe action in a motion scene
. and MotionLayout
will take care of everything.
Your layout file (This is only for the header. You can basically define your start state in the layout file)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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"
app:layoutDescription="@xml/sample_collapsing_animation_scene">
<View
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/md_green_300" />
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/md_white_1000"
android:elevation="4dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="36dp"
android:layout_marginEnd="36dp"
android:layout_marginTop="40dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/your_scrolling_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Your motion scene (Where you define your states and swipe triggers)
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start">
<OnSwipe
motion:dragDirection="dragUp"
motion:touchAnchorId="@id/your_scrolling_content"
motion:touchAnchorSide="top" />
<OnSwipe />
</Transition>
<ConstraintSet android:id="@+id/start">
/** Very simple animation no need for start state*/
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="24dp"
android:elevation="4dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="56dp" />
</ConstraintSet>
</MotionScene>
That's all you need, but in a real app, things can be a bit different.
Here is the link for MotionLayout
Here is a great reference on creating a CollapsingToolbar
feel using motion layout.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With