美文网首页
可以放大指定item的LayoutManager

可以放大指定item的LayoutManager

作者: 菜鸟何时起飞 | 来源:发表于2020-05-12 19:18 被阅读0次
/**
 *
 * 使用ScalePosition 需要先设置Model为MODEL_NOT_POSITION,设置SCALE_START 那么第一个可见view 会放大scaleRatio 其余的大小不变
 * 设置为SCALE_CENTER 那么中间的保持原始大小 两边的距离越远 大小与越小
 *
 * Model 设置MODEL_POINT_POSITION 那么可以指定currentPosition 那么指定的currentPosition对饮的view 就会放大scaleRatio 其余的不变
 */

public class StackLayoutManager extends RecyclerView.LayoutManager {

    /**
     * 一次完整的聚焦滑动所需要的移动距离
     */
    private float onceCompleteScrollLength = -1;

    /**
     * 第一个子view的偏移量
     */
    private float firstChildCompleteScrollLength = -1;

    /**
     * 屏幕可见第一个view的position
     */
    private int mFirstVisiPos;

    /**
     * 屏幕可见的最后一个view的position
     */
    private int mLastVisiPos;

    /**
     * 水平方向累计偏移量
     */
    private long mHorizontalOffset;

    /**
     * 普通view之间的margin
     */
    private float normalViewGap = 0;

    private int childWidth = 0;



    /*指定放大的item 下标固定不不变*/
    public static final int MODEL_POINT_POSITION = 0;
    /*不指定下标 滑动到指定位置 的放大*/
    public static final int MODEL_NOT_POSITION = 1;

    private int currentModel = 0;
    /*指定当前放大的position*/
    private int currentPosition = 0;

    private float scaleRatio = 1;

    @IntDef({MODEL_POINT_POSITION,MODEL_NOT_POSITION})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Model{}


    /*放大的位置在开始*/
    public static final int SCALE_START = 0;
    /*放大的位置居中*/
    public static final int SCALE_CENTER = 1;

    /*在开头放大 还是在居中放大*/
    private int scalePosition = 0;

    @IntDef({SCALE_START,SCALE_CENTER})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ScalePosition{}


    /**
     * 是否自动选中
     */
    private boolean isAutoSelect = false;
    private ValueAnimator selectAnimator;

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT);
    }

    public StackLayoutManager(Context context, int gap) {
        normalViewGap = dp2px(context, gap);
    }

    public StackLayoutManager(Context context) {
        this(context, 0);
    }

    public static float dp2px(Context context, float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                context.getResources().getDisplayMetrics());
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //  super.onLayoutChildren(recycler, state);
        if (state.getItemCount() == 0) {
            removeAndRecycleAllViews(recycler);
            return;
        }

        onceCompleteScrollLength = -1;

        // 分离全部已有的view 放入临时缓存  mAttachedScrap 集合中
        detachAndScrapAttachedViews(recycler);

        fill(recycler, state, 0);
    }

    private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, int dx) {
        int resultDelta = dx;
        resultDelta = fillHorizontalLeft(recycler, state, dx);
        recycleChildren(recycler);
        return resultDelta;
    }

    @Override
    public boolean canScrollHorizontally() {
        return true;
    }


    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 手指从右向左滑动,dx > 0; 手指从左向右滑动,dx < 0;
        // 位移0、没有子View 当然不移动
        if (dx == 0 || getChildCount() == 0) {
            return 0;
        }

        float realDx = dx / 1.0f;
        if (Math.abs(realDx) < 0.00000001f) {
            return 0;
        }

        mHorizontalOffset += dx;

        dx = fill(recycler, state, dx);

        return dx;
    }

    /**
     * 最大偏移量
     *
     * @return
     */
    private float getMaxOffset() {
        if (childWidth == 0 || getItemCount() == 0) return 0;
        // getWidth() / 2 + childWidth / 2 +
        float max = (childWidth + normalViewGap)*(getItemCount()) -getWidth()+childWidth*(scaleRatio-1);
//        return (childWidth + normalViewGap) * (getItemCount() - 1);
        return max >0 ? max :0;
    }

    /**
     * 获取最小的偏移量
     *
     * @return
     */
    private float getMinOffset() {
        if (childWidth == 0) return 0;
//        return (getWidth() - childWidth) / 2;
        return 0;
    }

    private int fillHorizontalLeft(RecyclerView.Recycler recycler, RecyclerView.State state, int dx) {
        //----------------1、边界检测-----------------
        if (dx < 0) {
            // 已到达左边界
            if (mHorizontalOffset < 0) {
                mHorizontalOffset = dx = 0;
            }
        }

        if (dx > 0) {
            if (mHorizontalOffset >= getMaxOffset()) {
                // 因为在因为scrollHorizontallyBy里加了一次dx,现在减回去
                // mHorizontalOffset -= dx;
                mHorizontalOffset = (long) getMaxOffset();
                dx = 0;
            }
        }

        // 分离全部的view,加入到临时缓存
        detachAndScrapAttachedViews(recycler);

        float startX = 0;
        float fraction = 0f;
        boolean isChildLayoutLeft = true;

        View tempView = null;
        int tempPosition = -1;

        if (onceCompleteScrollLength == -1) {
            // 因为mFirstVisiPos在下面可能被改变,所以用tempPosition暂存一下
            tempPosition = mFirstVisiPos;
            tempView = recycler.getViewForPosition(tempPosition);
            measureChildWithMargins(tempView, 0, 0);
            childWidth = getDecoratedMeasurementHorizontal(tempView);
        }

        // 修正第一个可见view mFirstVisiPos 已经滑动了多少个完整的onceCompleteScrollLength就代表滑动了多少个item
        firstChildCompleteScrollLength = childWidth ;
        if (mHorizontalOffset >= firstChildCompleteScrollLength) {
            startX = normalViewGap;
            onceCompleteScrollLength = childWidth + normalViewGap;
            mFirstVisiPos = (int) Math.floor(Math.abs(mHorizontalOffset - firstChildCompleteScrollLength) / onceCompleteScrollLength) + 1;
            /*计算第一个可见的view 偏移的距离*/
            fraction = (Math.abs(mHorizontalOffset - firstChildCompleteScrollLength) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);
        } else {
            mFirstVisiPos = 0;
            startX = getMinOffset();
            onceCompleteScrollLength = firstChildCompleteScrollLength;
            fraction = (Math.abs(mHorizontalOffset) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);
        }


        // 临时将mLastVisiPos赋值为getItemCount() - 1,放心,下面遍历时会判断view是否已溢出屏幕,并及时修正该值并结束布局
        mLastVisiPos = getItemCount() - 1;

        float normalViewOffset = onceCompleteScrollLength * fraction;
        boolean isNormalViewOffsetSetted = false;

        //----------------3、开始布局-----------------
        for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
            View item;
            if (i == tempPosition && tempView != null) {
                // 如果初始化数据时已经取了一个临时view 这里只是避免去取第一个view 已经拿出来过一次
                item = tempView;
            } else {
                item = recycler.getViewForPosition(i);
            }

            int focusPosition = (int) (Math.abs(mHorizontalOffset) / (childWidth + normalViewGap));
           /*这里只影响显示的先后顺序 不影响拜访位置的顺序*/
            if (i <= focusPosition) {
                addView(item);
            } else {
                addView(item, 0);
            }
            measureChildWithMargins(item, 0, 0);

            /*只有第一个view 需要计算 后续的直接 累加*/
            if (!isNormalViewOffsetSetted) {
                startX -= normalViewOffset;
                isNormalViewOffsetSetted = true;
            }

            int l, t, r, b;
            l = (int) startX;
            t = getPaddingTop();
            r = l + getDecoratedMeasurementHorizontal(item);
            b = t + getDecoratedMeasurementVertical(item);

            if(currentModel == MODEL_POINT_POSITION){
                if(i == currentPosition){
                    item.setPivotX(0);
                    item.setPivotY(getDecoratedMeasurementVertical(item));
                    item.setScaleX(scaleRatio);
                    item.setScaleY(scaleRatio);
                }
            }else{
                // 缩放子view
                final float minScale = 0.6f;
                float currentScale = 0f;
                final int childCenterX = (r + l) / 2;
                final int parentCenterX = getCenterX();

                if(scalePosition == SCALE_START){
                    /*如果是开头放大*/
                    if(childCenterX <= getCenterX()){
                        item.setScaleX(scaleRatio);
                        item.setScaleY(scaleRatio);
                    }else {
                        item.setScaleX(1);
                        item.setScaleY(1);
                    };
                }else {
                    isChildLayoutLeft = childCenterX <= parentCenterX;
                    if (isChildLayoutLeft) {
                        final float fractionScale = (parentCenterX - childCenterX) / (parentCenterX * 1.0f);
                        currentScale = 1.0f - (1.0f - minScale) * fractionScale;
                    } else {
                        final float fractionScale = (childCenterX - parentCenterX) / (parentCenterX * 1.0f);
                        currentScale = 1.0f - (1.0f - minScale) * fractionScale;
                    }
                    item.setScaleX(currentScale);
                    item.setScaleY(currentScale);
                }
            }


            layoutDecoratedWithMargins(item, l, t, r, b);
            /*如果是选中的view 需要添加放大的偏移*/
            if(i == currentPosition && currentModel == MODEL_POINT_POSITION){
                startX += (childWidth*scaleRatio + normalViewGap);
            }else{
                startX += (childWidth + normalViewGap);
            }

            if (startX > getWidth() - getPaddingRight()) {
                mLastVisiPos = i;
                break;
            }
        }

        return dx;
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        switch (state) {
            case RecyclerView.SCROLL_STATE_DRAGGING:
                //当手指按下时,停止当前正在播放的动画
                cancelAnimator();
                break;
            case RecyclerView.SCROLL_STATE_IDLE:
                //当列表滚动停止后,判断一下自动选中是否打开
                if (isAutoSelect) {
                    //找到离目标落点最近的item索引 是否滑动到指定Position
                    smoothScrollToPosition(findShouldSelectPosition(), null);
                }
                break;
            default:
                break;
        }
    }

    public int findShouldSelectPosition() {
        if (onceCompleteScrollLength == -1 || mFirstVisiPos == -1) {
            return -1;
        }
        int position = (int) (Math.abs(mHorizontalOffset) / (childWidth + normalViewGap));
        int remainder = (int) (Math.abs(mHorizontalOffset) % (childWidth + normalViewGap));
        // 超过一半,应当选中下一项
        if (remainder >= (childWidth + normalViewGap) / 2.0f) {
            if (position + 1 <= getItemCount() - 1) {
                return position + 1;
            }
        }
        return position;
    }

    /**
     * 平滑滚动到某个位置
     *
     * @param position 目标Item索引
     */
    public void smoothScrollToPosition(int position, OnStackListener listener) {
        if (position > -1 && position < getItemCount()) {
            startValueAnimator(position, listener);
        }
    }

    private void startValueAnimator(int position, final OnStackListener listener) {
        cancelAnimator();

        final float distance = getScrollToPositionOffset(position);

        long minDuration = 100;
        long maxDuration = 300;
        long duration;

        float distanceFraction = (Math.abs(distance) / (childWidth + normalViewGap));

        if (distance <= (childWidth + normalViewGap)) {
            duration = (long) (minDuration + (maxDuration - minDuration) * distanceFraction);
        } else {
            duration = (long) (maxDuration * distanceFraction);
        }
        selectAnimator = ValueAnimator.ofFloat(0.0f, distance);
        selectAnimator.setDuration(duration);
        selectAnimator.setInterpolator(new LinearInterpolator());
        final float startedOffset = mHorizontalOffset;
        selectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                mHorizontalOffset = (long) (startedOffset + value);
                requestLayout();
            }
        });
        selectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (listener != null) {
                    listener.onFocusAnimEnd();
                }
            }
        });
        selectAnimator.start();
    }

    /**
     * @param position
     * @return
     */
    private float getScrollToPositionOffset(int position) {
        return position * (childWidth + normalViewGap) - Math.abs(mHorizontalOffset);
    }

    /**
     * 取消动画
     */
    public void cancelAnimator() {
        if (selectAnimator != null && (selectAnimator.isStarted() || selectAnimator.isRunning())) {
            selectAnimator.cancel();
        }
    }

    /**
     * 回收需回收的item
     */
    private void recycleChildren(RecyclerView.Recycler recycler) {
        List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
        for (int i = 0; i < scrapList.size(); i++) {
            RecyclerView.ViewHolder holder = scrapList.get(i);
            removeAndRecycleView(holder.itemView, recycler);
        }
    }

    /**
     * 获取某个childView在水平方向所占的空间,将margin考虑进去
     *
     * @param view
     * @return
     */
    public int getDecoratedMeasurementHorizontal(View view) {
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
                view.getLayoutParams();
        return getDecoratedMeasuredWidth(view) + params.leftMargin
                + params.rightMargin;
    }

    /**
     * 获取某个childView在竖直方向所占的空间,将margin考虑进去
     *
     * @param view
     * @return
     */
    public int getDecoratedMeasurementVertical(View view) {
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
                view.getLayoutParams();
        return getDecoratedMeasuredHeight(view) + params.topMargin
                + params.bottomMargin;
    }

    public int getVerticalSpace() {
        return getHeight() - getPaddingTop() - getPaddingBottom();
    }

    public int getHorizontalSpace() {
        return getWidth() - getPaddingLeft() - getPaddingRight();
    }

    public interface OnStackListener {
        void onFocusAnimEnd();
    }

    public void setModel(@Model int model){
        currentModel = model;
    }

    public void setPosition(int currentPosition){
        this.currentPosition = currentPosition;
    }

    public void setScaleRatio(float scaleRatio){
        this.scaleRatio = scaleRatio;
    }

    public void setScaleModel(@ScalePosition int scalePosition){
        this.scalePosition = scalePosition;
    }

    public void isAutoSelect(boolean isAutoSelect){
        this.isAutoSelect = isAutoSelect;
    }

    private int getCenterX(){
        return scalePosition == SCALE_START ? childWidth/2 : getWidth()/2;
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        LinearSmoothScroller linearSmoothScroller =
                new LinearSmoothScroller(recyclerView.getContext());
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }
}

相关文章

网友评论

      本文标题:可以放大指定item的LayoutManager

      本文链接:https://www.haomeiwen.com/subject/kweinhtx.html