如何在Android中滚动Recycler View时放大中心项?

11
我需要在滚动时通过放大来始终突出显示RecyclerView中心项。

1
更新您的问题,提出更具体的问题,并添加您目前的一些代码。如果您仍然保持这个问题,您将会得到负面评价并且失去再次提问的能力。 - Eugene H
我需要在水平的RecyclerView中突出显示中心项。是否有任何方法可以指导我?类似于这篇文章https://dev59.com/m10b5IYBdhLWcg3wMuxw... - Android
5个回答

25

你应该遵循这段代码,它帮助我缩放了回收视图中的居中项。

public class CenterZoomLayoutManager extends LinearLayoutManager {

    private final float mShrinkAmount = 0.15f;
    private final float mShrinkDistance = 0.9f;

    public CenterZoomLayoutManager(Context context) {
        super(context);
    }

    public CenterZoomLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int orientation = getOrientation();
        if (orientation == VERTICAL) {
            int scrolled = super.scrollVerticallyBy(dy, recycler, state);
            float midpoint = getHeight() / 2.f;
            float d0 = 0.f;
            float d1 = mShrinkDistance * midpoint;
            float s0 = 1.f;
            float s1 = 1.f - mShrinkAmount;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                float childMidpoint =
                        (getDecoratedBottom(child) + getDecoratedTop(child)) / 2.f;
                float d = Math.min(d1, Math.abs(midpoint - childMidpoint));
                float scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0);
                child.setScaleX(scale);
                child.setScaleY(scale);
            }
            return scrolled;
        } else {
            return 0;
        }
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int orientation = getOrientation();
        if (orientation == HORIZONTAL) {
            int scrolled = super.scrollHorizontallyBy(dx, recycler, state);

            float midpoint = getWidth() / 2.f;
            float d0 = 0.f;
            float d1 = mShrinkDistance * midpoint;
            float s0 = 1.f;
            float s1 = 1.f - mShrinkAmount;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                float childMidpoint =
                        (getDecoratedRight(child) + getDecoratedLeft(child)) / 2.f;
                float d = Math.min(d1, Math.abs(midpoint - childMidpoint));
                float scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0);
                child.setScaleX(scale);
                child.setScaleY(scale);
            }
            return scrolled;
        } else {
            return 0;
        }

    }
}

居中水平视图 在此输入图片描述


3
感谢完美的解决方案。但如何在RecyclerView初始化时将调整大小应用于第一个元素? - fHate
1
@fHate,你是如何将它应用到第一个项目的? - Divyang Panchal
同时,这并不适用于最后一项。 - Vahe Gharibyan
这是一个不错的解决方案,但在某些情况下仍存在问题。如果每个项目之间存在一定的边距,在缩放项目时,边距会变得更大或更小。在某些情况下,这是非常明显的。 - tainy
只有在第一个和最后一个项目上,我才能看到下一个/最后一个元素的一瞥,在中间我看不到下一个和最后一个元素。 - Tushar Gogna
显示剩余2条评论

12

我简化了那个解决方案并在onLayoutComplete期间添加了初始调整大小。由于我不需要垂直滚动,因此我将该部分删除。

class CenterZoomLinearLayoutManager(
    context: Context,
    private val mShrinkDistance: Float = 0.9f,
    val mShrinkAmount: Float = 0.15f
) : LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) {

    override fun onLayoutCompleted(state: RecyclerView.State?) {
        super.onLayoutCompleted(state)
        scaleChildren()
    }

    override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
        return if (orientation == HORIZONTAL) {
            super.scrollHorizontallyBy(dx, recycler, state).also { scaleChildren() }
        } else {
            0
        }
    }

    private fun scaleChildren() {
        val midpoint = width / 2f
        val d1 = mShrinkDistance * midpoint
        for (i in 0 until childCount) {
            val child = getChildAt(i) as View
            val d = Math.min(d1, Math.abs(midpoint - (getDecoratedRight(child) + getDecoratedLeft(child)) / 2f))
            val scale = 1f - mShrinkAmount * d / d1
            child.scaleX = scale
            child.scaleY = scale
        }
    }
}

请参见https://github.com/pcholt/toy-card-carousel


@fHate 我认为这就是你正在寻找的东西? - Anarchofascist
好的例子,但不幸的是第一次该项未正确居中。 - AndroidRuntimeException
这个很好用!为了让它也适用于第一个和最后一个项目,我们只需要添加一个项目装饰器,在开头和结尾加入额外的空间即可。就像这样:https://gist.github.com/dadouf/38f5816447c78d020d894c5c7c0c0fae - David Ferrand
如果我只想缩放视图中的子项,例如项目的 TextView,该如何做?目前遇到了这个问题。 - Ayusch
1
我该如何减少项目之间的边距?@Anarchofascist - hushed_voice

1
这种方法对我很有效,我解决的问题是当RecyclerView创建时,它们上面的比例尺没有应用于它们,让我也说一下,我使用的数字是基于我的需求,你需要根据自己的需求来调整比例尺。
public class CenterZoomLayoutManager extends LinearLayoutManager {
private final float mShrinkAmount = 0.25f;
private final float mShrinkDistance = 2.0f;

public CenterZoomLayoutManager(Context context) {
    super(context);
}

public CenterZoomLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
}

public CenterZoomLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
public void onLayoutCompleted(RecyclerView.State state) {
    super.onLayoutCompleted(state);
    scaleChild();
}

@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    int orientation = getOrientation();
    if (orientation == HORIZONTAL) {
        scaleChild();
        return super.scrollHorizontallyBy(dx, recycler, state);
    } else {
        return 0;
    }
}

private void scaleChild() {
    float midPoint = getWidth() / 2.f;
    float d1 = mShrinkDistance * midPoint;
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        float childMidPoint = (getDecoratedRight(child) + getDecoratedLeft(child)) / 2f;
        float d = Math.min(d1, Math.abs(midPoint - childMidPoint));
        float scale = 1.05f - mShrinkAmount * d / d1;
        child.setScaleY(scale);
        child.setScaleX(scale);
    }
}

}

请务必在onLayoutCompleted中调用scaleChild方法。这样可以在RecyclerView布局创建时就对项目应用缩放,而不仅仅是滚动时。这是一个重要的点。

@Override
public void onLayoutCompleted(RecyclerView.State state) {
    super.onLayoutCompleted(state);
    scaleChild();
}

下一个重要的点是在调用 return super.scrollHorizontallyBy(dx, recycler, state) 之前使用 scaleChild() 方法。在滚动完成之前进行调用。每次对项目进行缩放。
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    int orientation = getOrientation();
    if (orientation == HORIZONTAL) {
        scaleChild();
        return super.scrollHorizontallyBy(dx, recycler, state);
    } else {
        return 0;
    }
}

最终,这就是我的程序输出 在此输入图片描述

1
作为对 Mayank Garg's answer 和评论的补充,评论中提到这个类不能用于第一个或最后一个项目。当您在使用此类并且在列表开头和结尾添加额外的填充以使第一个项目已经居中时,就会发生这种情况。在这些情况下,像 getDecoratedRight()getDecoratedLeft() 这样的函数将包括它们返回的大小中的额外填充。这会破坏视图中点的计算,因此它不适用于第一个和最后一个项目。
解决方法是检测布局管理器是否正在显示列表的开头,并使用条件来使用不同的计算,该计算使用其中一个装饰锚点作为原点,然后使用视图的一半宽度来查找中点。
换句话说,在 Mayank 的代码中,您有:
childMidpoint =
   (getDecoratedRight(child) + getDecoratedLeft(child)) / 2.f;

您可以将其替换为类似以下内容的内容:
if (findFirstVisibleItemPosition() == 0 && i == 0) {
   childMidPoint = getDecoratedRight(child) - child.getWidth() / 2.f;
} else {
   childMidPoint = getDecoratedLeft(child) + child.getWidth() / 2.f;
}

换句话说,这个检查确保第一个子视图是或不是适配器中的第一项,如果是,则使用左侧或右侧装饰的水平位置通过减去或添加项目的一半宽度来计算中点。
另一个更简单的选择是:
childMidpoint = child.getX() + child.getWidth() / 2.0f

但是,您需要测试一下是否适用于您布局/视图上可能存在的其他 约束条件,因为Mayank使用getDecoratedLeft()而不是getX(),很可能有原因。


0

好的,跟随Anarchofascist和Mayank的建议,在Mayank的代码开头添加这个覆盖来使效果与第一个元素一起工作。

@Override
public void onLayoutCompleted(RecyclerView.State state) {
    super.onLayoutCompleted(state);

    //aqui ele executa o codigo no estado inicial, originalmente ele nao aplicava no inicio
    //este codigo é em horizontal. Para usar em vertical apagar e copiar o codigo
    int orientation = getOrientation();
    if (orientation == HORIZONTAL) {

        float midpoint = getWidth() / 2.f;
        float d0 = 0.f;
        float d1 = mShrinkDistance * midpoint;
        float s0 = 1.f;
        float s1 = 1.f - mShrinkAmount;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            float childMidpoint =
                    (getDecoratedRight(child) + getDecoratedLeft(child)) / 2.f;
            float d = Math.min(d1, Math.abs(midpoint - childMidpoint));
            float scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0);
            child.setScaleX(scale);
            child.setScaleY(scale);
        }

    } else {

        float midpoint = getHeight() / 2.f;
        float d0 = 0.f;
        float d1 = mShrinkDistance * midpoint;
        float s0 = 1.f;
        float s1 = 1.f - mShrinkAmount;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            float childMidpoint =
                    (getDecoratedBottom(child) + getDecoratedTop(child)) / 2.f;
            float d = Math.min(d1, Math.abs(midpoint - childMidpoint));
            float scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0);
            child.setScaleX(scale);
            child.setScaleY(scale);
        }

    }
}

@fHate 这就是你要找的吗? - Thiago Silva

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接