Androïde rotation de deux doigts
j'essaie de mettre en œuvre deux doigts rotation dans android cependant, il ne fonctionne pas tout à fait comme prévu. Le but est de mettre en œuvre la rotation comme le fait Google Earth (rotation à deux doigts de l'image autour du point focal). Actuellement, mon auditeur de rotation ressemble à ceci:
private class RotationGestureListener {
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY, focalX, focalY;
private int ptrID1, ptrID2;
public RotationGestureListener(){
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
sX = event.getX();
sY = event.getY();
ptrID1 = event.getPointerId(0);
break;
case MotionEvent.ACTION_POINTER_DOWN:
fX = event.getX();
fY = event.getY();
focalX = getMidpoint(fX, sX);
focalY = getMidpoint(fY, sY);
ptrID2 = event.getPointerId(event.getActionIndex());
break;
case MotionEvent.ACTION_MOVE:
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){
float nfX, nfY, nsX, nsY;
nfX = event.getX(event.findPointerIndex(ptrID1));
nfY = event.getY(event.findPointerIndex(ptrID1));
nsX = event.getX(event.findPointerIndex(ptrID2));
nsY = event.getY(event.findPointerIndex(ptrID2));
float angle = angleBtwLines(fX, fY, nfX, nfY, sX, sY, nsX, nsY);
rotateImage(angle, focalX, focalY);
fX = nfX;
fY = nfY;
sX = nfX;
sY = nfY;
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
}
return false;
}
private float getMidpoint(float a, float b){
return (a + b) / 2;
}
private float angleBtwLines (float fx1, float fy1, float fx2, float fy2, float sx1, float sy1, float sx2, float sy2){
float angle1 = (float) Math.atan2(fy1 - fy2, fx1 - fx2);
float angle2 = (float) Math.atan2(sy1 - sy2, sx1 - sx2);
return (float) Math.toDegrees((angle1-angle2));
}
}
Cependant chaque fois que je tourne l'angle de rotation est beaucoup plus grand et il tourne parfois du mauvais côté. Toutes les idées sur la façon de résoudre ce problème?
soit dit en passant Je le teste sur une Atrix de Motorola, donc il n'a pas le bug de l'écran tactile.
Merci
6 réponses
améliorations de la classe:
- angle retourné total depuis le début de la rotation
- suppression des fonctions inutiles
- simplification
- obtenir la position de premier pointeur seulement après la deuxième pointeur qui est en bas
public class RotationGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY;
private int ptrID1, ptrID2;
private float mAngle;
private OnRotationGestureListener mListener;
public float getAngle() {
return mAngle;
}
public RotationGestureDetector(OnRotationGestureListener listener){
mListener = listener;
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ptrID1 = event.getPointerId(event.getActionIndex());
break;
case MotionEvent.ACTION_POINTER_DOWN:
ptrID2 = event.getPointerId(event.getActionIndex());
sX = event.getX(event.findPointerIndex(ptrID1));
sY = event.getY(event.findPointerIndex(ptrID1));
fX = event.getX(event.findPointerIndex(ptrID2));
fY = event.getY(event.findPointerIndex(ptrID2));
break;
case MotionEvent.ACTION_MOVE:
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){
float nfX, nfY, nsX, nsY;
nsX = event.getX(event.findPointerIndex(ptrID1));
nsY = event.getY(event.findPointerIndex(ptrID1));
nfX = event.getX(event.findPointerIndex(ptrID2));
nfY = event.getY(event.findPointerIndex(ptrID2));
mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
if (mListener != null) {
mListener.OnRotation(this);
}
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_CANCEL:
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
private float angleBetweenLines (float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY)
{
float angle1 = (float) Math.atan2( (fY - sY), (fX - sX) );
float angle2 = (float) Math.atan2( (nfY - nsY), (nfX - nsX) );
float angle = ((float)Math.toDegrees(angle1 - angle2)) % 360;
if (angle < -180.f) angle += 360.0f;
if (angle > 180.f) angle -= 360.0f;
return angle;
}
public static interface OnRotationGestureListener {
public void OnRotation(RotationGestureDetector rotationDetector);
}
}
comment l'utiliser:
- mettez la classe ci-dessus dans un fichier séparé
RotationGestureDetector.java
- créer un champ privé
mRotationDetector
de typeRotationGestureDetector
dans votre classe d'activité et créer une nouvelle instance du détecteur lors de l'initialisation (méthodeonCreate
par exemple) et donner comme paramètre une classe implémentant la méthodeonRotation
(ici la méthodeactivity = this
). - dans la méthode
onTouchEvent
, envoyer les événements de toucher reçus au détecteur de gestes avecmRotationDetector.onTouchEvent(event);
- Instruments
RotationGestureDetector.OnRotationGestureListener
dans votre activité et ajouter la méthode 'public void OnRotation(RotationGestureDetector rotationDetector)
" dans l'activité. Dans cette méthode, obtenir l'angle avecrotationDetector.getAngle()
exemple:
public class MyActivity extends Activity implements RotationGestureDetector.OnRotationGestureListener {
private RotationGestureDetector mRotationDetector;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRotationDetector = new RotationGestureDetector(this);
}
@Override
public boolean onTouchEvent(MotionEvent event){
mRotationDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
@Override
public void OnRotation(RotationGestureDetector rotationDetector) {
float angle = rotationDetector.getAngle();
Log.d("RotationGestureDetector", "Rotation: " + Float.toString(angle));
}
}
Note:
vous pouvez également utiliser la classe RotationGestureDetector
dans un View
au lieu d'un Activity
.
voici mon amélioration sur la réponse de Leszek. J'ai trouvé que le sien ne fonctionnait pas pour les petites vues comme quand un contact est sorti de la vue le calcul de l'angle était erroné. La solution est d'obtenir l'emplacement brut au lieu de simplement getX/Y.
Crédit ce fil pour obtenir les cru de points sur une rotative vue.
public class RotationGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private PointF mFPoint = new PointF();
private PointF mSPoint = new PointF();
private int mPtrID1, mPtrID2;
private float mAngle;
private View mView;
private OnRotationGestureListener mListener;
public float getAngle() {
return mAngle;
}
public RotationGestureDetector(OnRotationGestureListener listener, View v) {
mListener = listener;
mView = v;
mPtrID1 = INVALID_POINTER_ID;
mPtrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_OUTSIDE:
Log.d(this, "ACTION_OUTSIDE");
break;
case MotionEvent.ACTION_DOWN:
Log.v(this, "ACTION_DOWN");
mPtrID1 = event.getPointerId(event.getActionIndex());
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.v(this, "ACTION_POINTER_DOWN");
mPtrID2 = event.getPointerId(event.getActionIndex());
getRawPoint(event, mPtrID1, mSPoint);
getRawPoint(event, mPtrID2, mFPoint);
break;
case MotionEvent.ACTION_MOVE:
if (mPtrID1 != INVALID_POINTER_ID && mPtrID2 != INVALID_POINTER_ID){
PointF nfPoint = new PointF();
PointF nsPoint = new PointF();
getRawPoint(event, mPtrID1, nsPoint);
getRawPoint(event, mPtrID2, nfPoint);
mAngle = angleBetweenLines(mFPoint, mSPoint, nfPoint, nsPoint);
if (mListener != null) {
mListener.onRotation(this);
}
}
break;
case MotionEvent.ACTION_UP:
mPtrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
mPtrID2 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_CANCEL:
mPtrID1 = INVALID_POINTER_ID;
mPtrID2 = INVALID_POINTER_ID;
break;
default:
break;
}
return true;
}
void getRawPoint(MotionEvent ev, int index, PointF point){
final int[] location = { 0, 0 };
mView.getLocationOnScreen(location);
float x = ev.getX(index);
float y = ev.getY(index);
double angle = Math.toDegrees(Math.atan2(y, x));
angle += mView.getRotation();
final float length = PointF.length(x, y);
x = (float) (length * Math.cos(Math.toRadians(angle))) + location[0];
y = (float) (length * Math.sin(Math.toRadians(angle))) + location[1];
point.set(x, y);
}
private float angleBetweenLines(PointF fPoint, PointF sPoint, PointF nFpoint, PointF nSpoint)
{
float angle1 = (float) Math.atan2((fPoint.y - sPoint.y), (fPoint.x - sPoint.x));
float angle2 = (float) Math.atan2((nFpoint.y - nSpoint.y), (nFpoint.x - nSpoint.x));
float angle = ((float) Math.toDegrees(angle1 - angle2)) % 360;
if (angle < -180.f) angle += 360.0f;
if (angle > 180.f) angle -= 360.0f;
return -angle;
}
public interface OnRotationGestureListener {
void onRotation(RotationGestureDetector rotationDetector);
}
}
j'ai essayé une combinaison de réponses qui sont ici, mais il ne fonctionne toujours pas parfaitement donc j'ai dû modifier un peu.
ce code vous donne l'angle delta sur chaque rotation, il fonctionne parfaitement pour moi, je l'utilise pour faire tourner un objet en OpenGL.
public class RotationGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY, focalX, focalY;
private int ptrID1, ptrID2;
private float mAngle;
private boolean firstTouch;
private OnRotationGestureListener mListener;
public float getAngle() {
return mAngle;
}
public RotationGestureDetector(OnRotationGestureListener listener){
mListener = listener;
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
sX = event.getX();
sY = event.getY();
ptrID1 = event.getPointerId(0);
mAngle = 0;
firstTouch = true;
break;
case MotionEvent.ACTION_POINTER_DOWN:
fX = event.getX();
fY = event.getY();
focalX = getMidpoint(fX, sX);
focalY = getMidpoint(fY, sY);
ptrID2 = event.getPointerId(event.getActionIndex());
mAngle = 0;
firstTouch = true;
break;
case MotionEvent.ACTION_MOVE:
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID){
float nfX, nfY, nsX, nsY;
nsX = event.getX(event.findPointerIndex(ptrID1));
nsY = event.getY(event.findPointerIndex(ptrID1));
nfX = event.getX(event.findPointerIndex(ptrID2));
nfY = event.getY(event.findPointerIndex(ptrID2));
if (firstTouch) {
mAngle = 0;
firstTouch = false;
} else {
mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
}
if (mListener != null) {
mListener.OnRotation(this);
}
fX = nfX;
fY = nfY;
sX = nsX;
sY = nsY;
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
private float getMidpoint(float a, float b){
return (a + b) / 2;
}
float findAngleDelta( float angle1, float angle2 )
{
float From = ClipAngleTo0_360( angle2 );
float To = ClipAngleTo0_360( angle1 );
float Dist = To - From;
if ( Dist < -180.0f )
{
Dist += 360.0f;
}
else if ( Dist > 180.0f )
{
Dist -= 360.0f;
}
return Dist;
}
float ClipAngleTo0_360( float Angle ) {
return Angle % 360.0f;
}
private float angleBetweenLines (float fx1, float fy1, float fx2, float fy2, float sx1, float sy1, float sx2, float sy2)
{
float angle1 = (float) Math.atan2( (fy1 - fy2), (fx1 - fx2) );
float angle2 = (float) Math.atan2( (sy1 - sy2), (sx1 - sx2) );
return findAngleDelta((float)Math.toDegrees(angle1),(float)Math.toDegrees(angle2));
}
public static interface OnRotationGestureListener {
public boolean OnRotation(RotationGestureDetector rotationDetector);
}
}
Il y a encore quelques erreurs, voici la solution qui a fonctionné parfait pour moi...
au lieu de
float angle = angleBtwLines(fX, fY, nfX, nfY, sX, sY, nsX, nsY);
vous devez écrire
float angle = angleBtwLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
et angleBetweenLines devrait être
private float angleBetweenLines (float fx1, float fy1, float fx2, float fy2, float sx1, float sy1, float sx2, float sy2)
{
float angle1 = (float) Math.atan2( (fy1 - fy2), (fx1 - fx2) );
float angle2 = (float) Math.atan2( (sy1 - sy2), (sx1 - sx2) );
return findAngleDelta((float)Math.toDegrees(angle1),(float)Math.toDegrees(angle2));
}
, Puis l'angle que vous obtenez est l'angle que vous devez faire pivoter l'image...
ImageAngle += angle...
vous avez un problème ici:
private float angleBtwLines (float fx1, float fy1, float fx2, float fy2, float sx1, float sy1, float sx2, float sy2){
float angle1 = (float) Math.atan2(fy1 - fy2, fx1 - fx2);
float angle2 = (float) Math.atan2(sy1 - sy2, sx1 - sx2);
return (float) Math.toDegrees((angle1-angle2));
}
vous devez clip Les angles à la [0..2 * Pi] et de calculer soigneusement la différence angulaire dans le (- Pi..+ Pi) portée.
voici le code pour 0..Plage d'angle de 360 °
float FindAngleDelta( float angle1, float angle2 )
{
float From = ClipAngleTo0_360( angle2 );
float To = ClipAngleTo0_360( angle1 );
float Dist = To - From;
if ( Dist < -180.0f )
{
Dist += 360.0f;
}
else if ( Dist > 180.0f )
{
Dist -= 360.0f;
}
return Dist;
}
en C++ Je coderais le ClipAngleTo0_360 comme
float ClipAngleTo0_360( float Angle ) { return std::fmod( Angle, 360.0f ); }
où le std:: fmod renvoie le reste du flottant.
en Java you peut utiliser quelque chose comme
float ClipAngleTo0_360( float Angle )
{
float Res = Angle;
while(Angle < 0) { Angle += 360.0; }
while(Angle >= 360.0) { Angle -= 360.0; }
return Res;
}
oui, une arithmétique flottante prudente est bien meilleure que la boucle while() évidente.
comme MeTTeO l'a mentionné (référence java, 15.17.3), vous pouvez utiliser l'opérateur '%' au lieu de C++'s std:: fmod:
float ClipAngleTo0_360( float Angle ) { return Angle % 360.0; }
j'ai essayé beaucoup d'exemple. Mais seulement ce bon travail.:
public class RotationGestureDetector {
public interface RotationListener {
public void onRotate(float deltaAngle);
}
protected float mRotation;
private RotationListener mListener;
public RotationGestureDetector(RotationListener listener) {
mListener = listener;
}
private float rotation(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);
}
public void onTouch(MotionEvent e) {
if (e.getPointerCount() != 2)
return;
if (e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
mRotation = rotation(e);
}
float rotation = rotation(e);
float delta = rotation - mRotation;
mRotation += delta;
mListener.onRotate(delta);
}
}
dans votre rappel:
view.setRotation(view.getRotetion() -deltaAndle));