Search code examples
javaandroidwebviewtoolbarandroid-coordinatorlayout

How to implement hiding the toolbar when scrolling WebView?


I have an Activity with WebView and my own AppBarLayout. I want to implement hiding/showing the AppBarLayout with animation when scrolling the WebView, like in the Google Chrome app.

I tried different ways.

  • ObservableWebView, but it doesn't work correctly, it doesn't always show AppBar, especially if the page is short.

  • ConstraintLayout with animations. Something similar, but still not the same as in Google Chrome.

  • Coordinator layout. This is the best variant, I think. But it have some bugs.

I have wrote the code of layout like this:

<?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/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <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.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@android:color/white"
            app:layout_scrollFlags="scroll|enterAlways">

            <View
                android:id="@+id/linkFrame"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/colorPrimary"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:visibility="visible" />

            <HorizontalScrollView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                android:background="@drawable/frame_link_browser"
                android:padding="8dp"
                app:layout_constraintBottom_toBottomOf="@+id/linkFrame"
                app:layout_constraintEnd_toStartOf="@+id/pauseButton"
                app:layout_constraintStart_toEndOf="@+id/linkDivider"
                app:layout_constraintTop_toTopOf="@+id/linkFrame">

                <TextView
                    android:id="@+id/linkTextView"
                    style="@style/BrowserSmallSecondaryText"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:maxLines="1"
                    android:textColor="@color/textPrimary"
                    android:textIsSelectable="true"
                    tools:text="https://vk.com/id118573338"
                    tools:visibility="visible" />
            </HorizontalScrollView>


            <View
                android:id="@+id/linkDivider"
                style="@style/VerticalDivider"
                android:layout_height="24dp"
                android:layout_centerVertical="true"
                android:layout_marginStart="8dp"
                android:layout_marginLeft="8dp"
                android:background="#90FFFFFF"
                android:textColor="@color/textPrimary"
                app:layout_constraintBottom_toBottomOf="@+id/timerTextView"
                app:layout_constraintStart_toEndOf="@+id/timerTextView"
                app:layout_constraintTop_toTopOf="@+id/timerTextView"
                tools:visibility="visible" />

            <TextView
                android:id="@+id/timerTextView"
                style="@style/BrowserMiddlePrimaryText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginStart="8dp"
                android:layout_marginLeft="8dp"
                android:gravity="center_vertical"
                app:layout_constraintBottom_toBottomOf="@+id/linkFrame"
                app:layout_constraintStart_toStartOf="@+id/linkFrame"
                app:layout_constraintTop_toTopOf="@+id/linkFrame"
                tools:text="30" />

            <TextView
                android:id="@+id/noInternetTextView"
                style="@style/BrowserBigPrimaryText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginLeft="16dp"
                android:text="@string/no_internet_connection"
                android:visibility="gone"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/noInternetAdditionalTextView"
                style="@style/BrowserSmallSecondaryText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/no_internet_connection_additional"
                android:visibility="gone"
                app:layout_constraintLeft_toLeftOf="@+id/noInternetTextView"
                app:layout_constraintTop_toBottomOf="@+id/noInternetTextView" />

            <ImageButton
                android:id="@+id/pauseButton"
                android:layout_width="44dp"
                android:layout_height="44dp"
                android:background="@color/transparent"
                android:text="@string/pause"
                app:layout_constraintBottom_toBottomOf="@+id/linkFrame"
                app:layout_constraintEnd_toStartOf="@+id/settingsButton"
                app:layout_constraintTop_toTopOf="@+id/linkFrame"
                app:srcCompat="@drawable/ic_pause_circle_filled_white_36dp"
                tools:visibility="visible" />

            <ImageButton
                android:id="@+id/settingsButton"
                android:layout_width="44dp"
                android:layout_height="44dp"
                android:background="@color/transparent"
                android:text="@string/settings"
                app:layout_constraintBottom_toBottomOf="@+id/linkFrame"
                app:layout_constraintEnd_toEndOf="@+id/linkFrame"
                app:layout_constraintTop_toTopOf="@+id/linkFrame"
                app:srcCompat="@drawable/ic_settings_white_36dp"
                tools:visibility="visible" />

        </android.support.constraint.ConstraintLayout>

    </android.support.design.widget.AppBarLayout>


    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">


        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white"
                android:orientation="vertical">

                <ProgressBar
                    android:id="@+id/progressBar"
                    style="?android:attr/progressBarStyleHorizontal"
                    android:layout_width="match_parent"
                    android:layout_height="4dp"
                    android:indeterminate="false"
                    android:max="100"
                    android:progress="0"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent" />

                <com.github.ksoichiro.android.observablescrollview.ObservableWebView xmlns:android="http://schemas.android.com/apk/res/android"
                    android:id="@+id/webView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />

            </LinearLayout>

        </android.support.v4.widget.NestedScrollView>
    </android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>

In this case the page is not scrolling. I have tried many variants. If in the behavior I put clean webview (without swiperefresh, nestedscrolling etc) it's work correctly, but if I lock and unlock the screen - WebView swells up and on the bottom of page appears white empty place. I have been trying to solve this problem for several days now, but it doesn’t work. Please help make a stable solution to this problem. Thank you very much.

I tried this method: How to Hide ActionBar/Toolbar While Scrolling Down in Webview but if I lock and unlock the screen - WebView swells up and on the bottom of page appears white empty place


Solution

  • I created WebView with this features and it works perfect:

    import android.content.Context;
    import android.support.v4.view.MotionEventCompat;
    import android.support.v4.view.NestedScrollingChild;
    import android.support.v4.view.NestedScrollingChildHelper;
    import android.support.v4.view.ViewCompat;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.ViewGroup;
    import android.webkit.WebView;
    
    public class NestedWebView extends WebView implements NestedScrollingChild, Scrollable {
        private int mLastY;
        private final int[] mScrollOffset = new int[2];
        private final int[] mScrollConsumed = new int[2];
        private int mNestedOffsetY;
        private NestedScrollingChildHelper mChildHelper;
    
        private int mScrollY;
        private ScrollViewCallback callback;
        private boolean firstScroll = true;
    
    
        public NestedWebView(Context context) {
            this(context, null);
        }
    
        public NestedWebView(Context context, AttributeSet attrs) {
            this(context, attrs, android.R.attr.webViewStyle);
        }
    
        public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mChildHelper = new NestedScrollingChildHelper(this);
            setNestedScrollingEnabled(true);
        }
    
        public void setOnScrollListener(ScrollViewCallback callback) {
            this.callback = callback;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            boolean returnValue = false;
    
            MotionEvent event = MotionEvent.obtain(ev);
            final int action = MotionEventCompat.getActionMasked(event);
            if (action == MotionEvent.ACTION_DOWN) {
                mNestedOffsetY = 0;
            }
            int eventY = (int) event.getY();
            event.offsetLocation(0, mNestedOffsetY);
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    int deltaY = mLastY - eventY;
                    // NestedPreScroll
                    if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
                        deltaY -= mScrollConsumed[1];
                        mLastY = eventY - mScrollOffset[1];
                        event.offsetLocation(0, -mScrollOffset[1]);
                        mNestedOffsetY += mScrollOffset[1];
                    }
                    returnValue = super.onTouchEvent(event);
    
                    // NestedScroll
                    if (dispatchNestedScroll(0, mScrollOffset[1], 0, deltaY, mScrollOffset)) {
                        event.offsetLocation(0, mScrollOffset[1]);
                        mNestedOffsetY += mScrollOffset[1];
                        mLastY -= mScrollOffset[1];
                    }
                    break;
                case MotionEvent.ACTION_DOWN:
                    returnValue = super.onTouchEvent(event);
                    if (firstScroll) {
                        // dispatching first down scrolling properly by making sure that first deltaY will be -ve
                        mLastY = eventY - 5;
                        firstScroll = false;
                    } else {
                        mLastY = eventY;
                    }
                    // start NestedScroll
                    startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                    break;
                default:
                    returnValue = super.onTouchEvent(event);
                    break;
            }
            return returnValue;
        }
    
        // Nested Scroll implements
        @Override
        public void setNestedScrollingEnabled(boolean enabled) {
            mChildHelper.setNestedScrollingEnabled(enabled);
        }
    
        @Override
        public boolean isNestedScrollingEnabled() {
            return mChildHelper.isNestedScrollingEnabled();
        }
    
        @Override
        public boolean startNestedScroll(int axes) {
            return mChildHelper.startNestedScroll(axes);
        }
    
        @Override
        public void stopNestedScroll() {
            mChildHelper.stopNestedScroll();
        }
    
        @Override
        public boolean hasNestedScrollingParent() {
            return mChildHelper.hasNestedScrollingParent();
        }
    
        @Override
        public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                                            int[] offsetInWindow) {
            return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
        }
    
        @Override
        public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
            return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        }
    
        @Override
        public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
            return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
        }
    
        @Override
        public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
            return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
        }
    
    
        @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            super.onScrollChanged(l, t, oldl, oldt);
            mScrollY = t;
            dispatchOnScrollChanged(mScrollY);
        }
    
        private void dispatchOnScrollChanged(int scrollY) {
            if (callback != null) {
                callback.onScrollChanged(scrollY);
            }
        }
    
        // Scrollable implements
        @Override
        public void setScrollViewCallbacks(ObservableScrollViewCallbacks listener) {
    
        }
    
        @Override
        public void scrollVerticallyTo(int y) {
    
        }
    
        @Override
        public int getCurrentScrollY() {
            return mScrollY;
        }
    
        @Override
        public void setTouchInterceptionViewGroup(ViewGroup viewGroup) {
    
        }
    
        public interface ScrollViewCallback {
            void onScrollChanged(int scrollY);
        }
    
    
    }