Search code examples
javaandroidandroid-fragmentsandroid-recyclerviewandroid-viewpager

Can the RecyclerView scroll with the rest of the fragment layouts?


I have set up a RecyclerView adapter with ViewPager in an activity namely TvShowEpisodeDetails , it works well but there is one issue, the Layout of RecyclerView is fixed while scrolling up and down in the Fragment(TvShowEpisodeDetailsFragment). But I want it to scroll with them.

The RecyclerView and ViewPager are both set inside viewpager_with_toolbar_overlay.xml Layout , and both has been settup in The Activity.

TvShowEpisodeDetailsFragment is the fragment class which belongs to activity class TvShowEpisodeDetails , the fragment creates as many episodes as a TV Show season can offer.

enter image description here

And off course this issue will be gone if I set RecyclerView adapter inside fragment, but I will get non-fixable highlighting and scrolling issues , that is why I set it inside the activity because it does not give those issues.

I need to make it work somehow inside the activity.

My goal is that RecyclerView and ViewPager has to be in the same layout XML file and they both must either be in the activity or fragment class

Is it possible to make the RecyclerView scroll with rest of the fragment layouts?

or

Is it possible to do it programmatically?

Here is the activity

public class TvShowEpisodeDetails extends MizActivity{

    
    @Override
    protected int getLayoutResource() {
        return R.layout.viewpager_with_toolbar_overlay;
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        mBus = MizuuApplication.getBus();
        super.onCreate(savedInstanceState);

        // Set theme
        setTheme(R.style.Mizuu_Theme_NoBackground);

       


        // setting episodeslist
        final ArrayList<PlanetModel> episodeslist = new ArrayList<>();
        for(TvShowEpisode e : mEpisodes){
            episodeslist.add(new PlanetModel(e.mEpisode));
        }


        // setting RecyclerView
        mEpisodesList = (RecyclerView) findViewById(R.id.episodesLIST);


        // Setting LinearLayoutManager
        LinearLayoutManager layoutManager
                = new LinearLayoutManager(this.getApplicationContext(), LinearLayoutManager.HORIZONTAL, false);
        //mEpisodesList.setLayoutManager(new LinearLayoutManager(mContext));
        mEpisodesList.setLayoutManager(layoutManager);


        // Setting RecyclerView Adapter
        PlanetAdapter.OnItemClickListener indicatorCallback = new PlanetAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(String item) {
                SharedPreferences getPref = getContext().getSharedPreferences("PlanetAdapter", Context.MODE_PRIVATE);
                int pos = getPref.getInt("newPosition", 0);
                mViewPager.setCurrentItem(pos,false);
            }
        };
        final PlanetAdapter planetAdapter = new PlanetAdapter(episodeslist,indicatorCallback);
        mEpisodesList.setAdapter(planetAdapter);


        // Setting ViewPager
        mViewPager = (ViewPager) findViewById(R.id.awesomepager);
        mViewPager.setAdapter(new TvShowEpisodeDetailsAdapter(getSupportFragmentManager()));
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                planetAdapter.setSelectedIndex(position);
                planetAdapter.notifyDataSetChanged();
                mEpisodesList.smoothScrollToPosition(position);
                //mEpisodesList.scrollToPosition(position);
                for (int i=0; i<episodeslist.size(); i++)
                {
                    episodeslist.get(i).setPlanetSelected(false);
                }
                episodeslist.get(position).setPlanetSelected(true);
                ViewUtils.updateToolbarBackground(TvShowEpisodeDetails.this, mToolbar, 0, mEpisodes.get(position).getTitle(), Color.TRANSPARENT);
            }
        });

        if (savedInstanceState != null) {
            mViewPager.setCurrentItem(savedInstanceState.getInt("tab", 0));
        } else {
            for (int i = 0; i < mEpisodes.size(); i++) {
                if (mEpisodes.get(i).getSeason().equals(MizLib.addIndexZero(mSeason)) && mEpisodes.get(i).getEpisode().equals(MizLib.addIndexZero(mEpisode))) {
                    mViewPager.setCurrentItem(i);
                    break;
                }
            }
        }
    }
}

viewpager_with_toolbar_overlay

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.view.ViewPager
        android:id="@+id/awesomepager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ProgressBar
        android:id="@+id/progressbar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#068006"
        android:layout_marginTop="450dp"
        >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/episodesLIST"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:scrollbars="horizontal" />
    </LinearLayout>

    <include layout="@layout/toolbar_layout" />

</FrameLayout>

Here is the XML layout of the fragment which is inflated in onCreateView of the fragment class

episode_details.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:background="@color/abc_input_method_navigation_guard">

    <com.miz.views.ObservableScrollView
        android:id="@+id/observableScrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <RelativeLayout
                android:id="@+id/relativeLayout1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <ImageView
                    android:id="@+id/episodePhoto"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/backdrop_portrait_height"
                    android:scaleType="centerCrop"
                    android:src="@drawable/bg" />

                <com.melnykov.fab.FloatingActionButton
                    android:id="@+id/fab"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/episodePhoto"
                    android:layout_alignParentEnd="false"
                    android:layout_alignParentRight="true"
                    android:layout_marginTop="@dimen/content_details_fab_negative_margin"
                    android:layout_marginRight="@dimen/content_details_baseline_margin"
                    android:src="@drawable/ic_play_arrow_white_36dp"
                    app:fab_colorNormal="#666"
                    app:fab_type="mini" />

                <LinearLayout
                    android:id="@+id/linearLayout"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/fab"
                    android:layout_marginLeft="@dimen/content_details_baseline_margin"
                    android:layout_marginTop="@dimen/content_details_title_margin_top"
                    android:layout_marginRight="@dimen/content_details_baseline_margin"
                    android:layout_toLeftOf="@+id/fab"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/movieTitle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:ellipsize="end"
                        android:maxLines="3"
                        android:textAppearance="?android:attr/textAppearanceLarge"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/content_details_title" />

                    <TextView
                        android:id="@+id/textView7"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="@dimen/content_details_very_small_margin"
                        android:layout_marginBottom="@dimen/content_details_baseline_margin"
                        android:textAppearance="?android:attr/textAppearanceLarge"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/content_details_subheader"
                        android:textStyle="bold|italic" />
                </LinearLayout>

            </RelativeLayout>

            <LinearLayout
                android:id="@+id/details_area"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#666"
                android:baselineAligned="false"
                android:elevation="1dp"
                android:minHeight="@dimen/content_details_large_margin"
                android:orientation="horizontal"
                android:paddingLeft="@dimen/content_details_baseline_margin"
                android:paddingTop="@dimen/content_details_small_margin"
                android:paddingRight="@dimen/content_details_baseline_margin"
                android:paddingBottom="@dimen/content_details_small_margin">

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_weight="1"
                    android:gravity="center_horizontal"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/TextView03"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:ellipsize="end"
                        android:gravity="center_horizontal"
                        android:lines="1"
                        android:maxLines="1"
                        android:text="@string/detailsAirDate"
                        android:textAppearance="?android:attr/textAppearanceMedium"
                        android:textSize="@dimen/content_details_area_subheader" />

                    <TextView
                        android:id="@+id/textReleaseDate"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:gravity="center_horizontal"
                        android:textAppearance="?android:attr/textAppearanceMedium"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/content_details_area_header"
                        android:textStyle="bold" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_weight="1"
                    android:gravity="center_horizontal"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/textView61"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:ellipsize="end"
                        android:gravity="center_horizontal"
                        android:lines="1"
                        android:maxLines="1"
                        android:text="@string/detailsRating"
                        android:textAppearance="?android:attr/textAppearanceMedium"
                        android:textSize="@dimen/content_details_area_subheader" />

                    <TextView
                        android:id="@+id/textView12"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:gravity="center_horizontal"
                        android:textAppearance="?android:attr/textAppearanceMedium"
                        android:textColor="#FFFFFF"
                        android:textSize="@dimen/content_details_area_header"
                        android:textStyle="bold" />
                </LinearLayout>
            </LinearLayout>



            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:padding="@dimen/content_details_baseline_margin">

                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="@dimen/content_details_baseline_margin"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:textColor="#FFFFFF"
                    android:textSize="@dimen/content_details_body_text" />

                <TextView
                    android:id="@+id/director"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="12dp"
                    android:drawableLeft="@drawable/ic_movie_white_24dp"
                    android:drawablePadding="@dimen/movie_details_padding"
                    android:focusable="false"
                    android:gravity="center_vertical"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:textColor="#f0f0f0"
                    android:textSize="@dimen/content_details_body_text" />

                <TextView
                    android:id="@+id/writer"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="12dp"
                    android:drawableLeft="@drawable/ic_edit_white_24dp"
                    android:drawablePadding="@dimen/movie_details_padding"
                    android:focusable="false"
                    android:gravity="center_vertical"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:textColor="#f0f0f0"
                    android:textSize="@dimen/content_details_body_text" />

                <TextView
                    android:id="@+id/guest_stars"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="12dp"
                    android:drawableLeft="@drawable/ic_people_white_24dp"
                    android:drawablePadding="@dimen/movie_details_padding"
                    android:focusable="false"
                    android:gravity="center_vertical"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:textColor="#f0f0f0"
                    android:textSize="@dimen/content_details_body_text" />

                <TextView
                    android:id="@+id/textView3"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:drawableLeft="@drawable/ic_folder_open_white_24dp"
                    android:drawablePadding="@dimen/movie_details_padding"
                    android:gravity="center_vertical"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:textColor="#FFFFFF"
                    android:textSize="@dimen/content_details_body_text" />
            </LinearLayout>

        </LinearLayout>

    </com.miz.views.ObservableScrollView>

    <FrameLayout
        android:id="@+id/progress_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bg"
        android:visibility="gone">

        <ProgressBar
            android:id="@+id/progressBar1"
            style="?android:attr/progressBarStyleLarge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

</FrameLayout>

Update

Fragment

@SuppressLint("InflateParams") public class TvShowEpisodeDetailsFragment extends Fragment {




    public TvShowEpisodeDetailsFragment() {}

    public static TvShowEpisodeDetailsFragment newInstance(String showId, int season, int episode) {
        TvShowEpisodeDetailsFragment pageFragment = new TvShowEpisodeDetailsFragment();
        Bundle bundle = new Bundle();
        bundle.putString("showId", showId);
        bundle.putInt("season", season);
        bundle.putInt("episode", episode);
        pageFragment.setArguments(bundle);
        return pageFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setRetainInstance(true);
        setHasOptionsMenu(true);

        mContext = getActivity();

        mBus = MizuuApplication.getBus();

        mShowFileLocation = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(SHOW_FILE_LOCATION, true);

        mPicasso = MizuuApplication.getPicassoDetailsView(getActivity());

        mMediumItalic = TypefaceUtils.getRobotoMediumItalic(mContext);
        mMedium = TypefaceUtils.getRobotoMedium(mContext);
        mCondensedRegular = TypefaceUtils.getRobotoCondensedRegular(mContext);

        mDatabaseHelper = MizuuApplication.getTvEpisodeDbAdapter();

        LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver,
                new IntentFilter(LocalBroadcastUtils.UPDATE_TV_SHOW_EPISODE_DETAILS_OVERVIEW));

        loadEpisode();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mBroadcastReceiver);
    }

    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            loadEpisode();
            loadData();
        }
    };

    private void loadEpisode() {
        if (!getArguments().getString("showId").isEmpty() && getArguments().getInt("season") >= 0 && getArguments().getInt("episode") >= 0) {
            Cursor cursor = mDatabaseHelper.getEpisode(getArguments().getString("showId"), getArguments().getInt("season"), getArguments().getInt("episode"));

            if (cursor.moveToFirst()) {
                mEpisode = new TvShowEpisode(getActivity(),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_SHOW_ID)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE_TITLE)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE_PLOT)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_SEASON)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE_AIRDATE)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE_DIRECTOR)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE_WRITER)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE_GUESTSTARS)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE_RATING)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_HAS_WATCHED)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_FAVOURITE))
                );

                mEpisode.setFilepaths(MizuuApplication.getTvShowEpisodeMappingsDbAdapter().getFilepathsForEpisode(
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_SHOW_ID)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_SEASON)),
                        cursor.getString(cursor.getColumnIndex(DbAdapterTvShowEpisodes.KEY_EPISODE))
                ));
            }
            cursor.close();
        }
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.episode_details, container, false);
    }


    @Override
    public void onViewCreated(final View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mBackdrop = (ImageView) view.findViewById(R.id.imageBackground);
        mEpisodePhoto = (ImageView) view.findViewById(R.id.episodePhoto);
        mDetailsArea = view.findViewById(R.id.details_area);

        mTitle = (TextView) view.findViewById(R.id.movieTitle);
        mSeasonEpisodeNumber = (TextView) view.findViewById(R.id.textView7);
        mDescription = (TextView) view.findViewById(R.id.textView2);
        mFileSource = (TextView) view.findViewById(R.id.textView3);
        mAirDate = (TextView) view.findViewById(R.id.textReleaseDate);
        mRating = (TextView) view.findViewById(R.id.textView12);
        mDirector = (TextView) view.findViewById(R.id.director);
        mWriter = (TextView) view.findViewById(R.id.writer);
        mGuestStars = (TextView) view.findViewById(R.id.guest_stars);
        mScrollView = (ObservableScrollView) view.findViewById(R.id.observableScrollView);
        mFab = (FloatingActionButton) view.findViewById(R.id.fab);

        mFab.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ViewUtils.animateFabJump(v, new SimpleAnimatorListener() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        play();
                    }
                });
            }
        });  
        
        ...

    }
    
    ...

}

Solution

  • I have set up a RecyclerView adapter with ViewPager in an activity namely TvShowEpisodeDetails , it works well but there is one issue, the Layout of RecyclerView is fixed while scrolling up and down in the Fragment(TvShowEpisodeDetailsFragment). But I want it to scroll with them.

    It's hard to use the RecyclerView outside of the ViewPager page fragment; because you can't synchronize and link the touch/motion events when you scroll the page up/down (or when you swipe to next/previous page) between both the page fragment and a standalone (i.e. activity) RecyclerView. Even that won't be smooth.

    So, the RecyclerView should be a part of the ViewPager page.

    And off course this issue will be gone if I set RecyclerView adapter inside fragment, but I will get unfixable highlighting and scrolling issues

    So, now the issue of adding the RecyclerView in the ViewPager fragment is the highlighting issue of the RecyclerView item when you click on any item or when you swipe the ViewPager to right/left page.

    But, the main issue is that there is a RecyclerView instance per page, i.e. if you have 5 pages, then you have 5 RecyclerViews.

    So, it's a cumbersome to track highlighting only within the RecyclerView adapter class. And hence manipulating that with OnClickListeners solely will have issues of linking these RecyclerView and adapter instances together to update their highlighting item.

    So, a simpler approach is to pass a parameter to the RecyclerView adapter with the selected item (which is definitely equals to the current ViewPager page position).

    And then do a check in the onBindViewHolder() if the position equals to the passed-in value, then highlight the item; otherwise keep items with the original color.

    So, wrapping this up into actions:

    • Change the adapter constructor to accept a highlighted_position parameter
    public PlanetAdapter(ArrayList<PlanetModel> episodeslist, 
                             int highlightedPosition, OnItemClickListener listener)
    
    • Whenever you create the ViewPager fragment using newInstance() pass-in the current position of the page fragment:

    So, in the TvShowEpisodeDetails activity:

    for (int i = 0; i < mEpisodes.size(); i++)
        fragments.add(TvShowEpisodeDetailsFragment
                        .newInstance(mShowId, 
                                  Integer.parseInt(mEpisodes.get(i).getSeason()),
                                  Integer.parseInt(mEpisodes.get(i).getEpisode()), 
                                  i)); // The position
    

    And in the TvShowEpisodeDetailsFragment fragment, register this into a field in the fragment argument:

    public static TvShowEpisodeDetailsFragment newInstance(String showId, int season, int episode, int position) { // adding position
        TvShowEpisodeDetailsFragment pageFragment = new TvShowEpisodeDetailsFragment();
        Bundle bundle = new Bundle();
    
        // trimmed code.
    
        bundle.putInt("position", position); // <<<< into the bundle
        pageFragment.setArguments(bundle);
        return pageFragment;
    }
    
    • And set that in the adapter creation:
    int currentPosition = getArguments().getInt("position");
    planetAdapter = new PlanetAdapter(episodeslist, currentPosition, new PlanetAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(final int pos) {
            mCallback.sendText(pos);
        }
    });
    
    • And reflect that in the adapter to highlight the item:
    public class PlanetAdapter extends RecyclerView.Adapter<PlanetAdapter.PlanetViewHolder> {
    
        private final int highlightedPos;
        public PlanetAdapter(ArrayList<PlanetModel> episodeslist, int highlightedPosition, OnItemClickListener listener) {
            this.episodeslist = episodeslist;
            this.listener = listener;
            this.highlightedPos = highlightedPosition; // <<<  set the position
        }
    
        @Override
        public void onBindViewHolder(PlanetAdapter.PlanetViewHolder vh, final int position) {
            TextView tv = (TextView) vh.itemView;
            PlanetModel planetModel = episodeslist.get(position);
            tv.setText(planetModel.getPlanetName());
            tv.setCompoundDrawablesWithIntrinsicBounds(R.drawable.bg, 0, 0, 0);
    
            if (highlightedPos == position) { //<<<<< Highlight the item
                vh.itemView.setBackgroundColor(getContext().getResources().getColor(R.color.colorPrimaryLight));
                Log.d("LOG_TAG", "onClick: Highlight item: " + highlightedPos);
    
            } else {
                vh.itemView.setBackgroundColor(getContext().getResources().getColor(R.color.colorPrimaryDark));
                Log.d("LOG_TAG", "onClick: No highlight: " + highlightedPos);
            }
        }
    }