27

I implemented BottomSheet using the DialogFragment approach. I have a TabLayout and ViewPager in the BottomSheet. The ViewPager is hosting 2 pages, each inflates a RecyclerView. The first(Coffee tab) RecyclerView scrolls fine. The problem I'm having now is that for the 2nd(Milk tab) the scroll is not working. Any idea how can I fix this? Thanks!

You can test out with the demo project I created here: https://github.com/choongyouqi/bottomsheet`

enter image description here

You Qi
  • 8,353
  • 8
  • 50
  • 68
  • Im betting its not inflating correctly. Do you have it set for vertical? – MNM Sep 07 '16 at 08:51
  • Check my answer below no any extra library used not any bugs is their as stated in other answers just check my `Static Fragment` code and apply that to yours. – Jay Rathod Sep 09 '16 at 06:01
  • Possible duplicate of [Android ViewPager with RecyclerView works incorrectly inside BottomSheet](http://stackoverflow.com/questions/37715822/android-viewpager-with-recyclerview-works-incorrectly-inside-bottomsheet) – Vitaly Feb 18 '17 at 06:33

7 Answers7

14

use this view as root view:

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

public class DisallowInterceptView extends LinearLayout {
    public DisallowInterceptView(Context context) {
        super(context);
        requestDisallowInterceptTouchEvent(true);
    }

    public DisallowInterceptView(Context context, AttributeSet attrs) {
        super(context, attrs);
        requestDisallowInterceptTouchEvent(true);
    }

    public DisallowInterceptView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        requestDisallowInterceptTouchEvent(true);
    }


    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.onTouchEvent(event);
    }

}

then in your layout that used for bottmSheet:

<?xml version="1.0" encoding="utf-8"?>
<com.your.package.DisallowInterceptView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:maxHeight="400dp"
    android:minHeight="300dp"
    android:orientation="vertical"
    >

    ...
</LinearLayout>

</com.your.package.DisallowInterceptView>
Hadi Ahmadi
  • 1,924
  • 2
  • 17
  • 38
13

As mentioned by R. Zagórski, I described the reason for this scrolling behavior here, i.e., BottomSheetBehavior only supports one scrolling child. However this answer wasn't focusing on Bottom Sheet Dialogs.

Therefore – just like R. Zagórski – I extended my own library that overcomes this limitation. Starting with 0.0.3 there is support for Bottom Sheet Dialogs! You can find the library and the example app here: https://github.com/laenger/ViewPagerBottomSheet

To use in your project, simply add the maven repo url to your build.gradle:

repositories {
    maven { url "https://raw.github.com/laenger/maven-releases/master/releases" }
}

Add the library to the dependencies:

dependencies {
    compile "biz.laenger.android:vpbs:0.0.3"
}

Use ViewPagerBottomSheetDialogFragment as super class for Dialog Fragments. Then setup any ViewPager inside the content view:

public class DialogFragment extends ViewPagerBottomSheetDialogFragment {
    @Override
    public void setupDialog(Dialog dialog, int style) {
        super.setupDialog(dialog, style);
        final View contentView = View.inflate(getContext(), R.layout.dialog_bottom_sheet, null);

        final ViewPager viewPager = (ViewPager) contentView.findViewById(R.id.viewpager);
        // ...
        BottomSheetUtils.setupViewPager(viewPager);

        dialog.setContentView(contentView);
    }
}

sample implementation

Community
  • 1
  • 1
laenger
  • 991
  • 1
  • 10
  • 20
6

When trying to look for the problem on StackOverflow I found this thread. It depicts the bug (at least that is how I look at it), that BottomSheetBehaviour works only for the first scrollable child it finds. It also proposes the usage of different CoordinatorLayout.Behavior proposed and published here.

However, your case is a bit different. BottomSheetDialogFragment is used. And this is where the provided solution does not work. However, I managed to overcome this problem. Published repository, where your project was modified to be working. It uses the ViewPagerBottomSheetBehavior from the library mentioned earlier.

Basically, the following changes were made:

  1. StatisticFragment extends ViewPagerBottomSheetDialogFragment and not BottomSheetDialogFragment
  2. The onCreateDialog function in StatisticsFragment is changed:

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        ViewPagerBottomSheetDialog dialog = (ViewPagerBottomSheetDialog) super.onCreateDialog(savedInstanceState);
        View rootView = View.inflate(getContext(), R.layout.sheet_main, null);
        viewPager = (ViewPager) rootView.findViewById(R.id.viewpager);
        tabLayout = (TabLayout) rootView.findViewById(R.id.tabs);
        dialog.setContentView(rootView);
        mBehavior = ViewPagerBottomSheetBehavior.from((View) rootView.getParent());
        mBehavior.setPeekHeight(400);
        if (viewPager != null && tabLayout != null) {
            initViewPager();
        }
        return dialog;
    }
    
  3. The following function is invoked on the ViewPager:

    BottomSheetUtils.setupViewPager(viewPager);
    

And that is all. The project works.

The following is done behind the scenes:

BottomSheetDialogFragment has only one method:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new BottomSheetDialog(getContext(), getTheme());
}

There BottomSheetDialog is returned. However, it has statically defined behaviour set to BottomSheetBehavior. What was needed was to override ViewPagerBottomSheetDialogFragment to return ViewPagerBottomSheetDialog where it's CoordinatorLayout.Behavior is set to ViewPagerBottomSheetBehavior. Also, the custom BottomSheet was needed to be overriden to accustom to ViewPagerBottomSheetBehavior.

Daniel
  • 2,415
  • 3
  • 24
  • 34
R. Zagórski
  • 20,020
  • 5
  • 65
  • 90
  • 1
    it doesnt work for me :(. I only can scroll the items of first viewpager item .Its the same problem with this library too – Ajay Shrestha Jan 31 '17 at 23:48
  • Can you help me to implement this in persistent bottom sheet rather than modal bottom sheet, i tried using the vbps library but it got some issues in it for which i have already raised a issue on the git repo. – saurabh dhillon Mar 22 '18 at 12:18
2

I had the same issue, to fix this without the need to override BottomSheetBehavior or the need of an additional library you can to the following: Implement a callback inside your bottom sheet implementation that registers changes of the page.

fun onPageChanged(currentPage: Int) {
   recycler1.isNestedScrollingEnabled = currentPage == 0
   recycler2.isNestedScrollingEnabled = currentPage == 1
   dialog?.findViewById<FrameLayout>(R.id.design_bottom_sheet)?.requestLayout()
}

In the BottomSheetBehavior implementation in onLayoutChild a lookup for the first child that supports nested scrolling is performed, with this change the lookup is repeated. Not optimal solution but works fine in my case

luktant
  • 121
  • 1
  • 6
0

you can use 2 RecyclerView in CoordinatorLayout.

<android.support.design.widget.CoordinatorLayout
         android:id="@+id/mainBottomSheet"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:background="@color/white">

         <android.support.v7.widget.RecyclerView
                  android:id="@+id/recyclerViewRight"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent" />

         <android.support.v7.widget.RecyclerView
                  android:id="@+id/recyclerViewLeft"
                  android:layout_width="200dp"
                  android:layout_height="match_parent" />

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

check this post link

Rasoul Miri
  • 11,234
  • 1
  • 68
  • 78
0

A better solution for this issue is to add the following lines to proguard rules:

-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
-keep class com.google.android.material.bottomsheet.BottomSheetBehavior { *** findScrollingChild(...); }

More details about this can be found on this link: https://github.com/kafumi/android-bottomsheet-viewpager#proguardr8

Nik
  • 192
  • 2
  • 8
-1

You not need to extends StatisticFragment as ViewPagerBottomSheetDialogFragment or no need to use any Library for that.

It's you code i have just made some changes in your Static Fragment related to View Pager.

Here is the Statistic Fragment in which i have made changes.

There is not any bugs as stated in all above Answers.

Replace this code with your old Static fragment only not any other changes it will give you the Desired output.

I have just made changes around with your View Pager only and made it working as you want. used OnPageChangeListener method just get that view.

Statistic Fragment.java

    public class StatisticFragment extends BottomSheetDialogFragment {

        private BottomSheetBehavior mBehavior;
        private TabLayout tabLayout;
        private ViewPager viewPager;

        @NonNull
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
            View rootView = View.inflate(getContext(), R.layout.sheet_main, null);

            viewPager = (ViewPager) rootView.findViewById(R.id.viewpager);
            tabLayout = (TabLayout) rootView.findViewById(R.id.tabs);
            if (viewPager != null && tabLayout != null) {
                initViewPager();
            }

            final ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() {

                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

                }

                @Override
                public void onPageSelected(int arg0) {
                    // TODO Auto-generated method stub
                    View view = viewPager.findViewWithTag(arg0);
                    if (view == null) {
                        return;
                    }
                    CustomPagerAdapter adapter = new CustomPagerAdapter(getContext());
                    viewPager.setAdapter(adapter);
                    viewPager.setOffscreenPageLimit(10);
                    tabLayout.setupWithViewPager(viewPager);
                }

                @Override
                public void onPageScrollStateChanged(int state) {

                }
            };

            viewPager.addOnPageChangeListener(pageChangeListener);
            viewPager.post(new Runnable() {
                @Override
                public void run() {
                    pageChangeListener.onPageSelected(viewPager.getCurrentItem());
                }
            });


            dialog.setContentView(rootView);
            mBehavior = BottomSheetBehavior.from((View) rootView.getParent());
            return dialog;


        }

        private void initViewPager() {
            CustomPagerAdapter adapter = new CustomPagerAdapter(getContext());
            viewPager.setAdapter(adapter);
            viewPager.setOffscreenPageLimit(10);
            tabLayout.setupWithViewPager(viewPager);

        }

        @Override
        public void onStart() {
            super.onStart();
            //mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            //mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }

        public class ServiceVideHolder extends RecyclerView.ViewHolder {
            protected ViewGroup mItemView;
            protected TextView mNameView;
            protected TextView mCodeView;

            public ServiceVideHolder(View v) {
                super(v);
                //rootView = v;
                mItemView = (ViewGroup) v.findViewById(R.id.item);
                mNameView = (TextView) v.findViewById(R.id.main_text);
                mCodeView = (TextView) v.findViewById(R.id.sub_text);
            }
        }

        public class ItemViewHolder extends RecyclerView.ViewHolder {
            protected TextView mMainText;
            protected TextView mSubText;

            public ItemViewHolder(View v) {
                super(v);
                mMainText = (TextView) v.findViewById(R.id.main_text);
                mSubText = (TextView) v.findViewById(R.id.sub_text);
            }
        }

        public class ItemAdapter extends RecyclerView.Adapter<ItemViewHolder> {
            private List<Item> items;

            public ItemAdapter(List<Item> items) {
                this.items = items;
            }

            @Override
            public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
                View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
                return new ItemViewHolder(view);
            }

            @Override
            public void onBindViewHolder(final ItemViewHolder viewHolder, final int position) {
                final Item item = items.get(position);
                viewHolder.mMainText.setText(item.name);
                viewHolder.mSubText.setText(item.value);
                viewHolder.mMainText.setTextColor(ResourcesCompat.getColor(getResources(), position % 2 == 0 ? R.color.md_red_500 : R.color.md_blue_500, null));
            }

            @Override
            public int getItemCount() {
                return items.size();
            }
        }

        class ViewPagerAdapter extends FragmentPagerAdapter {
            private final List<Fragment> mFragmentList = new ArrayList<>();
            private final List<String> mFragmentTitleList = new ArrayList<>();

            public ViewPagerAdapter(FragmentManager manager) {
                super(manager);
            }

            @Override
            public Fragment getItem(int position) {
                return mFragmentList.get(position);
            }

            @Override
            public int getCount() {
                return mFragmentList.size();
            }

            public void addFrag(Fragment fragment, String title) {
                mFragmentList.add(fragment);
                mFragmentTitleList.add(title);
            }

            @Override
            public CharSequence getPageTitle(int position) {
                return mFragmentTitleList.get(position);
            }
        }

        public class CustomPagerAdapter extends PagerAdapter {

            private Context mContext;

            public CustomPagerAdapter(Context context) {
                mContext = context;
            }

            @Override
            public Object instantiateItem(ViewGroup collection, int position) {
                //CustomPagerEnum customPagerEnum = CustomPagerEnum.values()[position];
                LayoutInflater inflater = LayoutInflater.from(mContext);
                ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.adapter, collection, false);
                rootView.setTag(position);


                Toast.makeText(mContext, "Inside Instanciate Item", Toast.LENGTH_SHORT).show();

                //View rootView = View.inflate(getContext(), R.layout.adapter, null);
                final RecyclerView mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
                ArrayList<Item> items = new ArrayList<>();

                if (position == 0) {
                    items.add(new Item("Coffee 1", "1"));
                    items.add(new Item("Coffee 2", "2"));
                    items.add(new Item("Coffee 3", "3"));
                    items.add(new Item("Coffee 4", "4"));
                    items.add(new Item("Coffee 5", "5"));
                    items.add(new Item("Coffee 6", "6"));
                    items.add(new Item("Coffee 7", "7"));
                    items.add(new Item("Coffee 8", "8"));
                    items.add(new Item("Coffee 9", "9"));
                    items.add(new Item("Coffee 10", "10"));
                } else {
                    items.add(new Item("Milk 1", "1"));
                    items.add(new Item("Milk 2", "2"));
                    items.add(new Item("Milk 3", "3"));
                    items.add(new Item("Milk 4", "4"));
                    items.add(new Item("Milk 5", "5"));
                    items.add(new Item("Milk 6", "6"));
                    items.add(new Item("Milk 7", "7"));
                    items.add(new Item("Milk 8", "8"));
                    items.add(new Item("Milk 9", "9"));
                    items.add(new Item("Milk 10", "10"));
                }

                final ItemAdapter mAdapter = new ItemAdapter(items);

                mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
                mRecyclerView.setNestedScrollingEnabled(false);
                mRecyclerView.setAdapter(mAdapter);
                Paint paint = new Paint();
                paint.setStrokeWidth(1);
                paint.setColor(ResourcesCompat.getColor(getResources(), R.color.md_grey_500, null));
                paint.setAntiAlias(true);
                paint.setPathEffect(new DashPathEffect(new float[]{25.0f, 25.0f}, 0));
                mRecyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).showLastDivider().paint(paint).build()); //.marginResId(R.dimen.leftmargin, R.dimen.rightmargin)

                collection.addView(rootView);


                return rootView;
            }

            @Override
            public void destroyItem(ViewGroup collection, int position, Object view)    {
                collection.removeView((View) view);
            }

            @Override
            public int getCount() {
                return 2;
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }

            @Override
            public CharSequence getPageTitle(int position) {
                //CustomPagerEnum customPagerEnum = CustomPagerEnum.values()[position];
                return position == 0 ? "Coffee" : "Milk";
            }

        }
    }

It's Done.

enter image description here

Jay Rathod
  • 11,131
  • 6
  • 34
  • 58
  • 1
    it will lose the scroll state from previous page because you are just recreating the adapter and rebinding to tablayout every time a user swipe left/right. not only it's dirty and expensive, it cause flickering while the page is relayouting. I'm actually looking for a solution which you transfer the `fling` event/calculation to the active page of the viewpager. – You Qi Sep 20 '16 at 03:37