Xamarin.Form 可以将相机视图插入视图,例如 Instagram

Xamarin.Form It is possible insert camera view into view, like Instagram

是否可以使用 xamarin.form 创建一个界面来捕捉或选择图像,就像在 Instagram 上一样。

这将是一张像 Instagram 上那样的方形图片。我测试了 MediaPlugin,但它只是打开相机,我无法将它插入到视图中。

您应该创建一个自定义渲染器。

首先在您的共享项目中创建一个视图,如下所示:

public enum CameraOptions
{
    Rear,
    Front
}

public class CameraView : View
{
    public static readonly BindableProperty CameraProperty = BindableProperty.Create(nameof(Camera), typeof(CameraOptions), typeof(CameraView), CameraOptions.Rear);

    public CameraOptions Camera
    {
        get { return (CameraOptions)GetValue(CameraProperty); }
        set { SetValue(CameraProperty, value); }
    }
}

然后为每个平台创建一个渲染器。

Android:

[assembly: ExportRenderer(typeof(CameraView), typeof(CameraViewRenderer))]
namespace CameraViewXamarinForms.Droid.Renderers
{
    public class CameraViewRenderer : ViewRenderer<CameraView, NativeCameraView>
    {
        NativeCameraView cameraPreview;

        public CameraViewRenderer(Context context) : base(context)
        {
        }

        protected override async void OnElementChanged(ElementChangedEventArgs<CameraView> e)
        {
            base.OnElementChanged(e);

            if (Control == null)
            {
                cameraPreview = new NativeCameraView(Context);
                SetNativeControl(cameraPreview);
            }

            if (e.NewElement != null)
            {

                try
                {
                    PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.Camera>();
                    if (status != PermissionStatus.Granted)
                    {
                        status = await Permissions.RequestAsync<Permissions.Camera>();
                    }

                    if (status == PermissionStatus.Granted)
                    {
                        cameraPreview.OpenCamera(e.NewElement.Camera);
                        SetNativeControl(cameraPreview);
                    }
                    else if (status != PermissionStatus.Unknown)
                    {
                        //await DisplayAlert("Camera Denied", "Can not continue, try again.", "OK");
                    }
                }
                catch (Exception ex)
                {

                }
            }
        }
    }
}

Android - NativeCameraView:

public sealed class NativeCameraView : FrameLayout, TextureView.ISurfaceTextureListener
{
    private static readonly SparseIntArray Orientations = new SparseIntArray();

    public event EventHandler<ImageSource> Photo;

    public bool OpeningCamera { private get; set; }

    public CameraDevice CameraDevice;

    private readonly CameraStateListener _mStateListener;
    private CaptureRequest.Builder _previewBuilder;
    private CameraCaptureSession _previewSession;
    private SurfaceTexture _viewSurface;
    private readonly TextureView _cameraTexture;
    private Size _previewSize;
    private readonly Context _context;
    private CameraManager _manager;

    public NativeCameraView(Context context) : base(context)
    {
        _context = context;

        var inflater = LayoutInflater.FromContext(context);

        if (inflater == null) return;
        var view = inflater.Inflate(Resource.Layout.CameraLayout, this);
        _cameraTexture = view.FindViewById<TextureView>(Resource.Id.cameraTexture);

        _cameraTexture.Click += (sender, args) => { TakePhoto(); };

        _cameraTexture.SurfaceTextureListener = this;

        _mStateListener = new CameraStateListener { Camera = this };

        Orientations.Append((int)SurfaceOrientation.Rotation0, 0);
        Orientations.Append((int)SurfaceOrientation.Rotation90, 90);
        Orientations.Append((int)SurfaceOrientation.Rotation180, 180);
        Orientations.Append((int)SurfaceOrientation.Rotation270, 270);
    }

    public void OnSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
    {
        _viewSurface = surface;
        ConfigureTransform(width, height);
        StartPreview();
    }

    public bool OnSurfaceTextureDestroyed(SurfaceTexture surface)
    {
        return true;
    }

    public void OnSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
    {
    }

    public void OnSurfaceTextureUpdated(SurfaceTexture surface)
    {
    }

    public void OpenCamera(CameraOptions options)
    {
        if (_context == null || OpeningCamera)
        {
            return;
        }

        OpeningCamera = true;

        _manager = (CameraManager)_context.GetSystemService(Context.CameraService);

        var cameraId = _manager.GetCameraIdList()[(int)options];

        var characteristics = _manager.GetCameraCharacteristics(cameraId);
        var map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap);

        _previewSize = map.GetOutputSizes(Class.FromType(typeof(SurfaceTexture)))[0];

        _manager.OpenCamera(cameraId, _mStateListener, null);

    }


    private void TakePhoto()
    {
        if (_context == null || CameraDevice == null) return;

        var characteristics = _manager.GetCameraCharacteristics(CameraDevice.Id);
        Size[] jpegSizes = null;
        if (characteristics != null)
        {
            jpegSizes = ((StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap)).GetOutputSizes((int)ImageFormatType.Jpeg);
        }
        var width = 480;
        var height = 640;

        if (jpegSizes != null && jpegSizes.Length > 0)
        {
            width = jpegSizes[0].Width;
            height = jpegSizes[0].Height;
        }

        var reader = ImageReader.NewInstance(width, height, ImageFormatType.Jpeg, 1);
        var outputSurfaces = new List<Surface>(2) { reader.Surface, new Surface(_viewSurface) };

        var captureBuilder = CameraDevice.CreateCaptureRequest(CameraTemplate.StillCapture);
        captureBuilder.AddTarget(reader.Surface);
        captureBuilder.Set(CaptureRequest.ControlMode, new Integer((int)ControlMode.Auto));

        var windowManager = _context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
        var rotation = windowManager.DefaultDisplay.Rotation;
        captureBuilder.Set(CaptureRequest.JpegOrientation, new Integer(Orientations.Get((int)rotation)));

        var readerListener = new ImageAvailableListener();

        readerListener.Photo += (sender, buffer) =>
        {
            Photo?.Invoke(this, ImageSource.FromStream(() => new MemoryStream(buffer)));
        };

        var thread = new HandlerThread("CameraPicture");
        thread.Start();
        var backgroundHandler = new Handler(thread.Looper);
        reader.SetOnImageAvailableListener(readerListener, backgroundHandler);

        var captureListener = new CameraCaptureListener();

        captureListener.PhotoComplete += (sender, e) =>
        {
            StartPreview();
        };

        CameraDevice.CreateCaptureSession(outputSurfaces, new CameraCaptureStateListener
        {
            OnConfiguredAction = session =>
            {
                try
                {
                    _previewSession = session;
                    session.Capture(captureBuilder.Build(), captureListener, backgroundHandler);
                }
                catch (CameraAccessException ex)
                {
                    Log.WriteLine(LogPriority.Info, "Capture Session error: ", ex.ToString());
                }
            }
        }, backgroundHandler);
    }


    public void StartPreview()
    {
        if (CameraDevice == null || !_cameraTexture.IsAvailable || _previewSize == null) return;

        var texture = _cameraTexture.SurfaceTexture;

        texture.SetDefaultBufferSize(_previewSize.Width, _previewSize.Height);
        var surface = new Surface(texture);

        _previewBuilder = CameraDevice.CreateCaptureRequest(CameraTemplate.Preview);
        _previewBuilder.AddTarget(surface);

        CameraDevice.CreateCaptureSession(new List<Surface> { surface },
            new CameraCaptureStateListener
            {
                OnConfigureFailedAction = session =>
                {
                },
                OnConfiguredAction = session =>
                {
                    _previewSession = session;
                    UpdatePreview();
                }
        },
            null);


    }

    private void ConfigureTransform(int viewWidth, int viewHeight)
    {
        if (_viewSurface == null || _previewSize == null || _context == null) return;

        var windowManager = _context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();

        var rotation = windowManager.DefaultDisplay.Rotation;
        var matrix = new Matrix();
        var viewRect = new RectF(0, 0, viewWidth, viewHeight);
        var bufferRect = new RectF(0, 0, _previewSize.Width, _previewSize.Height);

        var centerX = viewRect.CenterX();
        var centerY = viewRect.CenterY();

        if (rotation == SurfaceOrientation.Rotation90 || rotation == SurfaceOrientation.Rotation270)
        {
            bufferRect.Offset(centerX - bufferRect.CenterX(), centerY - bufferRect.CenterY());
            matrix.SetRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.Fill);

            matrix.PostRotate(90 * ((int)rotation - 2), centerX, centerY);
        }

        _cameraTexture.SetTransform(matrix);
    }

    private void UpdatePreview()
    {
        if (CameraDevice == null || _previewSession == null) return;

        _previewBuilder.Set(CaptureRequest.ControlMode, new Integer((int)ControlMode.Auto));
        var thread = new HandlerThread("CameraPreview");
        thread.Start();
        var backgroundHandler = new Handler(thread.Looper);

        _previewSession.SetRepeatingRequest(_previewBuilder.Build(), null, backgroundHandler);
    }
}

iOS:

[assembly: ExportRenderer(typeof(CameraView), typeof(CameraViewRenderer))]
namespace CameraViewXamarinForms.iOS.Renderers
{
    public class CameraViewRenderer : ViewRenderer<CameraView, NativeCameraView>
    {
        NativeCameraView uiCameraPreview;

        protected override void OnElementChanged(ElementChangedEventArgs<CameraView> e)
        {
            base.OnElementChanged(e);

            if (Control == null)
            {
                uiCameraPreview = new NativeCameraView(e.NewElement.Camera);
                SetNativeControl(uiCameraPreview);
            }
            if (e.OldElement != null)
            {
                // Unsubscribe
                uiCameraPreview.Tapped -= OnCameraPreviewTapped;
            }
            if (e.NewElement != null)
            {
                // Subscribe
                uiCameraPreview.Tapped += OnCameraPreviewTapped;
            }
        }

        void OnCameraPreviewTapped(object sender, EventArgs e)
        {
            if (uiCameraPreview.IsPreviewing)
            {
                uiCameraPreview.CaptureSession.StopRunning();
                uiCameraPreview.IsPreviewing = false;
            }
            else
            {
                uiCameraPreview.CaptureSession.StartRunning();
                uiCameraPreview.IsPreviewing = true;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                Control.CaptureSession.Dispose();
                Control.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

iOS - NativeCameraView:

public class NativeCameraView : UIView
{
    AVCaptureVideoPreviewLayer previewLayer;
    CameraOptions cameraOptions;

    public event EventHandler<EventArgs> Tapped;

    public AVCaptureSession CaptureSession { get; private set; }

    public bool IsPreviewing { get; set; }

    public NativeCameraView(CameraOptions options)
    {
        cameraOptions = options;
        IsPreviewing = false;
        Initialize();
    }

    public override void Draw(CGRect rect)
    {
        base.Draw(rect);
        previewLayer.Frame = rect;
    }

    public override void TouchesBegan(NSSet touches, UIEvent evt)
    {
        base.TouchesBegan(touches, evt);
        OnTapped();
    }

    protected virtual void OnTapped()
    {
        var eventHandler = Tapped;
        if (eventHandler != null)
        {
            eventHandler(this, new EventArgs());
        }
    }

    void Initialize()
    {
        CaptureSession = new AVCaptureSession();
        previewLayer = new AVCaptureVideoPreviewLayer(CaptureSession)
        {
            Frame = Bounds,
            VideoGravity = AVLayerVideoGravity.ResizeAspectFill
        };

        var videoDevices = AVCaptureDevice.DevicesWithMediaType(AVMediaType.Video);
        var cameraPosition = (cameraOptions == CameraOptions.Front) ? AVCaptureDevicePosition.Front : AVCaptureDevicePosition.Back;
        var device = videoDevices.FirstOrDefault(d => d.Position == cameraPosition);

        if (device == null)
        {
            return;
        }

        NSError error;
        var input = new AVCaptureDeviceInput(device, out error);
        CaptureSession.AddInput(input);
        Layer.AddSublayer(previewLayer);
        CaptureSession.StartRunning();
        IsPreviewing = true;
    }
}

这是它在 Android 模拟器上的样子

If you have any question I've created a repository on Github for this question, pls check it here