【发布时间】:2014-08-07 12:15:44
【问题描述】:
我需要创建一系列同心椭圆(环),并且需要在这些椭圆的圆周上放置用户图标。见下图。
到目前为止,我已经在画布上绘制了 3 个椭圆同心圆并放置了用户图标。 我需要用户图标可以在环上拖动。
请提出实施方法。
【问题讨论】:
-
psot 正确的代码
标签: android android-canvas android-custom-view
我需要创建一系列同心椭圆(环),并且需要在这些椭圆的圆周上放置用户图标。见下图。
到目前为止,我已经在画布上绘制了 3 个椭圆同心圆并放置了用户图标。 我需要用户图标可以在环上拖动。
请提出实施方法。
【问题讨论】:
标签: android android-canvas android-custom-view
由于您似乎已经在环的圆周上放置了图标,我假设您知道如何进行数学运算 [但请参阅编辑] 以确定沿圆周的点并询问拖放.
您可能希望使用拖放方法来实现图标移动。假设您将戒指保留为单个图像,那么您将只有一个放置目的地。然后,您将需要以数学方式分析放置点 [参见编辑](通过确定其像素颜色)以确定图标被放置到哪个环中。如果您为环创建单独的视图,那么每个视图都可以是它自己的放置点。 (您可能最终需要弄清楚如何重新分配每个环中的图标,但这是一个不同的问题。)
这里的一些代码显示了使用单个视图组(您将在其中显示环图像)上的单个图像图标处理拖放的最小方法。
package com.example.dragexample;
import android.app.Activity;
import android.content.ClipData;
import android.os.Bundle;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnDragListener;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class MainActivity extends Activity {
static final String TAG = "DragActivity";
ImageView icon = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.rings).setOnDragListener(new OnDragListener() {
@Override
public boolean onDrag(View vw, DragEvent event) {
if (event.getAction() == DragEvent.ACTION_DROP) {
// Drop the icon and redisplay it:
icon.setX(event.getX());
icon.setY(event.getY());
icon.setVisibility(View.VISIBLE);
// Analyze the drop point mathematically (or perhaps get its pixel color)
// to determine which ring the icon has been dragged into and then take
// appropriate action.
int destRing = determineDestinationRing(event.getX(), event.getY());
}
return true;
}
});
icon = (ImageView) findViewById(R.id.icon);
icon.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View vw, MotionEvent event) {
Log.v(TAG, "Touch event " + event.getAction());
if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
Log.v(TAG, "Starting drag");
// Set up clip data (empty) and drag shadow objects and start dragging:
ClipData cd = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(vw);
vw.startDrag(cd, shadowBuilder, vw, 0);
vw.setVisibility(View.INVISIBLE);
}
return true;
}
});
}
public void resetImage(View vw) {
Log.v(TAG, "Resetting image position");
icon.setX(0f);
icon.setY(0f);
icon.setVisibility(View.VISIBLE);
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="resetImage" >
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
</FrameLayout>
要在数学上确定图标落入哪个环,您可以使用类似以下的方法,它使用硬编码的轴大小循环执行椭圆的标准方程。请注意,如果将图标放在最里面的“我”环中,则将返回零。此外,这种方法在实践中会更具挑战性,因为在渲染布局时可能会调整屏幕上的环大小。在这种情况下,需要在运行时确定轴的最终尺寸。
// Axis values must be ascending order; ring 0 is 'me';
float a[] = {50, 100, 150, 200};
float b[] = {100, 200, 300, 400};
public int determineDestinationRing(float x, float y) {
// Check for inclusion within each ring:
for (int i = 0; i < a.length; i++) {
if (((x * x) / (a[i] * a[i]) + (y * y) / (b[i] * b[i])) <= 1)
return i;
}
return -1;
}
【讨论】:
要解决这个问题,需要用到椭圆方程:
(x/a)2 + (y/a)2 = 1 ,
在哪里:
x,y 是椭圆圆周上任意点的坐标
a,b 分别是 x 和 y 轴上的半径。
当用户拖放图标时,如果图标的中心坐标与椭圆圆周相交,那么上面的公式应该成立。在这种情况下,您可以将图标放在椭圆上。由于您有 3 个椭圆,因此您必须多次检查,其他 2 个椭圆各一次。
public class CustomView extends View {
// Other methods
public boolean onTouchEvent(MotionEvent e) {
int index = e.getActionIndex();
float x = e.getX(index);
float y = e.getY(index);
int a = this.getWidth()/2;
int b = this.getHeight()/2;
// x-a instead of x and y-b instead of y, to get the offset from centre of ellipse.
double result = Math.pow(((x-a)/a), 2) + Math.pow(((y-b)/b), 2);
Log.v(TAG, "(" + (x-a) + "/" + a + ")2 + (" + (y-b) + "/" + b + ")2 = " + result);
return true;
}
}
【讨论】:
我会这样做。首先,我们需要检测在TOUCH_DOWN 事件上触摸了哪个图标。这可以通过比较触摸点和图标坐标来简单地完成。一旦找到最接近的图标,我们还应该知道这个图标属于哪个椭圆,这意味着我们知道这个椭圆的水平和垂直半径。我们也知道椭圆中心的坐标。
然后,这就是我们要做什么的想法。我们将计算通过触摸点和椭圆中心绘制的线与通过中心绘制的水平线之间的角度。
o <- touch point
/
/ \ <- angle
center-> o----- <- horizontal line
现在,在知道了角度之后,我们将使用调整为椭圆的圆方程,重新计算新图标的坐标,使我们的图标准确地贴在那个椭圆上。让我们进入算法。
为了检测触摸角度,我们将使用atan2函数。
double atan2Angle = Math.atan2(touchY - centerY, touchX - centerX);
这将根据角度为我们提供以下值。
-π
-2*π -0
o <- center
2*π 0
π
为了能够在圆方程中使用这个角度,我们需要将其转换为更传统的表示形式,如下所示。
270°
180° o 0°/360°
90°
可以这样。
float angleFactor = (float) (atan2Angle / 2 * Math.PI);
if (angleFactor < 0) {
angleFactor += 1f;
}
float touchAngle = angleFactor * 360f;
if (angle < 0) {
angle += 360f;
}
现在,在我们得到以度为单位的触摸角度之后,我们可以使用椭圆方程计算图标的新坐标。
double touchAngleRad = Math.toRadians(touchAngle);
float iconX = centerX + (float) (radiusX * Math.cos(touchAngleRad));
float iconY = centerY + (float) (radiusY * Math.sin(touchAngleRad));
// centerX, centerY - coordinates of the center of ellipses
// radiusX, radiusY - horizontal and vertical radiuses of the ellipse, to which
// the touched icon belongs
// iconX, iconY - new coordinates of the icon lying on that ellipse
如果您根据此算法在每个TOUCH_MOVE 上重新计算图标位置并使视图无效,那么您的图标将在其椭圆上移动。
可以通过使用弧度而不是度数来进一步优化代码,但我认为度数更适合解释。希望这有帮助。如果您在实施过程中遇到任何问题,请发布您的代码。
【讨论】: