像罗盘一样旋转ImageView(其他地方设置“北极”)

对于如何实现一个“个人指南针”,即一个指向一个具体的方位而不是标准的“北极”的指南针,我感到难以置信……不幸的是,我目前的尝试出现了错误(不指向给定轴承)。 它也与加速器连接起来,可以根据用户转向的方式来dynamic调整自己。

这是我目前的尝试(更新箭头的onSensorChanged()方法):

 public void onSensorChanged( SensorEvent event ) { // If we don't have a Location, we break out if ( LocationObj == null ) return; float azimuth = event.values[0]; float baseAzimuth = azimuth; GeomagneticField geoField = new GeomagneticField( Double .valueOf( LocationObj.getLatitude() ).floatValue(), Double .valueOf( LocationObj.getLongitude() ).floatValue(), Double.valueOf( LocationObj.getAltitude() ).floatValue(), System.currentTimeMillis() ); azimuth += geoField.getDeclination(); // converts magnetic north into true north //Correct the azimuth azimuth = azimuth % 360; //This is where we choose to point it float direction = azimuth + LocationObj.bearingTo( destinationObj ); rotateImageView( arrow, R.drawable.arrow, direction ); //Set the field if( baseAzimuth > 0 && baseAzimuth < 45 ) fieldBearing.setText("S"); else if( baseAzimuth >= 45 && baseAzimuth < 90 ) fieldBearing.setText("SW"); else if( baseAzimuth > 0 && baseAzimuth < 135 ) fieldBearing.setText("W"); else if( baseAzimuth > 0 && baseAzimuth < 180 ) fieldBearing.setText("NW"); else if( baseAzimuth > 0 && baseAzimuth < 225 ) fieldBearing.setText("N"); else if( baseAzimuth > 0 && baseAzimuth < 270 ) fieldBearing.setText("NE"); else if( baseAzimuth > 0 && baseAzimuth < 315 ) fieldBearing.setText("E"); else if( baseAzimuth > 0 && baseAzimuth < 360 ) fieldBearing.setText("SE"); else fieldBearing.setText("?"); } 

这里是旋转ImageView( rotateImageView() )的方法:

 private void rotateImageView( ImageView imageView, int drawable, float rotate ) { // Decode the drawable into a bitmap Bitmap bitmapOrg = BitmapFactory.decodeResource( getResources(), drawable ); // Get the width/height of the drawable DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); int width = bitmapOrg.getWidth(), height = bitmapOrg.getHeight(); // Initialize a new Matrix Matrix matrix = new Matrix(); // Decide on how much to rotate rotate = rotate % 360; // Actually rotate the image matrix.postRotate( rotate, width, height ); // recreate the new Bitmap via a couple conditions Bitmap rotatedBitmap = Bitmap.createBitmap( bitmapOrg, 0, 0, width, height, matrix, true ); //BitmapDrawable bmd = new BitmapDrawable( rotatedBitmap ); //imageView.setImageBitmap( rotatedBitmap ); imageView.setImageDrawable(new BitmapDrawable(getResources(), rotatedBitmap)); imageView.setScaleType( ScaleType.CENTER ); } 

任何帮助将不胜感激,因为我不知道如何进行。 我在尝试时得到的“读数”有些不准确,并指向了错误的方向。 我是在做一些真正的事情,还是只是做了一个非常糟糕的testing?

Solutions Collecting From Web of "像罗盘一样旋转ImageView(其他地方设置“北极”)"

你的rotateImageView函数应该可以正常工作,但是在你的旋转计算中有一些东西需要改变。

 //This is where we choose to point it float direction = azimuth + LocationObj.bearingTo( destinationObj ); rotateImageView( arrow, R.drawable.arrow, direction ); 

问题是bearingTo会给你一个从-180到180的范围,这会使事情混淆一点。 我们将需要将这个值转换成从0到360的范围以获得正确的旋转。

这是我们真正想要的东西,比较什么方向给我们

 + ----------- -------------- + +
 |  bearingTo | 真正的轴承|
 + ----------- -------------- + +
 |  0 |  0 |
 + ----------- -------------- + +
 |  90 |  90 |
 + ----------- -------------- + +
 |  180 |  180 |
 + ----------- -------------- + +
 |  -90 |  270 |
 + ----------- -------------- + +
 |  -135 |  225 |
 + ----------- -------------- + +
 |  -180 |  180 |
 + ----------- -------------- + +

即使bearingTo在-180到180之间,0仍然是正确的,这将使我们进行这样的计算:

 // Store the bearingTo in the bearTo variable float bearTo = LocationObj.bearingTo( destinationObj ); // If the bearTo is smaller than 0, add 360 to get the rotation clockwise. if (bearTo < 0) { bearTo = bearTo + 360; } 

如果我们添加一些虚拟值来testing我们的新公式:

 float bearTo = -100; // This will now equal to true if (-100 < 0) { bearTo = -100 + 360 = 360 - 100 = 260; } 

现在我们已经把轴承整理好了,让我们来看方位!

你需要减去赤纬而不是增加赤纬,因为当我们将电话直接指向正北时,我们希望方位angular为0,而不是将方位angular加到方位angular上,这样当我们指向电话时,赤纬就会增加一倍真正的北方。 通过减去偏angular而不是增加它来纠正这个问题。

 azimuth -= geoField.getDeclination(); // converts magnetic north into true north 

当我们现在将手机转向真北方位时,方位angular将等于0

您的用于更正方位angular的代码不再需要。

 // Remove / uncomment this line azimuth = azimuth % 360; 

我们现在将继续到我们计算真实旋转的点。 但是,首先我要总结一下我们现在有什么样的价值观,并解释他们真的是什么:

bearTo =从我们目前站立的angular度,从真北处到目的地的angular度。

azimuth =您将手机从真北向旋转的angular度。

这样说的话,如果你直接把手机指向正北,我们真的要把箭头旋转到被设定的angular度。 如果将手机指向正北方45度,我们希望箭头的旋转angular度比bearTo小45度。 这让我们进行以下计算:

 float direction = bearTo - azimuth; 

但是,如果我们join一些虚拟值:bearTo = 45; 方位angular= 180;

 direction = 45 - 180 = -135; 

这意味着箭头应该逆时针旋转135度。 我们将需要像对待熊一样进行一个类似的条件!

 // If the direction is smaller than 0, add 360 to get the rotation clockwise. if (direction < 0) { direction = direction + 360; } 

你的方位文本,N,E,S和W是closures的,所以我在下面的最后一个方法中纠正了它们。

你的onSensorChanged方法应该如下所示:

 public void onSensorChanged( SensorEvent event ) { // If we don't have a Location, we break out if ( LocationObj == null ) return; float azimuth = event.values[0]; float baseAzimuth = azimuth; GeomagneticField geoField = new GeomagneticField( Double .valueOf( LocationObj.getLatitude() ).floatValue(), Double .valueOf( LocationObj.getLongitude() ).floatValue(), Double.valueOf( LocationObj.getAltitude() ).floatValue(), System.currentTimeMillis() ); azimuth -= geoField.getDeclination(); // converts magnetic north into true north // Store the bearingTo in the bearTo variable float bearTo = LocationObj.bearingTo( destinationObj ); // If the bearTo is smaller than 0, add 360 to get the rotation clockwise. if (bearTo < 0) { bearTo = bearTo + 360; } //This is where we choose to point it float direction = bearTo - azimuth; // If the direction is smaller than 0, add 360 to get the rotation clockwise. if (direction < 0) { direction = direction + 360; } rotateImageView( arrow, R.drawable.arrow, direction ); //Set the field String bearingText = "N"; if ( (360 >= baseAzimuth && baseAzimuth >= 337.5) || (0 <= baseAzimuth && baseAzimuth <= 22.5) ) bearingText = "N"; else if (baseAzimuth > 22.5 && baseAzimuth < 67.5) bearingText = "NE"; else if (baseAzimuth >= 67.5 && baseAzimuth <= 112.5) bearingText = "E"; else if (baseAzimuth > 112.5 && baseAzimuth < 157.5) bearingText = "SE"; else if (baseAzimuth >= 157.5 && baseAzimuth <= 202.5) bearingText = "S"; else if (baseAzimuth > 202.5 && baseAzimuth < 247.5) bearingText = "SW"; else if (baseAzimuth >= 247.5 && baseAzimuth <= 292.5) bearingText = "W"; else if (baseAzimuth > 292.5 && baseAzimuth < 337.5) bearingText = "NW"; else bearingText = "?"; fieldBearing.setText(bearingText); } 

您应该能够将matrix设置为ImageView,而不必每次都重新创build位图,而呃“正常化”(就是那个单词?)读数。

 float b = mLoc.getBearing(); if(b < 0) b = 360 + b; float h = item.mHeading; if(h < 0) h = 360 + h; float r = (h - b) - 360; matrix.reset(); matrix.postRotate(r, width/2, height/2); 

在上面的例子中,mLoc是gps提供者返回的位置,getBearing返回当前旅行方向以北的度数。 item.mHeading已使用Location.bearingTo()函数使用mLoc和项目的位置进行计算。 宽度和高度是图像视图的尺寸。

所以,确保你的variables是度数而不是弧度,然后尝试“正常化”(把标题变成0-360而不是-180-180)。 另外,如果结果偏离了180度,那么确保你的目标达到了目标,而不是从你的目标到你的目标。

上面的matrix然后可以在一个ImageView中设置一个ScaleType.Matrix

 imageView.setMatrix(matrix); imageview.setScaleType(ScaleType.Matrix); 

由于你正在旋转imageView的中心点(postRotate中的width / 2,height / 2),所以你的drawable应该是向上的,并且会在绘制时旋转,而不是每次都重新创build一个新的bitmap 。

我一个周末花了大约40个小时来尝试这样做。

痛苦的屁股,希望我能免除你的痛苦。

好的,我警告你,这是一些丑陋的代码。 我正在勉强完成它,它没有命名scheme,但我尽可能为你评论它。

它被用来定位大堆的坚果堆放在田地里储存

使用手机当前的经纬度,目的地的纬度/经度,指南针传感器和一些代数,我能够计算到目的地的方向。

Lat / lon和传感器读数从MainApplication类中提取

这是arrow.class的一些代码,我用它在canvas上向一个方向绘制一个箭头。

  //The location you want to go to// //"Given North" double lat=0; double lon=0; ////////////////////////////////// protected void onDraw(Canvas canvas) { //Sensor values from another class managing Sensor float[] v = MainApplication.getValues(); //The current location of the device, retrieved from another class managing GPS double ourlat= MainApplication.getLatitudeD(); double ourlon= MainApplication.getLongitudeD(); //Manually calculate the direction of the pile from the device double a= Math.abs((lon-ourlon)); double b= Math.abs((lat-ourlat)); //archtangent of a/b is equal to the angle of the device from 0-degrees in the first quadrant. (Think of a unit circle) double thetaprime= Math.atan(a/b); double theta= 0; //Determine the 'quadrant' that the desired location is in //ASTC (All, Sin, Tan, Cos) Determines which value is positive //Gotta love Highschool algebra if((lat<ourlat)&&(lon>ourlon)){//-+ //theta is 180-thetaprime because it is in the 2nd quadrant theta= ((Math.PI)-thetaprime); //subtract theta from the compass value retrieved from the sensor to get our final direction theta=theta - Math.toRadians(v[0]); }else if((lat<ourlat)&&(lon<ourlon)){//-- //Add 180 degrees because it is in the third quadrant theta= ((Math.PI)+thetaprime); //subtract theta from the compass value retreived from the sensor to get our final direction theta=theta - Math.toRadians(v[0]); }else if((lat>ourlat)&&(lon>ourlon)){ //++ //No change is needed in the first quadrant theta= thetaprime; //subtract theta from the compass value retreived from the sensor to get our final direction theta=theta - Math.toRadians(v[0]); }else if((lat>ourlat)&&(lon<ourlon)){ //+- //Subtract thetaprime from 360 in the fourth quadrant theta= ((Math.PI*2)-thetaprime); //subtract theta from the compass value retreived from the sensor to get our final direction theta=theta - Math.toRadians(v[0]); } canvas.drawBitmap(_bitmap, 0, 0, paint); float[] results = {0}; //Store data Location.distanceBetween(ourlat, ourlon, lat, lon, results); try{ //Note, pileboundary is a value retreived from a database //This changes the color of the canvas based upon how close you are to the destination //Green < 100 (or database value), Yellow < (100)*2, Otherwise red if((results[0])<(pileboundary==0?100:pileboundary)){ _canvas.drawColor(Color.GREEN); }else if((results[0])<(pileboundary==0?100:pileboundary)*2){ _canvas.drawColor(Color.YELLOW); }else{ _canvas.drawColor(Color.rgb(0xff, 113, 116)); //RED-ish } //Draw the distance(in feet) from the destination canvas.drawText("Distance: "+Integer.toString((int) (results[0]*3.2808399))+ " Feet", 3, height-3, textpaint); }catch(IllegalArgumentException ex){ //im a sloppy coder } int w = canvas.getWidth(); int h = height; int x = w / 2; //put arrow in center int y = h / 2; canvas.translate(x, y); if (v != null) { // Finally, we rotate the canvas to the desired direction canvas.rotate((float)Math.toDegrees(theta)); } //Draw the arrow! canvas.drawPath(thearrow, paint); } //Some of my declarations, once again sorry :P GeomagneticField gf; Bitmap _bitmap; Canvas _canvas; int _height; int _width; Bitmap b; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Get the current GeomagneticField (Should be valid until 2016, according to android docs) gf = new GeomagneticField((float)lat,(float)lon,(float)MainApplication.getAltitude(),System.currentTimeMillis()); _height = View.MeasureSpec.getSize(heightMeasureSpec); _width = View.MeasureSpec.getSize(widthMeasureSpec); setMeasuredDimension(_width, _height); _bitmap = Bitmap.createBitmap(_width, _height, Bitmap.Config.ARGB_8888); _canvas = new Canvas(_bitmap); b=Bitmap.createBitmap(_bitmap); drawBoard(); invalidate(); } //Here is the code to draw the arrow thearrow.moveTo(0, -50); thearrow.lineTo(-20, 50); thearrow.lineTo(0, 50); thearrow.lineTo(20, 50); thearrow.close(); thearrow.setFillType(FillType.EVEN_ODD); 

希望你可以设法阅读我的代码…如果我得到时间,我会使它更漂亮一点。

如果你需要任何解释,让我知道。

-MrZander