我使用
绘制了画布上的立方曲线 myPath.cubicTo(10, 10, w, h/2, 10, h-10);
我在该屏幕上有四个ImageView,当我用触摸拖动图像时,我想在绘制的曲线上移动ImageViews。
我已经提到了链接:
我得到的是,动画在曲线上移动图像的持续时间由t定义。 但我想仅在该曲线区域的方向上移动该ImageView。
以下是我的屏幕:
所以,我希望曲线的所有(x,y) co-ordinates
仅在该曲线上移动ImageView。
否则我想要一个等式绘制曲线,以便我可以为触摸的y值插入x值。
我已经瞪了很多但没有成功。 任何建议或指导都会对我有所帮助。
答案 0 :(得分:5)
我建议采用与bezier不同的方法,因为你需要为它重现数学以获得位置。
通过使用简单的三角法,您可以获得相同的视觉效果,但另外可以完全控制位置。
例如:
THIS ONLINE DEMO 会产生此结果(为了演示而简化版本):
使用圆和角度位置定义一个数组,而不是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();
}
}