如何在Android上触摸事件的曲线上移动图像?

时间:2013-09-06 06:08:11

标签: android android-canvas

我使用

绘制了画布上的立方曲线
 myPath.cubicTo(10, 10, w, h/2, 10, h-10);

我在该屏幕上有四个ImageView,当我用触摸拖动图像时,我想在绘制的曲线上移动ImageViews。

我已经提到了链接:

Move Image on Curve Path

Move object on Curve

Move imageview on curve

我得到的是,动画在曲线上移动图像的持续时间由t定义。 但我想仅在该曲线区域的方向上移动该ImageView。

以下是我的屏幕:enter image description here

所以,我希望曲线的所有(x,y) co-ordinates仅在该曲线上移动ImageView。

否则我想要一个等式绘制曲线,以便我可以为触摸的y值插入x值。

我已经瞪了很多但没有成功。 任何建议或指导都会对我有所帮助。

2 个答案:

答案 0 :(得分:5)

方法

我建议采用与bezier不同的方法,因为你需要为它重现数学以获得位置。

通过使用简单的三角法,您可以获得相同的视觉效果,但另外可以完全控制位置。

三角

例如:

THIS ONLINE DEMO 会产生此结果(为了演示而简化版本):

Snapshot

使用圆和角度位置定义一个数组,而不是y和x位置。您可以稍后过滤角度(例如,仅显示介于-90和90度之间的角度)。

使用角度将确保它们在移动时保持有序。

var balls = [-90, -45, 0, 45];  // example "positions"

要替换贝塞尔曲线,您可以改为:

/// some setup variables
var xCenter = -80,                  /// X center of circle
    yCenter = canvas.height * 0.5,  /// Y center of circle
    radius = 220,                   /// radius of circle
    x, y;                           /// to calculate line position

/// draw half circle
ctx.arc(xCenter, yCenter, radius, 0, 2 * Math.PI);

ctx.stroke();

现在我们可以使用鼠标移动/触摸等的Y值来移动圆圈:

/// for demo, mousemove - adopt as needed for touch
canvas.onmousemove = function(e) {

    /// get Y position which is used as delta to angle
    var rect = demo.getBoundingClientRect();

    dlt = e.clientY - rect.top;

    /// render the circles in new positions        
    render();
}

渲染遍历球阵列并以角度+ delta渲染它们:

for(var i = 0, angle; i < balls.length; i++) {
    angle = balls[i];
    pos = getPosfromAngle(angle);

    /// draw circles etc. here
}

神奇的功能是:

function getPosfromAngle(a) {

    /// get angle from circle and add delta
    var angle = Math.atan2(delta - yCenter, radius) + a * Math.PI / 180;

    return [xCenter + radius * Math.cos(angle),
            yCenter + radius * Math.sin(angle)];
}

radius用作伪位置。您可以将其替换为实际的X位置,但坦白说不需要。

在这个演示中,为了简单起见,我只附加了鼠标移动。将鼠标移到画布上以查看效果。

由于这是演示代码,因此它不是最优化的结构(单独渲染背景和圆圈等)。

随意采用和修改以满足您的需求。

答案 1 :(得分:4)

这段代码我用来实现这个功能,它可以根据您的要求完美运行......

public class YourActivity extends Activity {


    private class ButtonInfo {
        public Button btnObj;
        public PointF OrigPos;
        public double origAngle;
        public double currentAngle;
        public double minAngle;
        public double maxAngle;
        boolean isOnClick = false;
    }

    private int height;
    private double radius;
    private PointF centerPoint;

    private final int NUM_BUTTONS = 4;
    private final int FIRST_INDEX = 0;
    private final int SECOND_INDEX = 1;
    private final int THIRD_INDEX = 2;
    private final int FORTH_INDEX = 3;

    private final String FIRST_TAG = "FiRST_BUTTON";
    private final String SECOND_TAG = "SECOND_BUTTON";
    private final String THIRD_TAG = "THIRD_BUTTON";
    private final String FORTH_TAG = "FORTH_BUTTON";

    private boolean animInProgress = false;
    private int currentButton = -1;

    private ButtonInfo[] buttonInfoArray = new ButtonInfo[NUM_BUTTONS];

    private int curveImageResource = -1;

    private RelativeLayout parentContainer;
    private int slop;

    private boolean initFlag = false;

    private int touchDownY = -1;
    private int touchDownX = -1;
    private int animCount;

    private Context context;

    @SuppressWarnings("deprecation")
    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);
        overridePendingTransition(R.anim.fadeinleft, R.anim.fadeoutleft);
        // hide action bar in view
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

        Thread.setDefaultUncaughtExceptionHandler(
                new MyDefaultExceptionHandler(this, getLocalClassName()));

        setContentView(R.layout.your_layout);



        context = this;

        final ImageView curve_image = (ImageView) findViewById(R.id.imageView1);
        parentContainer = (RelativeLayout) findViewById(R.id.llView);

        // Set buttons on their location
        for (int i = 0; i < NUM_BUTTONS; i++) {
            buttonInfoArray[i] = new ButtonInfo();
        }

        Button img1 = (Button) findViewById(R.id.button_option1);
        Button img2 = (Button) findViewById(R.id.button_option2);
        Button img3 = (Button) findViewById(R.id.button_option3);
        Button img4 = (Button) findViewById(R.id.button_option4);


        //1st button

        buttonInfoArray[FIRST_INDEX].btnObj = (Button) this
                .findViewById(R.id.setting_button_option);
        buttonInfoArray[FIRST_INDEX].btnObj.setTag(FIRST_TAG);




        // 2nd button
        buttonInfoArray[SECOND_INDEX].btnObj = (Button) this
                .findViewById(R.id.scanning_button_option);
        buttonInfoArray[SECOND_INDEX].btnObj.setTag(SECOND_TAG);

        // 3rd button
        buttonInfoArray[THIRD_INDEX].btnObj = (Button) this
                .findViewById(R.id.manual_button_option);
        buttonInfoArray[THIRD_INDEX].btnObj.setTag(THIRD_TAG);

        // 4th button
        buttonInfoArray[FORTH_INDEX].btnObj = (Button) this
                .findViewById(R.id.logout_button_option);
        buttonInfoArray[FORTH_INDEX].btnObj.setTag(FORTH_TAG);

        for (ButtonInfo currentButtonInfo : buttonInfoArray) {
            currentButtonInfo.btnObj.setClickable(false);
        }
        for (ButtonInfo currentButtonInfo : buttonInfoArray) {
            currentButtonInfo.btnObj.bringToFront();
        }

        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);


        ViewTreeObserver vtoLayout = parentContainer.getViewTreeObserver();
        vtoLayout.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                if (initFlag == true)
                    return;

                centerPoint = new PointF(0, (parentContainer.getHeight()) / 2);

                curve_image.setImageResource(curveImageResource);

                ViewTreeObserver vtoCurveImage = curve_image
                        .getViewTreeObserver();
                vtoCurveImage
                        .addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

                            @Override
                            public void onGlobalLayout() {


                                if (initFlag == true)
                                    return;

                                ViewConfiguration vc = ViewConfiguration.get(parentContainer
                                        .getContext());
                                slop = vc.getScaledTouchSlop();
                                parentContainer.setOnTouchListener(tlobj);


                                height = curve_image.getMeasuredHeight();
                                curve_image.getMeasuredWidth();

                                radius = (height / 2);

                                double angleDiff = Math.PI / (NUM_BUTTONS + 1);
                                double initialAngle = (Math.PI / 2 - angleDiff);
                                for (ButtonInfo currentButtonInfo : buttonInfoArray) {
                                    currentButtonInfo.origAngle = initialAngle;
                                    initialAngle -= angleDiff;
                                }

                                double tempCurrentAngle;
                                double maxAngle = (-1 * Math.PI / 2);
                                tempCurrentAngle = maxAngle;
                                for (int i = NUM_BUTTONS - 1; i >= 0; i--) {
                                    buttonInfoArray[i].maxAngle = tempCurrentAngle;

                                    int buttonHeight = buttonInfoArray[i].btnObj
                                            .getHeight();

                                    if (buttonHeight < 30) {
                                        buttonHeight = 80;
                                    }

                                    tempCurrentAngle = findNextMaxAngle(
                                            tempCurrentAngle,
                                            (buttonHeight + 5));
                                }

                                double minAngle = (Math.PI / 2);
                                tempCurrentAngle = minAngle;
                                for (int i = 0; i < NUM_BUTTONS; i++) {
                                    buttonInfoArray[i].minAngle = tempCurrentAngle;

                                    int buttonHeight = buttonInfoArray[i].btnObj
                                            .getHeight();

                                    if (buttonHeight < 30) {
                                        buttonHeight = 80;
                                    }
                                    tempCurrentAngle = findNextMinAngle(
                                            tempCurrentAngle, (buttonHeight + 5));
                                }

                                for (ButtonInfo currentButtonInfo : buttonInfoArray) {

                                    PointF newPos = getPointByAngle(currentButtonInfo.origAngle);

                                    currentButtonInfo.OrigPos = newPos;
                                    currentButtonInfo.currentAngle = currentButtonInfo.origAngle;
                                    setTranslationX(
                                            currentButtonInfo.btnObj,
                                            (int) currentButtonInfo.OrigPos.x - 50);
                                    setTranslationY(
                                            currentButtonInfo.btnObj,
                                            (int) currentButtonInfo.OrigPos.y - 50);
                                    currentButtonInfo.btnObj.requestLayout();
                                }

                                initFlag = true;
                            }

                        });

            }

        });


    }

    /**
     * Find next max angle
     * @param inputAngle
     * @param yDist
     * @return
     */
    private double findNextMaxAngle(double inputAngle, int yDist) {
        float initYPos = (float) (centerPoint.y - (Math.sin(inputAngle) * radius));

        float finalYPos = initYPos - yDist;

        float finalXPos = getXPos(finalYPos);

        double newAngle = getNewAngle(new PointF(finalXPos, finalYPos));
        return newAngle;
    }

    /**
     * Find next min angle
     * @param inputAngle
     * @param yDist
     * @return
     */
    private double findNextMinAngle(double inputAngle, int yDist) {
        float initYPos = (int) (centerPoint.y - (Math.sin(inputAngle) * radius));

        float finalYPos = initYPos + yDist;

        float finalXPos = getXPos(finalYPos);

        double newAngle = getNewAngle(new PointF(finalXPos, finalYPos));
        return newAngle;
    }

    /**
     * Apply reset transformation when user release touch
     * @param buttonInfoObj
     */
    public void applyResetAnimation(final ButtonInfo buttonInfoObj) {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); // values from 0
                                                                // to 1
        animator.setDuration(1000); // 5 seconds duration from 0 to 1

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = ((Float) (animation.getAnimatedValue()))
                        .floatValue();
                // Set translation of your view here. Position can be calculated
                // out of value. This code should move the view in a half
                // circle.

                double effectiveAngle = buttonInfoObj.origAngle
                        + ((buttonInfoObj.currentAngle - buttonInfoObj.origAngle) * (1.0 - value));

                PointF newPos = getPointByAngle(effectiveAngle);

                setTranslationX(buttonInfoObj.btnObj, newPos.x - 50);
                setTranslationY(buttonInfoObj.btnObj, newPos.y - 50);

            }
        });

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                animCount++;
                if (animCount == NUM_BUTTONS) {
                    animCount = 0;
                    currentButton = -1;
                    animInProgress = false;

                    for (ButtonInfo currentButtonInfo : buttonInfoArray) {
                        setTranslationX(currentButtonInfo.btnObj,
                                currentButtonInfo.OrigPos.x - 50);
                        setTranslationY(currentButtonInfo.btnObj,
                                currentButtonInfo.OrigPos.y - 50);

                        currentButtonInfo.isOnClick = false;
                        currentButtonInfo.currentAngle = currentButtonInfo.origAngle;
                        currentButtonInfo.btnObj.setPressed(false);
                        currentButtonInfo.btnObj.requestLayout();
                    }

                }

            }
        });
        animator.start();
    }



    /**
     * On Touch start animation
     */
    private OnTouchListener tlobj = new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent motionEvent) {


            switch (MotionEventCompat.getActionMasked(motionEvent)) {
            case MotionEvent.ACTION_MOVE:

                if (currentButton < 0) {
                    return false;
                }
                if (animInProgress == true) {
                    return true;
                }

                float delta_y = motionEvent.getRawY() - touchDownY;
                float delta_x = motionEvent.getRawX() - touchDownX;

                updateButtonPos(new PointF((int) delta_x, (int) delta_y));

                if (Math.abs(delta_x) > slop || Math.abs(delta_y) > slop) {
                    buttonInfoArray[currentButton].isOnClick = false;
                    parentContainer.requestDisallowInterceptTouchEvent(true);
                }
                return true;
            case MotionEvent.ACTION_UP:
                animCount = 0;
                if (currentButton < 0) {
                    return false;
                }

                if(animInProgress == true) {
                    return true;
                }
                animInProgress = true;
                for (ButtonInfo currentButtonInfo : buttonInfoArray) {
                    applyResetAnimation(currentButtonInfo);
                    if (currentButtonInfo.isOnClick) {
                        // TODO onClick code
                        String currentTag = (String) currentButtonInfo.btnObj.getTag();

                        if(currentTag.equalsIgnoreCase(FIRST_TAG)) {
                            //handle first button click
                        } else if(currentTag.equalsIgnoreCase(SECOND_TAG)) {
                            //handle second button click
                        } else if(currentTag.equalsIgnoreCase(THIRD_TAG)) {
                            //handle third button click
                        } else if(currentTag.equalsIgnoreCase(FORTH_TAG)) {
                            //handle forth button click
                        } 
                    }
                }
                return true;
            case MotionEvent.ACTION_DOWN:

                if (currentButton >= 0) {
                    return false;
                }

                if (animInProgress == true) {
                    return true;
                }

                animCount = 0;

                int buttonIndex = 0;
                for (buttonIndex = 0; buttonIndex < NUM_BUTTONS; buttonIndex++) {
                    final ButtonInfo currentButtonInfo = buttonInfoArray[buttonIndex];

                    if (isRectHit(currentButtonInfo.btnObj, motionEvent,
                            currentButtonInfo.OrigPos)) {
                        currentButton = buttonIndex;
                        touchDownX = (int) motionEvent.getRawX();
                        touchDownY = (int) motionEvent.getRawY();
                        currentButtonInfo.isOnClick = true;
                        currentButtonInfo.btnObj.setPressed(true);

                        break;
                    }

                }
                if (buttonIndex == NUM_BUTTONS) {
                    currentButton = -1;
                }

                break;
            default:
                break;
            }
            return false;
        }

    };

    /**
     * Get X POS
     * @param yPos
     * @return
     */
    public float getXPos(float yPos) {
        float xPos = (float) (centerPoint.x
                + Math.sqrt((radius * radius)
                        - ((yPos - centerPoint.y) * (yPos - centerPoint.y))));
        return xPos;
    }

    /**
     * Get YPos based on X
     * @param xPos
     * @param isPositive
     * @return
     */
    public float getYPos(float xPos, boolean isPositive) {
        if (isPositive)
            return (float) (centerPoint.y - Math.sqrt((radius * radius)
                    - ((xPos - centerPoint.x) * (xPos - centerPoint.x))));
        else
            return (float) (centerPoint.y + Math.sqrt((radius * radius)
                    - ((xPos - centerPoint.x) * (xPos - centerPoint.x))));
    }

    /**
     * Get New angle from define point
     * @param newPoint
     * @return
     */
    private double getNewAngle(PointF newPoint) {

        double deltaY = newPoint.y - centerPoint.y;
        double deltaX = newPoint.x - centerPoint.x;

        double newPointAngle = Math.atan(-1.0 * deltaY / deltaX);
        return newPointAngle;
    }

    /**
     * get Point By Angle
     * @param angle
     * @return
     */
    private PointF getPointByAngle(double angle) {

        PointF newPos;
        double newX = centerPoint.x + Math.cos(angle) * radius;
        double newY = (centerPoint.y) - (Math.sin(angle) * radius);
        newPos = new PointF((int) newX, (int) newY);

        return newPos;
    }

    /**
     * Set new location for passed button
     * @param currentButtonIndex
     * @param effectiveDelta
     * @param percentageCompleted
     * @return
     */
    private double updateControl(int currentButtonIndex, PointF effectiveDelta,
            double percentageCompleted) {

        PointF newPos = new PointF();
        StringBuilder s1 = new StringBuilder();

        double maxAngleForCurrentButton = buttonInfoArray[currentButtonIndex].maxAngle;
        double minAngleForCurrentButton = buttonInfoArray[currentButtonIndex].minAngle;
        double targetAngleForCurrentButton;

        if (effectiveDelta.y > 0) {
            targetAngleForCurrentButton = maxAngleForCurrentButton;
        } else {
            targetAngleForCurrentButton = minAngleForCurrentButton;
        }
        if (percentageCompleted == -1) {
            boolean isYDisplacement = effectiveDelta.y > effectiveDelta.x ? true
                    : false;

            isYDisplacement = true;
            if (isYDisplacement) {
                float newY = buttonInfoArray[currentButtonIndex].OrigPos.y
                        + effectiveDelta.y;

                if (newY > (centerPoint.y) + (int) radius) {
                    newY = (centerPoint.y) + (int) radius;
                } else if (newY < (centerPoint.y) - (int) radius) {
                    newY = (centerPoint.y) - (int) radius;
                }
                float newX = getXPos(newY);

                newPos = new PointF(newX, newY);
                s1.append("isYDisplacement true : ");

            }

        } else {

            double effectiveAngle = buttonInfoArray[currentButtonIndex].origAngle
                    + ((targetAngleForCurrentButton - buttonInfoArray[currentButtonIndex].origAngle) * percentageCompleted);

            newPos = getPointByAngle(effectiveAngle);

            s1.append("percentage completed : " + percentageCompleted + " : "
                    + effectiveAngle);
        }

        double newAngle = getNewAngle(newPos);

        // For angle, reverse condition, because in 1st quarter, it is +ve, in
        // 4th quarter, it is -ve.

        if (newAngle < maxAngleForCurrentButton) {
            newAngle = maxAngleForCurrentButton;
            newPos = getPointByAngle(newAngle);
            s1.append("max angle : " + newAngle);
        }

        if (newAngle > minAngleForCurrentButton) {
            newAngle = minAngleForCurrentButton;
            newPos = getPointByAngle(newAngle);
            s1.append("min angle : " + newAngle);
        }

        setTranslationX(buttonInfoArray[currentButtonIndex].btnObj,
                newPos.x - 50);
        setTranslationY(buttonInfoArray[currentButtonIndex].btnObj,
                newPos.y - 50);

        return newAngle;

    }

    /**
     * Set button Position
     * @param deltaPoint
     */
    public void updateButtonPos(PointF deltaPoint) {

        for (int buttonIndex = 0; buttonIndex < NUM_BUTTONS; buttonIndex++) {
            if (currentButton == buttonIndex) {

                buttonInfoArray[buttonIndex].currentAngle = updateControl(
                        buttonIndex, deltaPoint, -1);

                double targetAngleForCurrentButton;

                if (deltaPoint.y > 0) {
                    targetAngleForCurrentButton = buttonInfoArray[buttonIndex].maxAngle;
                } else {
                    targetAngleForCurrentButton = buttonInfoArray[buttonIndex].minAngle;
                }

                double percentageCompleted = (1.0 * (buttonInfoArray[buttonIndex].currentAngle - buttonInfoArray[buttonIndex].origAngle))
                        / (targetAngleForCurrentButton - buttonInfoArray[buttonIndex].origAngle);

                for (int innerButtonIndex = 0; innerButtonIndex < NUM_BUTTONS; innerButtonIndex++) {
                    if (innerButtonIndex == buttonIndex)
                        continue;
                    buttonInfoArray[innerButtonIndex].currentAngle = updateControl(
                            innerButtonIndex, deltaPoint, percentageCompleted);
                }
                break;
            }
        }
    }

    /**
     * Find whether touch in button's rectanlge or not
     * @param v
     * @param rect
     */
    private static void getHitRect(View v, Rect rect) {
        rect.left = (int) com.nineoldandroids.view.ViewHelper.getX(v);
        rect.top = (int) com.nineoldandroids.view.ViewHelper.getY(v);
        rect.right = rect.left + v.getWidth();
        rect.bottom = rect.top + v.getHeight();
    }

    private boolean isRectHit(View viewObj, MotionEvent motionEvent,
            PointF viewOrigPos) {
        Rect outRect = new Rect();


        int x = (int) motionEvent.getX();
        int y = (int) motionEvent.getY();

        getHitRect(viewObj, outRect);

        if (outRect.contains(x, y)) {
            return true;
        } else {
            return false;
        }

    }

    /**
     * On Finish update transition
     */
    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.activityfinishin, R.anim.activityfinishout);
    }

    /**
     * On Native Back Pressed
     */
    @Override
    public void onBackPressed() {
        super.onBackPressed();
        finish();
    }   

}