前言

    项目要求模仿时光手杖APP做一个可平移、旋转、缩放的控件,于是就开始研究了起来,网上找的方案加上自己的思考,得出了以下完美版平移、缩放、旋转控件,双指缩放旋转,单指缩放旋转都支持。

效果图

    

    我们做出来的控件效果会比上图略好,时光手帐不支持双指的旋转,我们这个控件是支持的。

核心代码

1.单双指旋转缩放平移逻辑

        switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    if (mOnTouchListener != null) {
                        mOnTouchListener.onTouch(this);
                    }
                    setBorder(true);
                    oriLeft = getLeft();
                    oriRight = getRight();
                    oriTop = getTop();
                    oriBottom = getBottom();
                    lastTrueY = event.getRawY();
                    lastTrueX = event.getRawX();
                    oriX = (int) event.getRawX();
                    oriY = (int) event.getRawY();
                    lastSpacing = getSpacingRelateMidPoint(oriX,oriY);
                    textRotationDegree = getRotation();
                    scale = getScaleX();
                    if (initWidth == 0) {
                        initWidth = oriRight - oriLeft;
                        if (initWidth <= 0) {
                            initWidth = minWidth;
                        }
                    }
                    if (initHeight == 0) {
                        initHeight = oriBottom - oriTop;
                        if (initHeight <= 0) {
                            initHeight = minHeight;
                        }
                    }
                    LogUtil.d(TAG + " initWidth = " + initWidth + " ,initHeight = " + initHeight);
                    moveType = 1;
                    actionX = event.getRawX();
                    actionY = event.getRawY();
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    moveType = 2;
                    spacing = getSpacing(event);
                    degree = getDegree(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mOnTouchListener != null) {
                        mOnTouchListener.onMoveEvent(this);
                    }
                    updateLocation();//更新图标图片可见矩形
                    float tempRawX = event.getRawX();
                    float tempRawY = event.getRawY();

                    float dx = tempRawX - lastTrueX;
                    float dy = tempRawY - lastTrueY;
                    lastTrueX = tempRawX;
                    lastTrueY = tempRawY;
                    if (event.getPointerCount() == 1 && dragDirection == RIGHT_BOTTOM) {
                        Point cen = new Point(oriLeft+(oriRight-oriLeft)/2,oriTop+(oriBottom-oriTop)/2);
                        Point first = new Point(oriX,oriY);
                        Point second = new Point((int) tempRawX,(int) tempRawY);

                        //旋转
                        textRotationDegree += angle(cen,first,second);
                        setRotation(textRotationDegree);
                        //缩放
                        float tempScale = 1.0f;
                        lastSpacing = getSpacing(cen,first);
                        currentSpacing = getSpacing(cen,second);
                        tempScale = currentSpacing/lastSpacing;
                        //想增加拖动缩放的灵敏度,将0.02增大即可
                        if (tempScale < 1) {
                            tempScale -= 0.008;
                        } else if (tempScale > 1) {
                            tempScale += 0.008;
                        }
                        scale = scale * tempScale;
                        if(scale >= MAX_SCALE){
                            scale = MAX_SCALE;
                        }else if(scale <= MIN_SCALE){
                            scale = MIN_SCALE;
                        }
                        setScaleX(scale);
                        setScaleY(scale);

                        oriX = (int) tempRawX;
                        oriY = (int) tempRawY;
                        lastSpacing = currentSpacing;
                        resetCornerSize();
                        break;
                    }
                    if (moveType == 1) {
                        translationX = getTranslationX();
                        translationY = getTranslationY();
                        if (isTranstX(event.getRawX()))
                            translationX = translationX + event.getRawX() - actionX;
                        if (isTranstY(event.getRawY()))
                            translationY = translationY + event.getRawY() - actionY;
                        setTranslationX(translationX);
                        setTranslationY(translationY);
                        actionX = event.getRawX();
                        actionY = event.getRawY();
                    } else if (moveType == 2) {
                        scale = scale * getSpacing(event) / spacing;
                        setScaleX(scale);
                        setScaleY(scale);
                        rotation = rotation + getDegree(event) - degree;
                        if (rotation > 360) {
                            rotation = rotation - 360;
                        }
                        if (rotation < -360) {
                            rotation = rotation + 360;
                        }
                        setRotation(rotation);
                        resetCornerSize();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                    if (mOnTouchListener != null) {
                        mOnTouchListener.onActionUp();
                    }
                    moveType = 0;
                    dragDirection = CENTER;
                    requestLayout();
            }

2.相关计算(角度、距离等)

public float angle(Point cen, Point first, Point second)
    {
        float dx1, dx2, dy1, dy2;

        dx1 = first.x - cen.x;
        dy1 = first.y - cen.y;
        dx2 = second.x - cen.x;
        dy2 = second.y - cen.y;

        // 计算三边的平方
        float ab2 = (second.x - first.x) * (second.x - first.x) + (second.y - first.y) * (second.y - first.y);
        float oa2 = dx1*dx1 + dy1*dy1;
        float ob2 = dx2 * dx2 + dy2 *dy2;

        // 根据两向量的叉乘来判断顺逆时针
        boolean isClockwise = ((first.x - cen.x) * (second.y - cen.y) - (first.y - cen.y) * (second.x - cen.x)) > 0;

        // 根据余弦定理计算旋转角的余弦值
        double cosDegree = (oa2 + ob2 - ab2) / (2 * Math.sqrt(oa2) * Math.sqrt(ob2));

        // 异常处理,因为算出来会有误差绝对值可能会超过一,所以需要处理一下
        if (cosDegree > 1) {
            cosDegree = 1;
        } else if (cosDegree < -1) {
            cosDegree = -1;
        }

        // 计算弧度
        double radian = Math.acos(cosDegree);

        // 计算旋转过的角度,顺时针为正,逆时针为负
        return (float) (isClockwise ? Math.toDegrees(radian) : -Math.toDegrees(radian));

    }

    private LayoutParams setLayoutParams(View view) {
        LayoutParams ltparams = (LayoutParams) view.getLayoutParams();
        currentCornerWidth = (int) (dp2px(19) / scale);
        currentCornerHeight = (int) (dp2px(19) / scale);
        ltparams.width = currentCornerWidth;
        ltparams.height = currentCornerHeight;
        return ltparams;
    }

    // 触碰两点间距离
    private float getSpacing(MotionEvent event) {
        //通过三角函数得到两点间的距离
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    // 触碰两点间距离
    private float getSpacing(Point start,Point end) {
        //通过三角函数得到两点间的距离
        float x = end.x - start.x;
        float y = end.y - start.y;
        return (float) Math.sqrt(x * x + y * y);
    }

    // 获得某点相对于中点的距离
    private float getSpacingRelateMidPoint(int pointX,int pointY) {
        //通过三角函数得到两点间的距离
        Point midPoint = new Point(oriLeft+(oriRight-oriLeft),oriTop+(oriBottom-oriTop));
        float x = pointX - midPoint.x;
        float y = pointY - midPoint.y;
        return (float) Math.sqrt(x * x + y * y);
    }

    // 取旋转角度
    private float getDegree(MotionEvent event) {
        //得到两个手指间的旋转角度
        double delta_x = event.getX(0) - event.getX(1);
        double delta_y = event.getY(0) - event.getY(1);
        double radians = Math.atan2(delta_y, delta_x);
        return (float) Math.toDegrees(radians);
    }

    /**
     * 触摸点为中心->>移动
     */
    private void center(int dx, int dy) {
//        isTopLimit = false;
//        isBottomLimit = false;
        int left = getLeft() + dx;
        int top = getTop() + dy;
        int right = getRight() + dx;
        int bottom = getBottom() + dy;

        if (left < 0) {
            left = 0;
            right = left + getWidth();
        }
        if (right > screenWidth) {
            right = screenWidth;
            left = right - getWidth();
        }
        if (top < 0) {
            top = 0;
            bottom = top + getHeight();
//            isTopLimit = true;
        }
        if (bottom > screenHeight) {
            bottom = screenHeight;
            top = bottom - getHeight();
//            isBottomLimit = true;
        }

        oriLeft = left;
        oriTop = top;
        oriRight = right;
        oriBottom = bottom;
    }

    /**
     * 触摸点为上边缘
     */
    private void top(int dy) {
//        isTopLimit = false;
//        isBottomLimit = false;

        int lastTop = oriTop;
        oriTop += dy;
        int newTop = oriTop;
        Log.e("新旧长度:", lastTop + "," + newTop);
        if (isImage) {
            oriLeft += dy / 2;
            oriRight -= dy / 2;

            if (oriLeft < 0) {
                oriLeft = 0;
            }
            if (oriRight - oriLeft < minWidth) {
                oriLeft = oriRight - minWidth;
            }

            if (oriRight > screenWidth) {
                oriRight = screenWidth;
            }
            if (oriRight - oriLeft < minWidth) {
                oriRight = oriLeft + minWidth;
            }
        }
        if (oriTop < 0) {
            oriTop = 0;
//            isTopLimit = true;
        }
        if (oriBottom - oriTop < minHeight) {
            oriTop = oriBottom - minHeight;
        }
    }

    /**
     * 触摸点为下边缘
     */
    private void bottom(int dy) {
//        isTopLimit = false;
//        isBottomLimit = false;
        oriBottom += dy;
        if (isImage) {
            oriLeft -= dy / 2;
            oriRight += dy / 2;

            if (oriLeft < 0) {
                oriLeft = 0;
            }
            if (oriRight - oriLeft < minWidth) {
                oriLeft = oriRight - minWidth;
            }

            if (oriRight > screenWidth) {
                oriRight = screenWidth;
            }
            if (oriRight - oriLeft < minWidth) {
                oriRight = oriLeft + minWidth;
            }
        }
        if (oriBottom > screenHeight) {
            oriBottom = screenHeight;
//            isBottomLimit = true;
        }
        if (oriBottom - oriTop < minHeight) {
            oriBottom = minHeight + oriTop;
        }
    }

    /**
     * 触摸点为右边缘
     */
    private void right(int dx) {
//        isTopLimit = false;
//        isBottomLimit = false;
        oriRight += dx;
        if (isImage) {
            oriTop -= dx / 2;
            oriBottom += dx / 2;

            if (oriTop < 0) {
                oriTop = 0;
//                isTopLimit = true;
            }
            if (oriBottom - oriTop < minHeight)
                oriTop = oriBottom = minHeight;

            if (oriBottom > screenHeight) {
                oriBottom = screenHeight;
//                isBottomLimit = true;
            }
            if (oriBottom - oriTop < minHeight) {
                oriBottom = minHeight + oriTop;
            }
        }
        if (oriRight > screenWidth) {
            oriRight = screenWidth;
        }
        if (oriRight - oriLeft < minWidth) {
            oriRight = oriLeft + minWidth;
        }
    }

    /**
     * 触摸点为左边缘
     */
    private void left(int dx) {
//        isTopLimit = false;
//        isBottomLimit = false;
        oriLeft += dx;
        if (isImage) {
            oriTop += dx / 2;
            oriBottom -= dx / 2;

            if (oriTop < 0) {
                oriTop = 0;
//                isTopLimit = true;
            }
            if (oriBottom - oriTop < minHeight)
                oriTop = oriBottom = minHeight;

            if (oriBottom > screenHeight) {
                oriBottom = screenHeight;
//                isBottomLimit = true;
            }
            if (oriBottom - oriTop < minHeight) {
                oriBottom = minHeight + oriTop;
            }
        }
        if (oriLeft < 0) {
            oriLeft = 0;
        }
        if (oriRight - oriLeft < minWidth) {
            oriLeft = oriRight - minWidth;
        }
    }

总结

    以上就是该控件的核心代码和功能介绍。实现平移很简单,实现旋转和缩放的话,重点在于角度和距离的计算,因此需要一定的数学知识。以上代码是从项目中截取的重点部分,可能无法直接套用或者缺少一些东西,但是实现思路很明确了,提供给大家做个参考或者移植到自己的项目中。希望能帮到跟我遇到同样需求的攻城狮。有疑问可在下方留言。

该类完整代码下载地址:https://download.csdn.net/download/yonghuming_jesse/11906385

另,附上博主自己的淘宝客项目下载码(可查淘宝商品优惠券,使用优惠券购物后可加微信客服返现,返现额度全网最高,客服微信android_jesse):

博主上传资源下载链接:

全屏播放视频不拉伸源码:

https://download.csdn.net/download/yonghuming_jesse/10646274

科大讯飞语音评测服务接入源码:

https://download.csdn.net/download/yonghuming_jesse/10616924

android饺子播放器使用源码:

https://download.csdn.net/download/yonghuming_jesse/10619119

视频播放前显示视频第一帧源码:

https://download.csdn.net/download/yonghuming_jesse/10646332

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐