xamarin forms image zoom and scroll - image

I am using this pinchGestureRecognizer in order to zoom on an image, but the problem is that after zooming I am not able to scroll the image vertically, even though I've wrapped my image in a scrollView with Orientation="Both"
Here is the PinchToZoomContainer class:
public class PinchToZoomContainer : ContentView
{
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
public PinchToZoomContainer()
{
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
This the Clamp method:
public static class DoubleExtensions
{
public static double Clamp(this double self, double min, double max)
{
return Math.Min(max, Math.Max(self, min));
}
}
And this is the image in my XAML file:
<StackLayout Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Grid.RowSpan="3">
<ScrollView x:Name="imageScroll" Orientation="Both" VerticalOptions="FillAndExpand">
<entry:PinchToZoomContainer>
<entry:PinchToZoomContainer.Content>
<Image x:Name="zoomImage" Source="{images:EmbeddedImage Vebko.Images.CB_Edite_Vb1_2.jpg}" Margin="0,0,0,10" HorizontalOptions="Center" VerticalOptions="Center"/>
</entry:PinchToZoomContainer.Content>
</entry:PinchToZoomContainer>
</ScrollView>
</StackLayout>
Can anybody please help?

What i guess is that while you scale the Content, the PinchToZoomContainer : ContentView size remains the same that is why scrollview doesn't react = change control's size with height/width requests in code when its content gets scaled.

Related

Tried to make 3 moving parts of a screensaver in Processing, got one super fast part. Any ideas?

I created a screensaver that bounces around the screen. I tried to fit original the code into OOP (which I'm terrible at) so that I could have multiple versions of it bouncing around. Instead, I'm getting one instance travelling at an unusually fast speed.
How can I get instance1, instance2 and instance3 to be seperate instead of combining into one?
Notes:
In the class screenSavers, C stands for constructor parameter to differentiate from the original variables at the top. CS stands for class-specific to avoid confusion with the original variables at the top. (yes, it's confusing. I don't know why I did that)
GIANT WALL OF CODE ALERT
import java.lang.Math;
//defining dimensions and initial coords for the laptop frame
float frameW = 120;
float frameH = frameW * 3.0/4.0;
float frameX = random(frameW * 2, width);
float frameY = random(frameW * 2, height);
//defining variables are used in the movement of the laptop
float xSpeed = random(frameW / 50,frameH / 15);
float ySpeed = random(2,5);
boolean moveLeft = false;
boolean moveUp = false;
//defining the colours used in the program, as well as the alpha and stroke widths
int alpha = 255;
color baseColor = color(random(100, 255), random(100, 255), random(100, 255), alpha);
color accentColor = color(random(100, 255), random(100, 255), random(100, 255), alpha);
color baseColorAlpha;
color accentColorAlpha;
color bgColor = 0;
color outline = 255;
int outerCircleWeight = 5;
int laptopStrokeWeight = 1;
//initial states of little ellipses on the screen
float startingPoint = 7.5;
float leftDotRaise = 0;
boolean leftDown = false;
float centerDotRaise = 0;
boolean centerDown = false;
float rightDotRaise = 0;
boolean rightDown = false;
//variable for the radian at which the circles outside the laptop are at
float circleOrbit;
//variable for roundness of any initially circular shapes (ellipses, orbits, etc)
float specialRadii = 200;
//square-ification button
int buttonX = 200;
int buttonY = 200;
boolean buttonState = true;
int buttonTextSize = 30;
String offMessage = "Revert";
String onMessage = "Squarify";
String buttonMessage = onMessage;
public class screenSavers {
float frameWCS;
float xSpeedCS;
float ySpeedCS;
boolean moveLeftCS;
boolean moveUpCS;
int alphaCS;
color baseColorCS;
color accentColorCS;
color bgColorCS;
color outlineCS;
int outerCircleWeightCS;
int laptopStrokeWeightCS;
float startingPointCS;
float leftDotRaiseCS;
boolean leftDownCS;
float centerDotRaiseCS;
boolean centerDownCS;
float rightDotRaiseCS;
boolean rightDownCS;
float specialRadiiCS;
int buttonXCS;
int buttonYCS;
boolean buttonStateCS;
int buttonTextSizeCS;
String offMessageCS;
String onMessageCS;
String buttonMessageCS;
float circleOrbitCS;
public screenSavers (float frameWC, float xSpeedC, float ySpeedC, boolean moveLeftC, boolean moveUpC, int alphaC, color bgColorC, color outlineC, int outerCircleWeightC, int laptopStrokeWeightC, float startingPointC, float leftDotRaiseC, boolean leftDownC, float centerDotRaiseC, boolean centerDownC, float rightDotRaiseC, boolean rightDownC, float specialRadiiC, int buttonXC, int buttonYC, boolean buttonStateC, int buttonTextSizeC, String offMessageC, String onMessageC, String buttonMessageC, float circleOrbitC){
frameWCS = frameWC;
xSpeedCS = xSpeedC;
ySpeedCS = ySpeedC;
moveLeftCS = moveLeftC;
moveUpCS = moveUpC;
alphaCS = alphaC;
bgColorCS = bgColorC;
outlineCS = outlineC;
outerCircleWeightCS = outerCircleWeightC;
laptopStrokeWeightCS = laptopStrokeWeightC;
startingPointCS = startingPointC;
leftDotRaiseCS = leftDotRaiseC;
leftDownCS = leftDownC;
centerDotRaiseCS = centerDotRaiseC;
centerDownCS = centerDownC;
rightDotRaiseCS = rightDotRaiseC;
rightDownCS = rightDownC;
specialRadiiCS = specialRadiiC;
buttonXCS = buttonXC;
buttonYCS = buttonYC;
buttonStateCS = buttonStateC;
buttonTextSizeCS = buttonTextSizeC;
offMessageCS = offMessageC;
onMessageCS = onMessageC;
buttonMessageCS = buttonMessageC;
circleOrbitCS = circleOrbitC;
}
public void main(String[] args){
}
public void updateAlpha(){
/*
updates alpha versions of color variables that account for the alpha of the color, we have to put this
so that it updates every frame, otherwise the transparency will only update with every bounce. This way
the color updates with each bounce but the transparency constantly updates every frame (or 60 times per second)
*/
baseColorAlpha = color(red(baseColor),green(baseColor),blue(baseColor), alpha);
accentColorAlpha = color(red(accentColor),green(accentColor),blue(accentColor), alpha);
/*
transparency of objects changes with X position of the mouse, 1st quarter of the screen
= 25% brightness, 2nd quarter = 50%, 3rd quarter = 75%, 4th quarter = 100%
*/
if (mouseX < width / 3){
alpha = 75;
} else
if (mouseX > width / 3 && mouseX < 2 * width / 3) {
alpha = 150;
} else
if (mouseX > width * 2 / 3 && mouseX < width){
alpha = 255;
}
}
void resetBackground(){
//reset background to prevent copies of the laptop from generating
background(bgColor);
//set outline color
stroke(outline);
fill(baseColorAlpha);
}
void updateRadii (){
//changing radii of shapes based on button
if (buttonStateCS == true){
specialRadiiCS = 2 * frameW;
} else {
specialRadiiCS = 0;
}
}
void generateComputer(){
//making the frame
rect(frameX, frameY, frameW, frameH);
//making the screen
fill(accentColorAlpha);
rect(frameX, frameY, frameW - frameW / 5, frameH - frameH * 4.0/15.0);
//switching back to the base color for the moving ellipses on the screen
fill(baseColorAlpha);
//creating the laptop keyboard
fill(accentColorAlpha);
beginShape();
vertex(frameX - frameW / 2, frameY + frameH / 2);
vertex(frameX - (frameW / 2 + 15), frameY + frameH / 2 + 15);
vertex(frameX + frameW / 2 + 15, frameY + frameH / 2 + 15);
vertex(frameX + frameW / 2, frameY + frameH / 2);
endShape();
}
void dots(){
rect(frameX - frameW / 5,frameY + leftDotRaise + startingPoint,frameH / 5,frameH / 5,specialRadiiCS);
//moving the dots
if (leftDotRaise >= frameH * (11.0 / 75.0)){
leftDown = false;
} else
if (leftDotRaise <= frameH * (-11.0 / 75.0)){
leftDown = true;
}
if (leftDown == true){
leftDotRaise += frameH / 75;
} else if (leftDown == false){
leftDotRaise -= frameH / 75;
}
rect(frameX,frameY + centerDotRaise, frameH / 5, frameH / 5,specialRadiiCS);
if (centerDotRaise >= frameH * (11.0 / 75.0)){
centerDown = false;
} else
if (centerDotRaise <= frameH * (-11.0 / 75.0)){
centerDown = true;
}
if (centerDown == true){
centerDotRaise += frameH / 75;
} else if (centerDown == false){
centerDotRaise -= frameH / 75;
}
rect(frameX + frameW / 5,frameY + rightDotRaise - startingPoint,frameH / 5,frameH / 5,specialRadiiCS);
if (rightDotRaise >= frameH * (11.0 / 75.0)){
rightDown = false;
} else
if (rightDotRaise <= frameH * (-11.0 / 75.0)){
rightDown = true;
}
if (rightDown == true){
rightDotRaise += frameH / 75;
} else if (rightDown == false){
rightDotRaise -= frameH / 75;
}
startingPoint = 0;
}
void generateOrbitals(){
//creating and animated the outer rotating circles
pushMatrix();
translate(frameX, frameY);
rotate(circleOrbitCS);
translate(frameW * 1.5, frameH / -10);
circleOrbitCS += 0.01;
rect(0, 0, frameW / 2, frameW / 2, specialRadiiCS);
popMatrix();
pushMatrix();
translate(frameX, frameY);
rotate(circleOrbitCS);
translate(frameW * -1.5, frameH / 10);
circleOrbitCS += 0.01;
rect(0, 0, frameW / 2, frameW / 2, specialRadiiCS);
popMatrix();
pushMatrix();
translate(frameX, frameY);
rotate(circleOrbitCS);
translate(frameH / 10, frameW * 1.5);
circleOrbit += 0.01;
rect(0, 0, frameW / 2, frameW / 2, specialRadiiCS);
popMatrix();
pushMatrix();
translate(frameX, frameY);
rotate(circleOrbit);
translate(frameH / -10, frameW * -1.5);
circleOrbit += 0.01;
rect(0, 0, frameW / 2,frameW / 2, specialRadiiCS);
popMatrix();
}
void generateOuterCircle(){
noFill();
stroke(accentColor);
strokeWeight(outerCircleWeight);
rect(frameX, frameY, frameW * 4, frameW * 4,specialRadii);
strokeWeight(laptopStrokeWeight);
}
void bounce(){
if (frameX + 2 * frameW > width) {
moveLeft = true;
baseColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
accentColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
} else if (frameX - 2 * frameW < 0) {
moveLeft = false;
baseColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
accentColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
}
if (frameY + 8.0/3.0 * frameH > height) {
moveUp = true;
baseColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
accentColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
} else if (frameY - 8.0/3.0 * frameH < 0) {
moveUp = false;
baseColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
accentColor = color(random(50, 255), random(50, 255), random(50, 255),alpha);
}
if (moveLeft == true) {
xSpeed = abs(xSpeed) * -1;
} else {
xSpeed = abs(xSpeed);
}
if (moveUp == true) {
ySpeed = abs(ySpeed) * -1;
} else {
ySpeed = abs(ySpeed);
}
}
void updateCoords(){
//update coords
println("Screensaver Icon is currently at (" + frameX + ", " + frameY + ')');
}
void generateSquareButton(){
//square-ification button
fill(accentColor);
rect(buttonXCS,buttonYCS, 200, 200, specialRadiiCS);
fill(255);
textSize(buttonTextSizeCS);
text(buttonMessageCS,buttonXCS,buttonYCS+5);
}
void alignShapes(){
//letting the program know that we are referencing X and Y coords of a rectangle or text box by its center
rectMode(CENTER);
textAlign(CENTER);
}
void testForButton(){
//check if the mouse was pressed at the button's coordinates
if (mouseX > buttonXCS - 100 && mouseY > buttonYCS - 100 && mouseX < buttonXCS + 50 && mouseY < buttonYCS + 50){
//if the button is on, turn it off
if (buttonStateCS == true){
buttonStateCS = false;
buttonMessageCS = offMessageCS;
} else
//if the button is off, turn it on
if (buttonStateCS == false){
buttonStateCS = true;
buttonMessageCS = onMessageCS;
}
}
}
void move(){
frameX += xSpeed;
frameY += ySpeed;
}
void display(){
updateAlpha();
resetBackground();
updateRadii();
generateComputer();
dots();
generateOrbitals();
generateOuterCircle();
bounce();
move();
updateCoords();
generateSquareButton();
}
}
//creating the first instance of the screensaver as an object
screenSavers instance1 = new screenSavers(frameW,xSpeed,ySpeed,moveLeft,moveUp,alpha,bgColor,outline,outerCircleWeight,laptopStrokeWeight,startingPoint,leftDotRaise,leftDown,centerDotRaise, centerDown, rightDotRaise, rightDown, specialRadii, buttonX, buttonY, buttonState, buttonTextSize, offMessage, onMessage, buttonMessage,circleOrbit);
//creating the second instance of the screensaver as an object
screenSavers instance2 = new screenSavers(frameW,xSpeed,ySpeed,moveLeft,moveUp,alpha,bgColor,outline,outerCircleWeight,laptopStrokeWeight,startingPoint,leftDotRaise,leftDown,centerDotRaise, centerDown, rightDotRaise, rightDown, specialRadii, buttonX, buttonY, buttonState, buttonTextSize, offMessage, onMessage, buttonMessage,circleOrbit);
//creating the third instance of the screensaver as an object
screenSavers instance3 = new screenSavers(frameW,xSpeed,ySpeed,moveLeft,moveUp,alpha,bgColor,outline,outerCircleWeight,laptopStrokeWeight,startingPoint,leftDotRaise,leftDown,centerDotRaise, centerDown, rightDotRaise, rightDown, specialRadii, buttonX, buttonY, buttonState, buttonTextSize, offMessage, onMessage, buttonMessage,circleOrbit);
//setup loop, runs ONCE
void setup() {
/*note: to see the console update the X and Y, comment out fullScreen();
and un-comment size(1000,1000); */
//enters fullscreen, regardless of device resolution
fullScreen();
//uncomment this for seeing coords updated
//size(1000,1000);
//makes the framerate go from 24 to a smoother 60 frames. If I don't add this your eyes will hurt
frameRate(60);
instance1.alignShapes();
instance2.alignShapes();
instance3.alignShapes();
}
void draw() {
instance1.display();
instance2.display();
instance3.display();
}
//////////////////////////////////////////////////////////////////
//do something when mouse is pressed
void mousePressed(){
instance1.testForButton();
instance2.testForButton();
instance3.testForButton();
}```
Right now you initialize all 3 instances with the exact same properties. They are only "combined" because you made them the same. You should identify what you want to be different between them, and pass in different properties.
Other notes:
Why does screenSavers have a main() function? Delete it
Please rename it to ScreenSaver.
Do not distinguish using C vs CS. Just name them the same. But use the this keyword to distinguish the class vars. example: this.frameW = frameW;
Use proper indentation with if statements.
Found it a few days later by the way, I was looking for the this keyword

How to make this ball spawn in the middle instead at the side? MonoGame in VisualStudio

Trying to create a simple breakout game, I've written all the code, except i dont understand why the ball is on the side of the screen boundaries instead of on top the paddle(player).
Breakout game, ball on side
class Ball
{
int Width => texture.Width;
int Height => texture.Height;
public Rectangle BoundingBox =>
new Rectangle((int)position.X, (int)position.Y, Width, Height);
Texture2D texture;
Vector2 position;
Vector2 speed;
public void SetStartBallPosition(Rectangle rec)
{
position.X = rec.X + (rec.Width - Width);
position.Y = rec.Y - Height;
if (Game1.RandomNumber.Next(0, 2) < 1)
{
speed = new Vector2(-200.0f, -200.0f);
}
else
{
speed = new Vector2(200.0f, -200.0f);
}
}
public void Draw(SpriteBatch sb)
{
sb.Draw(texture, position, Color.White);
}
public void Update(GameTime gt)
{
position += speed * (float)gt.ElapsedGameTime.TotalSeconds;
if (position.X + Width > Game1.ScreenBounds.Width)
speed.X *= -1;
position.X = Game1.ScreenBounds.Width - Width;
if (position.X < 0)
{
speed.X *= -1;
position.Y = 0;
}
if (position.Y < 0)
{
speed.Y *= -1;
position.Y = 0;
}
}
TL;DR.
My ball spawns at the side instead of middle.
Thanks for Help!
In the SetStartBallPosition(Rectangle rec), You've set the ball position at the full width of the boundary box, minus the full width of the ball:
position.X = rec.X + (rec.Width - Width);
Assuming that rec is empty, then in order to get the center, you need to divide both width's there in half. Like this:
position.X = rec.X + (rec.Width/2 - Width/2);
Be aware that when dividing, that they shouldn't have decimals.
Let me know if it works.

Xamarin.Forms Pinch Gesture inside ScrollView Issue

I'm new to Xamarin.Forms and I've been trying to make a simple program. I wanted to make a program where I could pan and zoom an image. I found code on this site to handle the pinch gesture and view scaling (https://developer.xamarin.com/guides/xamarin-forms/user-interface/gestures/pinch/). I then tried to use this code to extend a 'ScrollView' class.
When I run my code on iOS, it works fine in the simulator. (The scaling is a little clunky, but I don't know if its the simulator or the technique)
When I run my code on Android (both the simulator OR an actual device), the page only scrolls. On the simulator, I'm using the command key to invoke the pinch gesture. I see the appropriate interface for the pinch gesture come up, but it looks like the scroll view is intercepting the gesture as a pan gesture.
When I take the ScrollView class out the mix (and simply make it a ContentView class), the pinch gesture works fine on both platforms.
Any ideas?
Here's the code for my container (which is almost an exact copy of the code at the link above)
using System;
using Xamarin.Forms;
namespace ImageTest
{
public class PinchToZoomContainer : ScrollView
{
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
public PinchToZoomContainer()
{
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
}
public void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(0.1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
}
The answer lays within working on the Android, IOS project.
Check out this guide: http://www.xamboy.com/2017/08/02/creating-a-zoomable-scrollview-in-xamarin-forms/
What is suggested is to create a render that android and ios platformsupport to beable to zoom in on the application on ScrollView.

Xamarin Forms pinch and pan together

I have implemented both pan and pinch individually, and it works fine. I'm now trying to use pinch and pan together and I'm seeing some issues. Here's my code:
XAML:
<AbsoluteLayout x:Name="PinchZoomContainer">
<controls:NavBar x:Name="NavBar" ShowPrevNext="true" ShowMenu="false" IsModal="true" />
<controls:PanContainer x:Name="PinchToZoomContainer">
<Image x:Name="ImageMain" />
</controls:PanContainer>
</AbsoluteLayout>
Pinch/Pan Gesture Add's:
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
Pan Method:
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
startX = e.TotalX;
startY = e.TotalY;
Content.AnchorX = 0;
Content.AnchorY = 0;
break;
case GestureStatus.Running:
// Translate and ensure we don't pan beyond the wrapped user interface element bounds.
Content.TranslationX = Math.Max(Math.Min(0, x + e.TotalX), -Math.Abs(Content.Width - App.ScreenWidth));
Content.TranslationY = Math.Max(Math.Min(0, y + e.TotalY), -Math.Abs(Content.Height - App.ScreenHeight));
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
break;
}
}
Pinch Method:
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
//ImageMain.AnchorX = 0;
//ImageMain.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
currentScale = Math.Min(currentScale, 2.5);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
If I turn off either gesture and only use the other then the functionality works perfectly. The issue arises when I add the pan AND pinch gestures. What seems to be happening is this:
1) The pan actually seems to be working as expected
2) When you pan on the image initially, let's say, move the image to Y-center and X-center, and then you try to zoom, the image gets set back to it's initial state. Then, when you pan, it moves you back to where you were before you tried to zoom (which is why I say the pan is working fine).
From what I'm understanding from my debugging is that when you zoom it's not taking into consideration the position you are currently at. So when you pan first, and then zoom, it doesn't zoom on the position you're at but the beginning point of the image. Then when you try to pan from there, the pan method still remembers where you were, and it moves you back to where you were before you tried to zoom.
Hoping some insight on this. Obviously, there's an issue with my pinch method. I just think (obviously can't figure out) I need to add logic into it that takes into consideration where you're currently at.
The main reason might be that everybody seems to copy and use this code (coming from the dev.xamarin site) with its very convoluted and very unnecessary co-ordinate calculations :-). Unnecessary because we could simply ask the view to do the heavy lifting for us, using the AnchorX and AnchorY properties which serve exactly this purpose.
We can have a double tap operation to zoom in and to revert to the original scale. Note that because Xamarin fails to provide coordinate values with its Tap events (a very unwise decision, actually), we can only zoom from the center now:
private void OnTapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
AnchorX = AnchorY = 0.5;
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
The pinch handler is similarly simple, no need to calculate any translations at all. All we have to do is to set the anchors to the pinch starting point and the framework will do the rest, the scaling will occur around this point. Note that we even have an extra feature here, springy bounce-back on overshoot at both ends of the zoom scale.
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
StartScale = Scale;
AnchorX = e.ScaleOrigin.X;
AnchorY = e.ScaleOrigin.Y;
break;
case GestureStatus.Running:
double current = Scale + (e.Scale - 1) * StartScale;
Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
break;
case GestureStatus.Completed:
if (Scale > MAX_SCALE)
this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
else if (Scale < MIN_SCALE)
this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
break;
}
}
And the panning handler, even simpler. On start, we calculate the starting point from the anchor and during panning, we keep changing the anchor. This anchor being relative to the view area, we can easily clamp it between 0 and 1 and this stops the panning at the extremes without any translation calculation at all.
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
StartX = (1 - AnchorX) * Width;
StartY = (1 - AnchorY) * Height;
break;
case GestureStatus.Running:
AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
break;
}
}
The constants and variables used are just these:
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 8;
private const double OVERSHOOT = 0.15;
private double StartX, StartY;
private double StartScale;
Went with a completely different method of handling this. For anyone who is having issues, this works 100%.
OnPanUpdated
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
var s = (ContentView)sender;
// do not allow pan if the image is in its intial size
if (currentScale == 1)
return;
switch (e.StatusType)
{
case GestureStatus.Running:
double xTrans = xOffset + e.TotalX, yTrans = yOffset + e.TotalY;
// do not allow verical scorlling unless the image size is bigger than the screen
s.Content.TranslateTo(xTrans, yTrans, 0, Easing.Linear);
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
xOffset = s.Content.TranslationX;
yOffset = s.Content.TranslationY;
// center the image if the width of the image is smaller than the screen width
if (originalWidth * currentScale < ScreenWidth && ScreenWidth > ScreenHeight)
xOffset = (ScreenWidth - originalWidth * currentScale) / 2 - s.Content.X;
else
xOffset = System.Math.Max(System.Math.Min(0, xOffset), -System.Math.Abs(originalWidth * currentScale - ScreenWidth));
// center the image if the height of the image is smaller than the screen height
if (originalHeight * currentScale < ScreenHeight && ScreenHeight > ScreenWidth)
yOffset = (ScreenHeight - originalHeight * currentScale) / 2 - s.Content.Y;
else
//yOffset = System.Math.Max(System.Math.Min((originalHeight - (ScreenHeight)) / 2, yOffset), -System.Math.Abs((originalHeight * currentScale - ScreenHeight - (originalHeight - ScreenHeight) / 2)) + (NavBar.Height + App.StatusBarHeight));
yOffset = System.Math.Max(System.Math.Min((originalHeight - (ScreenHeight)) / 2, yOffset), -System.Math.Abs((originalHeight * currentScale - ScreenHeight - (originalHeight - ScreenHeight) / 2)));
// bounce the image back to inside the bounds
s.Content.TranslateTo(xOffset, yOffset, 500, Easing.BounceOut);
break;
}
}
OnPinchUpdated
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
var s = (ContentView)sender;
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = s.Content.Scale;
s.Content.AnchorX = 0;
s.Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = System.Math.Max(1, currentScale);
currentScale = System.Math.Min(currentScale, 5);
//scaleLabel.Text = "Scale: " + currentScale.ToString ();
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = s.Content.X + xOffset;
double deltaX = renderedX / App.ScreenWidth;
double deltaWidth = App.ScreenWidth / (s.Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = s.Content.Y + yOffset;
double deltaY = renderedY / App.ScreenHeight;
double deltaHeight = App.ScreenHeight / (s.Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * s.Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * s.Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
var transX = targetX.Clamp(-s.Content.Width * (currentScale - 1), 0);
var transY = targetY.Clamp(-s.Content.Height * (currentScale - 1), 0);
s.Content.TranslateTo(transX, transY, 0, Easing.Linear);
// Apply scale factor.
s.Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation applied during the pan
xOffset = s.Content.TranslationX;
yOffset = s.Content.TranslationY;
// center the image if the width of the image is smaller than the screen width
if (originalWidth * currentScale < ScreenWidth && ScreenWidth > ScreenHeight)
xOffset = (ScreenWidth - originalWidth * currentScale) / 2 - s.Content.X;
else
xOffset = System.Math.Max(System.Math.Min(0, xOffset), -System.Math.Abs(originalWidth * currentScale - ScreenWidth));
// center the image if the height of the image is smaller than the screen height
if (originalHeight * currentScale < ScreenHeight && ScreenHeight > ScreenWidth)
yOffset = (ScreenHeight - originalHeight * currentScale) / 2 - s.Content.Y;
else
yOffset = System.Math.Max(System.Math.Min((originalHeight - ScreenHeight) / 2, yOffset), -System.Math.Abs(originalHeight * currentScale - ScreenHeight - (originalHeight - ScreenHeight) / 2));
// bounce the image back to inside the bounds
s.Content.TranslateTo(xOffset, yOffset, 500, Easing.BounceOut);
}
}
OnSizeAllocated (most of this you probably dont need, but some you do. consider ScreenWidth, ScreenHeight, yOffset, xOffset, currentScale)
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height); //must be called
if (width != -1 && (ScreenWidth != width || ScreenHeight != height))
{
ResetLayout(width, height);
originalWidth = initialLoad ?
ImageWidth >= 960 ?
App.ScreenWidth > 320
? 768
: 320
: ImageWidth / 3
: imageContainer.Content.Width / imageContainer.Content.Scale;
var normalizedHeight = ImageWidth >= 960 ?
App.ScreenWidth > 320 ? ImageHeight / (ImageWidth / 768)
: ImageHeight / (ImageWidth / 320)
: ImageHeight / 3;
originalHeight = initialLoad ?
normalizedHeight : (imageContainer.Content.Height / imageContainer.Content.Scale);
ScreenWidth = width;
ScreenHeight = height;
xOffset = imageContainer.TranslationX;
yOffset = imageContainer.TranslationY;
currentScale = imageContainer.Scale;
if (initialLoad)
initialLoad = false;
}
}
Layout (XAML in C#)
ImageMain = new Image
{
HorizontalOptions = LayoutOptions.CenterAndExpand,
VerticalOptions = LayoutOptions.CenterAndExpand,
Aspect = Aspect.AspectFill,
Source = ImageMainSource
};
imageContainer = new ContentView
{
Content = ImageMain,
BackgroundColor = Xamarin.Forms.Color.Black,
WidthRequest = App.ScreenWidth - 250
};
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
imageContainer.GestureRecognizers.Add(panGesture);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
imageContainer.GestureRecognizers.Add(pinchGesture);
double smallImageHeight = ImageHeight / (ImageWidth / 320);
absoluteLayout = new AbsoluteLayout
{
HeightRequest = App.ScreenHeight,
BackgroundColor = Xamarin.Forms.Color.Black,
};
AbsoluteLayout.SetLayoutFlags(imageContainer, AbsoluteLayoutFlags.All);
AbsoluteLayout.SetLayoutBounds(imageContainer, new Rectangle(0f, 0f, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
absoluteLayout.Children.Add(imageContainer, new Rectangle(0, 0, 1, 1), AbsoluteLayoutFlags.All);
Content = absoluteLayout;
I've been working on a Image viewer with pan&zoom...
I reached another variation.
I'll share with you.
First, we need a Pan/Zoom class controller:
using System;
using Xamarin.Forms;
namespace Project.Util
{
public class PanZoom
{
bool pitching = false;
bool panning = false;
bool collectFirst = false;
double xOffset = 0;
double yOffset = 0;
//scale processing...
double scaleMin;
double scaleMax;
double scale;
double _xScaleOrigin;
double _yScaleOrigin;
double panTotalX;
double panTotalY;
ContentPage contentPage;
View Content;
public void Setup(ContentPage cp, View content)
{
contentPage = cp;
Content = content;
PinchGestureRecognizer pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += PinchUpdated;
contentPage.Content.GestureRecognizers.Add(pinchGesture);
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
contentPage.Content.GestureRecognizers.Add(panGesture);
contentPage.SizeChanged += (sender, e) => { layoutElements(); };
}
public void layoutElements()
{
if (contentPage.Width <= 0 || contentPage.Height <= 0 || Content.WidthRequest <= 0 || Content.HeightRequest <= 0)
return;
xOffset = 0;
yOffset = 0;
double pageW = contentPage.Width;
double pageH = contentPage.Height;
double w_s = pageW / Content.WidthRequest;
double h_s = pageH / Content.HeightRequest;
if (w_s < h_s)
scaleMin = w_s;
else
scaleMin = h_s;
scaleMax = scaleMin * 3.0;
scale = scaleMin;
double w = Content.WidthRequest * scale;
double h = Content.HeightRequest * scale;
double x = pageW / 2.0 - w / 2.0 + xOffset;
double y = pageH / 2.0 - h / 2.0 + yOffset;
AbsoluteLayout.SetLayoutBounds(Content, new Rectangle(x, y, w, h));
}
void fixPosition(
ref double x, ref double y, ref double w, ref double h,
bool setoffset
)
{
double pageW = contentPage.Width;
double pageH = contentPage.Height;
if (w <= pageW)
{
double new_x = pageW / 2.0 - w / 2.0;
if (setoffset)
xOffset = new_x - (pageW / 2.0 - w / 2.0);
x = new_x;
} else
{
if (x > 0)
{
double new_x = 0;
if (setoffset)
xOffset = new_x - (pageW / 2.0 - w / 2.0);
x = new_x;
}
if (x < (pageW - w))
{
double new_x = (pageW - w);
if (setoffset)
xOffset = new_x - (pageW / 2.0 - w / 2.0);
x = new_x;
}
}
if (h <= pageH)
{
double new_y = pageH / 2.0 - h / 2.0;
if (setoffset)
yOffset = new_y - (pageH / 2.0 - h / 2.0);
y = new_y;
}
else
{
if (y > 0)
{
double new_y = 0;
if (setoffset)
yOffset = new_y - (pageH / 2.0 - h / 2.0);
y = new_y;
}
if (y < (pageH - h))
{
double new_y = (pageH - h);
if (setoffset)
yOffset = new_y - (pageH / 2.0 - h / 2.0);
y = new_y;
}
}
}
private void PinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (sender != contentPage.Content)
return;
switch (e.Status)
{
case GestureStatus.Started:
{
pitching = true;
collectFirst = true;
double pageW = contentPage.Width;
double pageH = contentPage.Height;
_xScaleOrigin = e.ScaleOrigin.X * pageW;
_yScaleOrigin = e.ScaleOrigin.Y * pageH;
}
break;
case GestureStatus.Running:
if (pitching)
{
double targetScale = scale * e.Scale;
targetScale = Math.Min(Math.Max(scaleMin, targetScale), scaleMax);
double scaleDelta = targetScale / scale;
double pageW = contentPage.Width;
double pageH = contentPage.Height;
double w_old = Content.WidthRequest * scale;
double h_old = Content.HeightRequest * scale;
double x_old = pageW / 2.0 - w_old / 2.0 + xOffset;
double y_old = pageH / 2.0 - h_old / 2.0 + yOffset;
scale = targetScale;
//new w and h
double w = Content.WidthRequest * scale;
double h = Content.HeightRequest * scale;
//transform x old and y old
// to get new scaled position over a pivot
double _x = (x_old - _xScaleOrigin) * scaleDelta + _xScaleOrigin;
double _y = (y_old - _yScaleOrigin) * scaleDelta + _yScaleOrigin;
//fix offset to be equal to _x and _y
double x = pageW / 2.0 - w / 2.0 + xOffset;
double y = pageH / 2.0 - h / 2.0 + yOffset;
xOffset += _x - x;
yOffset += _y - y;
x = pageW / 2.0 - w / 2.0 + xOffset;
y = pageH / 2.0 - h / 2.0 + yOffset;
fixPosition(ref x, ref y, ref w, ref h, true);
AbsoluteLayout.SetLayoutBounds(Content, new Rectangle(x, y, w, h));
}
break;
case GestureStatus.Completed:
pitching = false;
break;
}
}
public void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (sender != contentPage.Content)
return;
switch (e.StatusType)
{
case GestureStatus.Started:
{
panning = true;
panTotalX = e.TotalX;
panTotalY = e.TotalY;
collectFirst = true;
}
break;
case GestureStatus.Running:
if (panning)
{
if (collectFirst)
{
collectFirst = false;
panTotalX = e.TotalX;
panTotalY = e.TotalY;
}
double pageW = contentPage.Width;
double pageH = contentPage.Height;
double deltaX = e.TotalX - panTotalX;
double deltaY = e.TotalY - panTotalY;
panTotalX = e.TotalX;
panTotalY = e.TotalY;
xOffset += deltaX;
yOffset += deltaY;
double w = Content.WidthRequest * scale;
double h = Content.HeightRequest * scale;
double x = pageW / 2.0 - w / 2.0 + xOffset;
double y = pageH / 2.0 - h / 2.0 + yOffset;
fixPosition(ref x, ref y, ref w, ref h, true);
AbsoluteLayout.SetLayoutBounds(Content, new Rectangle(x, y, w, h));
}
break;
case GestureStatus.Completed:
panning = false;
break;
}
}
}
}
In the content page:
using System;
using FFImageLoading.Forms;
using Xamarin.Forms;
using Project.Util;
namespace Project.ContentPages
{
public class ContentPage_ImageViewer : ContentPage
{
AbsoluteLayout al = null;
CachedImage image = null;
PanZoom panZoom;
public ContentPage_ImageViewer(string imageURL)
{
MasterDetailPage mdp = Application.Current.MainPage as MasterDetailPage;
mdp.IsGestureEnabled = false;
NavigationPage.SetHasBackButton(this, true);
Title = "";
image = new CachedImage()
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Aspect = Aspect.Fill,
LoadingPlaceholder = "placeholder_320x322.png",
ErrorPlaceholder = "placeholder_320x322.png",
Source = imageURL,
RetryCount = 3,
DownsampleToViewSize = false,
IsVisible = false,
FadeAnimationEnabled = false
};
image.Success += delegate (object sender, CachedImageEvents.SuccessEventArgs e)
{
Device.BeginInvokeOnMainThread(() =>
{
image.WidthRequest = e.ImageInformation.OriginalWidth;
image.HeightRequest = e.ImageInformation.OriginalHeight;
image.IsVisible = true;
for(int i = al.Children.Count-1; i >= 0; i--)
{
if (al.Children[i] is ActivityIndicator)
al.Children.RemoveAt(i);
}
panZoom.layoutElements();
});
};
ActivityIndicator ai = new ActivityIndicator()
{
IsRunning = true,
Scale = (Device.RuntimePlatform == Device.Android) ? 0.25 : 1.0,
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.Fill,
Color = Color.White
};
Content = (al = new AbsoluteLayout()
{
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.Fill,
BackgroundColor = Color.Black,
Children =
{
image,
ai
}
});
AbsoluteLayout.SetLayoutFlags(image, AbsoluteLayoutFlags.None);
AbsoluteLayout.SetLayoutBounds(ai, new Rectangle(0, 0, 1, 1));
AbsoluteLayout.SetLayoutFlags(ai, AbsoluteLayoutFlags.All);
panZoom = new PanZoom();
panZoom.Setup(this, image);
}
}
}
For me it worked like below, just did some changes in the code given in the question,
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user
interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user
interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale -
startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale -
startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
// Apply scale factor.
Content.Scale = currentScale;
width = Content.Width * currentScale;
height = Content.Height * currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
x = Content.TranslationX;
y = Content.TranslationY;
}
}
Pan Code
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (!width.Equals(Content.Width) && !height.Equals(Content.Height))
{
switch (e.StatusType)
{
case GestureStatus.Started:
startX = Content.TranslationX;
startY = Content.TranslationY;
break;
case GestureStatus.Running:
if (!width.Equals(0))
{
Content.TranslationX = Math.Max(Math.Min(0, x + e.TotalX), -Math.Abs(Content.Width - width));// App.ScreenWidth));
}
if (!height.Equals(0))
{
Content.TranslationY = Math.Max(Math.Min(0, y + e.TotalY), -Math.Abs(Content.Height - height)); //App.ScreenHeight));
}
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
break;
}
}
}

Pushpin size at WP7 map control

while trying to implement logic that show current user location i encountered an issue.
<Maps:Pushpin Location="{Binding MyLocation}" Canvas.ZIndex="1000" PositionOrigin="Center" >
<Maps:Pushpin.Template>
<ControlTemplate>
<Grid>
<Ellipse Width="{Binding MyAccuracyViewSize}" Height="{Binding MyAccuracyViewSize}"
Fill="#60008000" Stroke="Green" StrokeThickness="3"/>
<Ellipse Width="18" Height="18" Fill="#A0FF4500" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
</Maps:Pushpin.Template>
</Maps:Pushpin>
Bigger green circle shows accuracy area. Its size in pixels varies depending on zoom. If zoom level is big - it becomes rather big (> 480 pixels). At that point it gets cropped by screen resolution. AFAIK WP7 restriction is 2000x2000 px for control size.
Seems that this is a kind of a map control restriction.
Any ideas how to remove this restriction to show ellipse of size up to 2000x2000 px?
Thanks!
How about MapPolygon?
void OnPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
SecondsCounter = 0; //reset counter
double accuracy = e.Position.Location.HorizontalAccuracy;
if (accuracy < e.Position.Location.VerticalAccuracy)
{
accuracy = e.Position.Location.VerticalAccuracy;
}
if (PolyCircle == null)
{
PolyCircle = new MapPolygon();
PolyCircle.Opacity = 0.7;
//Set the polygon properties
PolyCircle.Fill = new SolidColorBrush(Colors.Orange);
PolyCircle.Stroke = new SolidColorBrush(Colors.Purple);
PolyCircle.StrokeThickness = 4;
map1.Children.Add(PolyCircle);
}
PolyCircle.Locations = CreateCircle(e.Position.Location, accuracy);
map1.Center = e.Position.Location;
}
public static double ToRadian(double degrees)
{
return degrees * (Math.PI / 180);
}
public static double ToDegrees(double radians)
{
return radians * (180 / Math.PI);
}
public static LocationCollection CreateCircle(GeoCoordinate center, double radius)
{
var earthRadius = 6367000; // radius in meters
var lat = ToRadian(center.Latitude); //radians
var lng = ToRadian(center.Longitude); //radians
var d = radius / earthRadius; // d = angular distance covered on earth's surface
var locations = new LocationCollection();
for (var x = 0; x <= 360; x++)
{
var brng = ToRadian(x);
var latRadians = Math.Asin(Math.Sin(lat) * Math.Cos(d) + Math.Cos(lat) * Math.Sin(d) * Math.Cos(brng));
var lngRadians = lng + Math.Atan2(Math.Sin(brng) * Math.Sin(d) * Math.Cos(lat), Math.Cos(d) - Math.Sin(lat) * Math.Sin(latRadians));
locations.Add(new GeoCoordinate(ToDegrees(latRadians), ToDegrees(lngRadians)));
}
return locations;
}
More here, in My Map position example. Good luck!

Resources