仿宝宝树孕育的圆形ListView的实现

仿宝宝树孕育的圆形ListView的实现

宝宝树孕育首页有一个圆形的转盘,可以旋转,点击,快速滑动等功能,选择某一项后,可以很平滑地显示选择项。如图所示:

这里写图片描述

参考别人写的类似圆形的组件,都不能很好的实现该功能,只好在别人的基础上做一些修改。该组件继承自ViewGroup类,并重写了它的onMeasure和onLayout方法。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int maxChildWidth = 0;
        int maxChildHeight = 0;
        // Measure once to find the maximum child size.
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);
        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST);

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth());
            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
        }

        // Measure again for each child to be exactly the same size.
        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxChildWidth,
                MeasureSpec.EXACTLY);
        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxChildHeight,
                MeasureSpec.EXACTLY);

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }

        setMeasuredDimension(resolveSize(maxChildWidth, widthMeasureSpec),
                resolveSize(maxChildHeight, heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int layoutWidth = r - l;
        int layoutHeight = b - t;

        // Laying out the child views
        final int childCount = getChildCount();
        int left, top;
        radius = (layoutWidth <= layoutHeight) ? layoutWidth * 2 / 5
                : layoutHeight * 2 / 5;

        childWidth = radius;
        childHeight = radius / 2;

        float angleDelay = 360.0f / getChildCount();

        for (int i = 0; i < childCount; i++) {
            final ItemView child = (ItemView) getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            if (angle > 360) {
                angle -= 360;
            } else {
                if (angle < 0) {
                    angle += 360;
                }
            }

            child.setPosition(i);

            left = Math.round((float) (((layoutWidth / 2) - childWidth / 2) + radius
                    * Math.cos(Math.toRadians(angle))));
            top = Math.round((float) (((layoutHeight / 2) - childHeight / 2) + radius
                    * Math.sin(Math.toRadians(angle))));

            child.layout(left, top, left + childWidth, top + childHeight);
            angle += angleDelay;
        }
    }

并规定组件的正下方为旋转基准,通过记录旋转的角度值来计算旋转了几圈。组件写死了六个View,这六个View根据旋转的角度和勾股定理公式来刷新它的位置,并显示不同的数据:

 private void setItems() {
        removeAllViews();
        for (int i = 0; i < ITEM_COUNT; i++) {
            ItemView itemView = new ItemView(getContext());
            itemView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            itemView.setPosition(i);
            itemView.setText(null);
            itemView.setTextVisible(true);
            itemView.setTextColor(textColor);
            itemView.setTextSize(textSize);
            itemView.setSmallText(null);
            if (i == 0) {
                itemView.setDataIndex(0);
                itemView.setIncreaseId(0);
            } else {
                itemView.setDataIndex(ITEM_COUNT - i);
                itemView.setIncreaseId(ITEM_COUNT - i);
            }
            addView(itemView, i);
        }
    }

该组件通过GestureDetector类来监听手势的tap事件,通过重写ViewGroup的onTouchEvent方法来监听用户的点击和滑动事件:

  @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        if (isEnabled()) {
            gestureDetector.onTouchEvent(event);
            if (isRotating) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        if (circleUpView != null && inRangeOfView(circleUpView, event)) {
                            return false;
                        }
                        // reset the touched quadrants
                        for (int i = 0; i < quadrantTouched.length; i++) {
                            quadrantTouched[i] = false;
                        }

                        stopAnimation();
                        touchStartAngle = getPositionAngle(event.getX(),
                                event.getY());
                        didMove = false;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (circleUpView != null && inRangeOfView(circleUpView, event)) {
                            return false;
                        }
                        double currentAngle = getPositionAngle(event.getX(),
                                event.getY());
                        float deltAngle = (float) (touchStartAngle - currentAngle);
                        if (deltAngle < 0) {
                            //anti clockwise
                            if (isFirstItem) {
                                return false;
                            }

                        } else if (deltAngle > 0) {
                            //clockwise
                            if (isEndItem) {
                                return false;
                            }
                        }
                        rotateButtons(deltAngle);
                        touchStartAngle = currentAngle;
                        didMove = true;
                        break;
                    case MotionEvent.ACTION_UP:
                        if (didMove) {
                            rotateViewToCenter((ItemView) getChildAt(selected));
                        }
                        break;
                }
            }

            // set the touched quadrant to true
            quadrantTouched[getPositionQuadrant(event.getX()
                    - (circleWidth / 2), circleHeight - event.getY()
                    - (circleHeight / 2))] = true;
            return true;
        }
        return false;
    }

组件的平滑移动是通过ObjectAnimator和NineOldAndroids来实现的:

 private void animateTo(float endDegree, long duration) {
        if (animator != null && animator.isRunning()
                || Math.abs(angle - endDegree) < 1) {
            return;
        }
        animator = ObjectAnimator.ofFloat(this, "angle", angle,
                endDegree);
        animator.setDuration(duration);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addListener(new Animator.AnimatorListener() {
            private boolean wasCanceled = false;

            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (wasCanceled) {
                    return;
                }

                if (onRotationFinishedListener != null) {
                    onRotationFinishedListener.onRotationFinished(getSelectedItem());
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                wasCanceled = true;
            }
        });
        animator.start();
    }

该组件整体上实现了相似功能,但和宝宝孕育树相比还是有一点瑕疵,不过整体效果是可以的。
这里写图片描述

本项目已经托管到github上面,详细说明和源码直接clone下来就可以。

github项目地址

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值