Subfly
Subfly

Reputation: 554

webView inside of the bottom sheet of the BottomSheetScaffold is not scrollable

I have list of groceries in the scaffold and when one of the items are selected, I have to open a modal sheet containing a webView. However, whatever I do, I can not prevent modal sheet to consume all the gestures itself.

What I meant is, I can not scroll inside the webview in the modal sheet even though I disable the gestures of the modal sheet. Any possible way to implement this?

I have already tried nested scroll connections but couldn't be able to proceed as I am still trying to understand the concept. Any help is appreciated.

Upvotes: 1

Views: 3957

Answers (3)

Mete
Mete

Reputation: 2884

This solution work on me

Custom webView:

class ObservableWebView : WebView {

    var onScrollChangedCallback: OnScrollChangeListener? = null

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)

    override fun onScrollChanged(currentHorizontalScroll: Int, currentVerticalScroll: Int, oldHorizontalScroll: Int, oldcurrentVerticalScroll: Int) {
        super.onScrollChanged(currentHorizontalScroll, currentVerticalScroll, oldHorizontalScroll, oldcurrentVerticalScroll)

        onScrollChangedCallback?.onScrollChanged(
            currentHorizontalScroll, 
            currentVerticalScroll, 
            oldHorizontalScroll, 
            oldcurrentVerticalScroll
         )
    }

    interface OnScrollChangeListener {
        fun onScrollChanged(
            currentHorizontalScroll: Int, 
            currentVerticalScroll: Int, 
            oldHorizontalScroll: Int, 
            oldcurrentVerticalScroll: Int
         )
     }
}

Bottomsheet:

class BottomSheetWebView(context: Context) : FrameLayout(context) {

    private val mBottomSheetDialog: BottomSheetDialog = BottomSheetDialog(context)
    private var mCurrentWebViewScrollY = 0

    init {
        inflateLayout(context)
        setupBottomSheetBehaviour()
        setupWebView()
    }

    private fun inflateLayout(context: Context) {
        inflate(context, R.layout.bottom_sheet_webview, this)

        mBottomSheetDialog.setContentView(this)

        mBottomSheetDialog.window?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
            ?.setBackgroundResource(android.R.color.transparent);
    }

    private fun setupBottomSheetBehaviour() {
        (parent as? View)?.let { view ->
            BottomSheetBehavior.from(view).let { behaviour ->
                behaviour.addBottomSheetCallback(object :
                    BottomSheetBehavior.BottomSheetCallback() {
                    override fun onSlide(bottomSheet: View, slideOffset: Float) {

                    }

                    override fun onStateChanged(bottomSheet: View, newState: Int) {
                        if (newState == BottomSheetBehavior.STATE_DRAGGING && mCurrentWebViewScrollY > 0) {
                            // this is where we check if webview can scroll up or not and based on that we let BottomSheet close on scroll down
                            behaviour.setState(BottomSheetBehavior.STATE_EXPANDED);
                        } else if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                            close()
                        }
                    }
                })
            }
        }
    }

    private fun setupWebView() {
        webView.onScrollChangedCallback = object : ObservableWebView.OnScrollChangeListener {
            override fun onScrollChanged(currentHorizontalScroll: Int, currentVerticalScroll: Int,
                                             oldHorizontalScroll: Int, oldcurrentVerticalScroll: Int) {
                mCurrentWebViewScrollY = currentVerticalScroll
            }
        }
    }

    fun showWithUrl(url: String) {
        webView.loadUrl(url)
        mBottomSheetDialog.show()
    }

    fun close() {
        mBottomSheetDialog.dismiss()
    }
}

You can find detailed explanation in here: https://medium.com/@nishantpardamwar/using-webview-with-bottomsheetdialog-f38e45cc95a5

Upvotes: 1

Subfly
Subfly

Reputation: 554

I finally did it. First I have copied the solution for nested scroll in webView in accompanist page. Even-though the solution did not merged, it has a crucial point to be addded.

I have copied the solution of bentrengrove to my code below the link https://github.com/google/accompanist/issues/1260 with fix https://github.com/google/accompanist/pull/1270 and after that modified the base WebView code such as:

var webView by remember { mutableStateOf<WebView?>(null) }
val scrollState = rememberScrollState() --> ADDED THIS LINE
.
.
.
AndroidView(
        factory = { context ->
            val web = WebView(context).apply {
                onCreated(this)

                layoutParams = LayoutParams(
                    LayoutParams.MATCH_PARENT,
                    LayoutParams.WRAP_CONTENT
                )

                webChromeClient = chromeClient
                webViewClient = client

                webView = this
            }

            // In order to dispatch nested scrolling correctly, WebView needs to
            // be wrapped in a NestedScrollView. This is important for things like
            // SwipeToRefresh and TopAppBar behaviours.
            NestedScrollView(context).apply {
                addView(web)
            }
        },
        modifier = modifier
            .clipToBounds()
            .verticalScroll(scrollState) --> ADDED THIS LINE

And now, the scroll in webView both collapses the topBar and scrolls inside of the modal sheet. I can improve the answer if anyone needs.

EDIT: I also forgot to mention that I have changed BottomSheetScaffold to BackdropScaffold.

Upvotes: 2

Mr. Mad
Mr. Mad

Reputation: 1238

you can try to create the bottom sheet as bottomsheetdialogfragment here is the working example ,you can set the state for the bottom sheet as collpsed, expended, half expended according to your requirement.

public class WebViewDialog extends BottomSheetDialogFragment implements View.OnClickListener {

public UserSessions mUserSessions;
BottomSheetBehavior bottomSheetBehavior;
WebviewLayoutBinding binding;
String title;
String url;

public WebViewDialog(String url, String title) {
    this.title = title;
    this.url = url;
}

public WebViewDialog() {

}

BottomSheetDialog bottomSheet;
View mView = null;

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    bottomSheet = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
    View view = View.inflate(getActivity(), R.layout.webview_layout, null);
    mView = view;
    binding = DataBindingUtil.bind(view);
    bottomSheet.setContentView(view);
    bottomSheet.setCancelable(false);
    bottomSheet.setCanceledOnTouchOutside(false);
    bottomSheetBehavior = BottomSheetBehavior.from((View) (view.getParent()));
    bottomSheetBehavior.setPeekHeight(BottomSheetBehavior.PEEK_HEIGHT_AUTO);
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View view, int i) {
            if (BottomSheetBehavior.STATE_EXPANDED == i) {

            }
            if (BottomSheetBehavior.STATE_COLLAPSED == i) {
            }

            if (BottomSheetBehavior.STATE_HIDDEN == i) {
                dismiss();
            }
        }

        @Override
        public void onSlide(@NonNull View view, float v) {

        }
    });
    mUserSessions = new UserSessions(getActivity());
    binding.imgCLose.setOnClickListener(this);
    if (url != null && !url.isEmpty()) {
        BBProgress.showProgressDialog(getActivity());
        binding.mWebView.setWebViewClient(new MyBrowserClient());
        binding.mWebView.getSettings().setJavaScriptEnabled(true);
        binding.mWebView.loadUrl(url);
    }
    return bottomSheet;
}

@Override
public void onDismiss(@NonNull DialogInterface dialog) {
    super.onDismiss(dialog);

}

@Override
public void onStart() {
    super.onStart();
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}

int mStep = 0;

public void onBackClick() {
    dismiss();
}

@Override
public void onClick(View v) {
    // TODO Auto-generated method stub
    if (v.getId() == R.id.imgCLose) {
        onBackClick();
    }
}


public boolean lg;

public class MyBrowserClient extends WebViewClient {
    public MyBrowserClient() {
    }

    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        NetworkInfo nf = ((ConnectivityManager) getActivity().getSystemService(CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
        if (nf == null || !nf.isConnected()) {
            CommonMethods.errorToast(getActivity(), getActivity().getString(R.string.error_internet));
            return true;
        } else if (url.startsWith("http:") || url.startsWith("https:")) {
            view.getSettings().setBuiltInZoomControls(false);
            view.loadUrl(url);
            return true;
        } else {
            view.getSettings().setBuiltInZoomControls(false);
            view.loadData(url, "text/html", "UTF-8");
            return true;
        }
    }

    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        if (!lg) {
            boolean unused = lg = true;
            return;
        }
    }

    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        BBProgress.hideProgressDialog(getActivity());

    }
}

}

And thew xml file for the layout is webview_layout.xml

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:orientation="vertical">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorAccent">

        <RelativeLayout
            android:id="@+id/rlTopHeader"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            android:background="@color/white"
            android:padding="@dimen/_10dp">

            <ImageView
                android:id="@+id/imgCLose"
                android:layout_width="@dimen/_44dp"
                android:layout_height="@dimen/_44dp"
                android:layout_centerVertical="true"
                android:background="?attr/selectableItemBackgroundBorderless"
                android:padding="@dimen/_5dp"
                android:src="@drawable/ic_back"
                app:tint="@color/black" />

            <TextView
                android:id="@+id/txtTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginStart="@dimen/_10dp"
                android:layout_toEndOf="@+id/imgCLose"
                android:text="@string/app_name_inside"
                android:textColor="@color/black"
                android:textSize="@dimen/_18dp" />

        </RelativeLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/appBarLayout"
        android:background="@color/white"
        android:minHeight="@dimen/_250dp"
        android:orientation="vertical">

        <WebView
            android:id="@+id/mWebView"
            android:nestedScrollingEnabled="true"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"></WebView>
    </LinearLayout>
</RelativeLayout>

I hope this example will help and solve your problem as per I am using the same and working fine for me.

Upvotes: 1

Related Questions