RecyclerView中具有动态项目宽度的水平滚动

时间:2019-01-24 09:44:59

标签: android android-layout scroll android-recyclerview

我需要创建一个滚动效果,如果某项小于某个定义的宽度(对于肖像),该效果将扩大项之间的间隔。这样做是为了使中心项居中后,显示的上一个/下一个项目的数量始终相同。

我通过在回收站视图中添加滚动侦听器来实现这一目的,在该视图中,我更改了所有部分或完全可见的物品(如果是肖像)的宽度。项目之间的最小间距是通过在项目上设置左右边距来实现的,除了第一个和最后一个项目(在最后一个边上具有较大的边距,以便它们保持居中)之外,项目之间不会发生变化。

如果我缓慢滚动,下面的实现效果很好,但是当我猛扑时,它会产生抖动。我猜这是因为fling根据fling时对象的宽度来计算它需要滚动的像素数...但是由于宽度的变化,fling运动计算是错误的,所以它需要进行调整,我猜这会产生抖动,问题是我不知道如何解决。

这是慢滚动的样子(很好地工作):

Scrolling Gif

和紧张的逃跑:

Flinging Gif

使用的代码(我跳过了我认为什么都没改变的部分): 在MainActivity onCreate中:

recyclerView.setHasFixedSize(true);

layoutManager = new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.HORIZONTAL, false);
((LinearLayoutManager) layoutManager).setInitialPrefetchItemCount(5);
recyclerView.setLayoutManager(layoutManager);

adapter = new MyAdapter(getApplicationContext(), thumbnails, recyclerView, 8, 24);
recyclerView.setAdapter(adapter);

SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

MyAdapter:

public MyAdapter(Context context, ArrayList<Thumbnail> galleryList, final RecyclerView recyclerView, float spacingDp, float sideItemsVisibleDp) {
    this.galleryList = galleryList;
    this.context = context;
    this.spacingDp = spacingDp;
    this.sideItemsVisibleDp = sideItemsVisibleDp;

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
            if (layoutManager != null && windowWidth != 0) {
                int firstPosition = layoutManager.findFirstVisibleItemPosition();
                int lastPosition = layoutManager.findLastVisibleItemPosition();

                if (firstPosition == RecyclerView.NO_POSITION || lastPosition == RecyclerView.NO_POSITION) {
                    Log.e(TAG, String.format(Locale.US, "onScrolled firstPosition %d lastPosition %d", firstPosition, lastPosition));
                    return;
                }

                for (int pos = firstPosition; pos <= lastPosition; pos++) {
                    View child = layoutManager.findViewByPosition(pos);
                    if (child != null) {
                        ViewHolder viewHolder = (ViewHolder) recyclerView.getChildViewHolder(child);
                        viewHolder.computeAndSetWidth();
                    } else {
                        Log.e(TAG, String.format(Locale.US, "onScrolled pos %d child == null", pos));
                    }
                }
            } else {
                Log.e(TAG, String.format(Locale.US, "onScrolled layoutManager %s windowWidth %d", layoutManager, windowWidth));
            }
        }
    });
}

@Override
public void onBindViewHolder(@NonNull final MyAdapter.ViewHolder viewHolder, final int position) {
    Log.d(TAG, "onBindViewHolder position " + position);

    // Set the text of the position
    viewHolder.text.setText(String.format(Locale.US, "%d", position));

    // Reset the container
    ViewGroup.LayoutParams layoutParams = viewHolder.container.getLayoutParams();
    layoutParams.height = itemMaxHeight;
    layoutParams.width = itemMaxWidth;
    viewHolder.container.setLayoutParams(layoutParams);

    // Set the image
    viewHolder.img.setScaleType(ImageView.ScaleType.FIT_CENTER);
    viewHolder.img.setImageResource(galleryList.get(position).getImageId());

    int startEndSpace = (windowWidth - itemMaxWidth) / 2;
    int leftSpace = itemMinSpacing;
    int rightSpace = itemMinSpacing;

    if (position == 0) {    // leftmost
        leftSpace = startEndSpace;
    }
    if (position == (galleryList.size() - 1)) { // rightmost
        rightSpace = startEndSpace;
    }

    ((RecyclerView.LayoutParams) layoutParams).setMargins(leftSpace, 0, rightSpace, 0);
}

ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {
    private View container;
    private ImageView img;
    private TextView text;


    public ViewHolder(View view) {
        super(view);
        container = view;
        img = (ImageView) view.findViewById(R.id.img);
        text = (TextView) view.findViewById(R.id.textview);
    }

    public boolean isPortrait() {
        return img.getMeasuredHeight() > img.getMeasuredWidth();
    }


    public void computeAndSetWidth() {
        int[] locationOnScreen = new int[2];
        container.getLocationOnScreen(locationOnScreen);

        int imgWidth = img.getMeasuredWidth();
        int centerX = locationOnScreen[0] + container.getWidth() / 2;    // location on screen of the center of image on X axis
        float percentFromEdge;

        if (centerX == (windowWidth/2)) {
            percentFromEdge = 1.0f; // is in center
        } else if (centerX > 0 && centerX < (windowWidth/2)) {    // left side of screen, but center is on screen
            percentFromEdge = (2f*centerX) / windowWidth;
        } else if (centerX > (windowWidth/2) && centerX < windowWidth) {
            percentFromEdge =  2f * (windowWidth - centerX) / windowWidth;
        } else {    // off screen
            percentFromEdge = 0.0f;
        }

        int width = (int) ((itemMaxWidth - imgWidth) * percentFromEdge + imgWidth);

        setWidth(width);

    }


    private void setWidth(final int width) {
        ViewGroup.LayoutParams layoutParams = container.getLayoutParams();
        layoutParams.width = width;
        layoutParams.height = itemMaxHeight;
        container.setLayoutParams(layoutParams);
    }

}

cell_layout.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:scaleType="fitCenter"
        android:adjustViewBounds="true" />

    <TextView
        android:id="@+id/textview"
        android:layout_gravity="center"
        android:textColor="@color/colorAccent"
        android:textSize="40sp"
        android:text="0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</FrameLayout>

RecyclerView被扩展了,在这里我覆盖了fling以减慢它的速度,但是就是这样:

@Override
public boolean fling(int velocityX, int velocityY) {
    return super.fling((int)(velocityX * flingFactor), (int)(velocityY * flingFactor));
}

1 个答案:

答案 0 :(得分:0)

好吧,对于有相同问题的任何人,这是我的解决方案:

我使所有的recyclerView项目具有相同的宽度,这是允许的最大宽度。我将ImageView移至项目容器的左/中/右,以便当容器位于中心时它位于中心,当容器位于右侧时则将其移到容器的左侧,反之亦然。完成此计算,而不是调用viewHolder.computeAndSetWidth();。在recyclerView.OnScrollListener()中。

最后一件事是将LinearSmoothScroller添加到扩展的recyclerView中,如下所示:

smoothScroller = new LinearSmoothScroller(context) {
            @Override
            protected int getHorizontalSnapPreference() {
                return LinearSmoothScroller.SNAP_TO_ANY;
            }

            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return scrollMillisecondsPerInch / displayMetrics.densityDpi;
            }
        };