Search code examples
c#androidxamarinxamarin.formsandroid-camera

Tap to focus for camera implementation


I'm trying to implement a manual focus feature for my camera page so that the user can tap to focus the camera.

I'm following this StackOverflow question that's currently written in Java for native Android. I've been converting it to C# for my Xamarin.Forms Android app.

Here's what I have so far:

public class CameraPage : PageRenderer, TextureView.ISurfaceTextureListener, Android.Views.View.IOnTouchListener, IAutoFocusCallback
{
    global::Android.Hardware.Camera camera;
    TextureView textureView;

    public void OnAutoFocus(bool success, Android.Hardware.Camera camera)
    {
        var parameters = camera.GetParameters();
        if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeContinuousPicture)
        {
            parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeContinuousPicture;

            if (parameters.MaxNumFocusAreas > 0)
            {
                parameters.FocusAreas = null;
            }
            camera.SetParameters(parameters);
            camera.StartPreview();
        }
    }

    public bool OnTouch(Android.Views.View v, MotionEvent e)
    {
        if (camera != null)
        {
            var parameters = camera.GetParameters();
            camera.CancelAutoFocus();
            Rect focusRect = CalculateTapArea(e.GetX(), e.GetY(), 1f);

            if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeAuto)
            {
                parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeAuto;
            }
            if (parameters.MaxNumFocusAreas > 0)
            {
                List<Area> mylist = new List<Area>();
                mylist.Add(new Android.Hardware.Camera.Area(focusRect, 1000));
                parameters.FocusAreas = mylist;
            }

            try
            {
                camera.CancelAutoFocus();
                camera.SetParameters(parameters);
                camera.StartPreview();
                camera.AutoFocus(OnAutoFocus); //Here is the issue. How do I use the callback? 
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Console.Write(ex.StackTrace);
            }
            return true;
        }
        return false; 
    }

    private Rect CalculateTapArea(object x, object y, float coefficient)
    {
        var focusAreaSize = 500;
        int areaSize = Java.Lang.Float.ValueOf(focusAreaSize * coefficient).IntValue();

        int left = clamp((int)x - areaSize / 2, 0, textureView.Width - areaSize);
        int top = clamp((int)y - areaSize / 2, 0, textureView.Height - areaSize);

        RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
        Matrix.MapRect(rectF);

        return new Rect((int)System.Math.Round(rectF.Left), (int)System.Math.Round(rectF.Top), (int)System.Math.Round(rectF.Right), (int)System.Math.Round(rectF.Bottom));
    }

    private int clamp(int x, int min, int max)
    {
        if (x > max)
        {
            return max;
        }
        if (x < min)
        {
            return min;
        }
        return x;
    }
}

I've managed to convert most of it but I'm not sure how to properly use the AutoFocusCallback here. What should I do to call OnAutoFocus from my OnTouch event like in the java answer I linked above?

After I attached the callback, then all I need to do is subscribe the OnTouch event to my page correct or...?

For example, I tried:

textureView.Click += OnTouch; but 'no overload for 'OnTouch' matches delegate 'EventHandler'. Is there a specific event handler I need to use?


Solution

  • You can try change

    camera.AutoFocus(OnAutoFocus);
    

    to

    camera.AutoFocus(this);
    

    and it will be using OnAutoFocus because it implementation from IAutoFocusCallback.

    And for your question about subscribe event you can try to subscribe event in OnElementChanged like this

    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
            {
                base.OnElementChanged(e);
                if (e.OldElement != null || Element == null)
                {
                    return;
                }
                try
                {
                    this.SetOnTouchListener(this);
                }
                catch (Exception e)
                {
    
                }
    
            }
    

    And btw I don't see to use TextureView.ISurfaceTextureListener in this code.