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

时间:2015-09-28 13:03:51

标签: android android-recyclerview

我需要在按比例放大滚动时始终突出显示回收器视图中的中心项目。

4 个答案:

答案 0 :(得分:5)

您应该遵循此代码,这有助于我在回收站视图中扩展中心项目。

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;
        }

    }
}

中心水平视图 enter image description here

答案 1 :(得分:1)

作为Mayank Garg's answer的附录及其评论说它不适用于第一项或最后一项,当您使用此类并且在列表的开头和结尾添加额外的填充,以使第一个项目已经居中。在这些情况下,getDecoratedRight()getDecoratedLeft()之类的函数将在返回的大小中包括额外的填充。这会弄乱视图中点的计算,因此对于第一项和最后一项来说不起作用

对此的一种解决方案是检测布局管理器是否正在显示列表的开头,并使用条件来使用不同的计算,该计算使用修饰的 anchors 中的一个作为原点,然后使用视图的一半宽度来找到中点。

换句话说,在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()而不是{{1 }}。

答案 2 :(得分:0)

我简化了该解决方案,并在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

答案 3 :(得分: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);
        }

    }
}