问题
I have a vertical RecyclerView hosted in a CoordinatorLayout featuring a collapsing toolbar layout. The ViewHolder for this RecyclerView contains yet another RecyclerView with a GridLayoutManager.
The reason for this nesting is that the inner RecyclerView displays a collection of pictures that can have anywhere between 1 - 20 pictures. I don't know how many pictures there will be, but the span of the Grid must always be 3. Every time I bind the ViewHolder I adjust the layout params of the RecyclerView to just accommodate the contents.
This works great if there is no CollapsingToolBarLayout. However, if there is a collapsing ToolBar, scrolling with my finger on the inner RecyclerView does not scroll the Collapsing toolbar, scrolling on another Item however does.
To reemphasize, in the bound ViewHolder, if I begin to scroll on any other item besides the nested RecyclerView, scrolling works fine. However, If I begin the scroll on the nested RecyclerView, then it breaks.
Is it possible to prevent scrolling interception on the inner RecyclerView and let only the Parent scroll? I would like the inner RecyclerView to still handle clicks however.
Here's a YouTube link for the phenomenon: https://youtu.be/fzj7HmqskzU
Here is the XML for the parent RecyclerView:
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivityFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp"
app:contentScrim="?attr/colorPrimary"
app:layout_collapseParallaxMultiplier="0.7"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<View
android:id="@+id/header_view"
android:layout_width="match_parent"
android:layout_height="192dp"
android:animateLayoutChanges="true"
android:background="@color/primary" />
<android.support.v7.widget.Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/abc_action_bar_default_height_material"
android:elevation="4dp"
app:layout_collapseMode="pin"
app:popupTheme="@style/Theme.AppCompat.NoActionBar"
app:theme="@style/Theme.AppCompat.NoActionBar" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
Here's the primary ViewHolder that hosts the nested RecyclerView:
public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.ViewHolder> {
private static final int MODEL_OBJECT = 1;
private List<ModelObject> modelObjects;
private Context context;
private int imageSize;
public SimpleAdapter(List<ModelObject> modelObjects) {
this.modelObjects = modelObjects;
setHasStableIds(true);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
context = viewGroup.getContext();
int screenWidth= context.getResources().getDisplayMetrics().widthPixels;
imageSize = screenWidth / 3;
final LayoutInflater inflater = LayoutInflater.from(context);
View itemView;
itemView = inflater.inflate(R.layout.fragment_main_row, viewGroup, false);
return new ViewHolder(itemView, viewType);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
final ModelObject modelObject = modelObjects.get(position);
// set text
modelObject.setPosition(position + 1);
viewHolder.titleTextView.setText(modelObject.getTitle());
viewHolder.positionTextView.setText("" + (position + 1));
adjustsizes(viewHolder.recyclerView, modelObject, imageSize);
final NestedAdapter nestedAdapter = new NestedAdapter(modelObject.getPhotos());
// Create and set GridLayoutManager for RecyclerView
final GridLayoutManager recylerViewGridLayoutManager = new GridLayoutManager(context, 3);
recylerViewGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return 1;
}
});
viewHolder.recyclerView.setAdapter(nestedAdapter);
viewHolder.recyclerView.setLayoutManager(recylerViewGridLayoutManager);
}
@Override
public int getItemViewType(int position) {
return MODEL_OBJECT;
}
@Override
public int getItemCount() {
return modelObjects.size();
}
@Override
public long getItemId(int position) {
return modelObjects.get(position).hashCode();
}
private void adjustsizes(RecyclerView recyclerView, ModelObject modelObject, int imageSize) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) recyclerView.getLayoutParams();
if (modelObject.getPhotos() != null) {
Double numberOfRowsDouble = modelObject.getPhotos().size() / 3.0;
int numberOfRowsInt = (numberOfRowsDouble.intValue() < numberOfRowsDouble)
? numberOfRowsDouble.intValue() + 1
: numberOfRowsDouble.intValue();
params.height = imageSize * numberOfRowsInt;
}
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public int viewType;
public ViewGroup mContainer;
public View mDragHandle;
public TextView titleTextView;
public TextView positionTextView;
public RecyclerView recyclerView;
public ViewHolder(View itemView, int viewType) {
super(itemView);
this.viewType = viewType;
mContainer = (ViewGroup) itemView.findViewById(R.id.container);
mDragHandle = itemView.findViewById(R.id.drag_handle);
titleTextView = (TextView) itemView.findViewById(R.id.title);
positionTextView = (TextView) itemView.findViewById(R.id.position);
recyclerView = (RecyclerView) itemView.findViewById(R.id.recycler_view);
}
}
}
Finally, here's the adapter for the nested child with the picture:
public class NestedAdapter extends RecyclerView.Adapter<NestedAdapter.PhotoViewHolder> {
private static final int PHOTO = 1;
private Context context;
ArrayList<String> photos;
/**
* Default constructor, takes no data as nothing has been recieved from the API
*/
public NestedAdapter(ArrayList<String> photos) {
this.photos = photos;
}
@Override
public PhotoViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
this.context = viewGroup.getContext();
View itemView;
switch (viewType) {
default: // Load default Message layout as bucketLists all have the same layout
itemView = LayoutInflater.from(context).inflate(R.layout.fragment_photo_row, viewGroup, false);
break;
}
return new PhotoViewHolder(itemView, viewType);
}
@Override
public void onBindViewHolder(PhotoViewHolder viewHolder, final int position) {
// Switch depending on the kind of View
switch (getItemViewType(position)) {
case PHOTO: // Subtract 1, padding has taken up a space
final String photo = photos.get(position);
Picasso.with(context)
.load(photo)
.placeholder(R.drawable.rocks) // Must use place holder or fit won't work!
.fit()
.centerCrop()
.into(viewHolder.photo);
break;
}
}
@Override
public int getItemViewType(int position) {
return PHOTO;
}
@Override
public int getItemCount() {
return photos.size();
}
// ViewHolder for actual content
public final static class PhotoViewHolder extends RecyclerView.ViewHolder {
public int viewType; // Used to specify the view type.
public ImageView photo;
public PhotoViewHolder(View itemView, int ViewType) {
super(itemView);
switch (ViewType) {
case PHOTO:
viewType = PHOTO;
photo = (ImageView) itemView.findViewById(R.id.photo);
break;
}
}
}
}
回答1:
I had the same issue with RecyclerView and CollapsingToolbarLayout, where the RecyclerView was inside a NestedScrollView child layout.
For me the solution was to set programmatically:
mRecyclerView.setNestedScrollingEnabled(false);
Note: I tried setting this on the RecyclerView in XML but it did not work?
android:nestedScrollingEnabled="false"
Another trick to make the CollapsingToolbarLayout scroll is to set a 1000dp min height value to the child of the NestedScrollView
android:minHeight="1000dp"
why 1000dp? SupportDesignDemos example here: https://github.com/android/platform_development/blob/master/samples/SupportDesignDemos/res/layout/include_appbar_scrollview.xml
Layout:
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout >
<ImageView/>
<android.support.v7.widget.Toolbar/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout android:minHeight="1000dp">
<android.support.v7.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
回答2:
mRecyclerView.setNestedScrollingEnabled(false);
回答3:
So while the above is still true, I found a workaround.
The workaround involves wrapping the ViewHolder RecyclerView in a RelativeLayout, and overlaying a transparent clickable view over it. In my case, I used a TextView.
Now while fixes the scrolling, there's still the issue of passing clicks to the right ViewHolder in the ViewHolderRecyclerView. To work around this second setback, I extended the GestureDetector.SimpleOnGestureListener class to contain a recyclerView. Then, I set a OnTouchListener on the wrapperTextView which then forwards the MotionEvent to the GestureDetector.SimpleOnGestureListener subclass.
The subclass finally interprets the motion event and passes it to the inner RecyclerView, which in turn from the coordinates of the click, figures out the child view clicked and calls performClick() on the child.
来源:https://stackoverflow.com/questions/31632204/nested-recyclerview-in-viewholder-breaks-collapsing-toolbar-layout