【问题标题】:Getting the data from the Custom View Renderer in the platform specific from Shared Project in Xamarin.Forms从 Xamarin.Forms 中的共享项目特定平台中的自定义视图渲染器获取数据
【发布时间】:2018-03-21 13:37:46
【问题描述】:

我正在使用 Xamarin.Forms 开发移动应用程序。在我的应用程序中,我正在使用自定义视图实现自定义相机。我已经成功实现了自定义相机视图。我现在要做的是,当单击共享项目内的按钮时,我想从特定于平台的自定义视图渲染器中检索一些数据。请参阅下面的场景。

这是我在共享项目中的 CameraPage.xaml。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:local="clr-namespace:MementoApp.Views;"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             Title="Capture your moment"
             Padding="0,20,0,0"
             x:Class="MementoApp.Views.CameraPage">
    <ContentPage.Content>
        <StackLayout Orientation="Vertical">

            <local:CameraPreview x:Name="Camera" Camera="Rear" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
            <StackLayout Orientation="Vertical" HorizontalOptions="Center">
                <Button x:Name="buttonCapturePhoto" Text="Capture photo" />
            </StackLayout>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

如您所见,CameraPreview 是自定义视图。我还在它下面添加了一个按钮。

这是 CameraPreview 类

namespace MementoApp.Views
{
    public enum CameraOptions
    {
        Rear,
        Front
    }

    public class CameraPreview : View
    {
        public static readonly BindableProperty CameraProperty = BindableProperty.Create(
            propertyName: "Camera",
            returnType: typeof(CameraOptions),
            declaringType: typeof(CameraPreview),
            defaultValue: CameraOptions.Rear);

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

在 Android 项目中,我像这样为相机自定义视图创建了渲染器。

[assembly: ExportRenderer(typeof(MementoApp.Views.CameraPreview), typeof(MementoApp.Droid.CameraPreviewRenderer))]
namespace MementoApp.Droid
{
    public class CameraPreviewRenderer : ViewRenderer<MementoApp.Views.CameraPreview, MementoApp.Droid.CameraPreview>
    {
        CameraPreview cameraPreview;

        public CameraPreview CameraPreview { get { return this.cameraPreview; } }

        public CameraPreviewRenderer(Context context) : base(context)
        {

        }

        protected override void OnElementChanged(ElementChangedEventArgs<MementoApp.Views.CameraPreview> e)
        {
            base.OnElementChanged(e);

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

            if (e.OldElement != null)
            {
                // Unsubscribe
                cameraPreview.Click -= OnCameraPreviewClicked;
            }
            if (e.NewElement != null)
            {
                Control.Preview = Camera.Open((int)e.NewElement.Camera);

                // Subscribe
                cameraPreview.Click += OnCameraPreviewClicked;
            }
        }

        void OnCameraPreviewClicked(object sender, EventArgs e)
        {
            if (cameraPreview.IsPreviewing)
            {
                cameraPreview.Preview.StopPreview();
                cameraPreview.IsPreviewing = false;
            }
            else
            {
                cameraPreview.Preview.StartPreview();
                cameraPreview.IsPreviewing = true;
            }
        }

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

这是 Android 项目中的 CameraPreview 类。

public sealed class CameraPreview : ViewGroup, ISurfaceHolderCallback
    {
        SurfaceView surfaceView;
        ISurfaceHolder holder;
        Camera.Size previewSize;
        IList<Camera.Size> supportedPreviewSizes;
        Camera camera;
        IWindowManager windowManager;

        public bool IsPreviewing { get; set; }

        public Camera Preview
        {
            get { return camera; }
            set
            {
                camera = value;
                if (camera != null)
                {
                    supportedPreviewSizes = Preview.GetParameters().SupportedPreviewSizes;
                    RequestLayout();
                }
            }
        }

        public CameraPreview(Context context)
            : base(context)
        {
            surfaceView = new SurfaceView(context);
            AddView(surfaceView);

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

            IsPreviewing = false;
            holder = surfaceView.Holder;
            holder.AddCallback(this);
        }

        protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            int width = ResolveSize(SuggestedMinimumWidth, widthMeasureSpec);
            int height = ResolveSize(SuggestedMinimumHeight, heightMeasureSpec);
            SetMeasuredDimension(width, height);

            if (supportedPreviewSizes != null)
            {
                previewSize = GetOptimalPreviewSize(supportedPreviewSizes, width, height);
            }
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
            var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);

            surfaceView.Measure(msw, msh);
            surfaceView.Layout(0, 0, r - l, b - t);
        }

        public void SurfaceCreated(ISurfaceHolder holder)
        {
            try
            {
                if (Preview != null)
                {
                    Preview.SetPreviewDisplay(holder);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(@"           ERROR: ", ex.Message);
            }
        }

        public void SurfaceDestroyed(ISurfaceHolder holder)
        {
            if (Preview != null)
            {
                Preview.StopPreview();
            }
        }

        public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
        {
            var parameters = Preview.GetParameters();
            parameters.SetPreviewSize(previewSize.Width, previewSize.Height);
            RequestLayout();

            switch (windowManager.DefaultDisplay.Rotation)
            {
                case SurfaceOrientation.Rotation0:
                    camera.SetDisplayOrientation(90);
                    break;
                case SurfaceOrientation.Rotation90:
                    camera.SetDisplayOrientation(0);
                    break;
                case SurfaceOrientation.Rotation270:
                    camera.SetDisplayOrientation(180);
                    break;
            }

            Preview.SetParameters(parameters);
            Preview.StartPreview();
            IsPreviewing = true;
        }

        Camera.Size GetOptimalPreviewSize(IList<Camera.Size> sizes, int w, int h)
        {
            const double AspectTolerance = 0.1;
            double targetRatio = (double)w / h;

            if (sizes == null)
            {
                return null;
            }

            Camera.Size optimalSize = null;
            double minDiff = double.MaxValue;

            int targetHeight = h;
            foreach (Camera.Size size in sizes)
            {
                double ratio = (double)size.Width / size.Height;

                if (Math.Abs(ratio - targetRatio) > AspectTolerance)
                    continue;
                if (Math.Abs(size.Height - targetHeight) < minDiff)
                {
                    optimalSize = size;
                    minDiff = Math.Abs(size.Height - targetHeight);
                }
            }

            if (optimalSize == null)
            {
                minDiff = double.MaxValue;
                foreach (Camera.Size size in sizes)
                {
                    if (Math.Abs(size.Height - targetHeight) < minDiff)
                    {
                        optimalSize = size;
                        minDiff = Math.Abs(size.Height - targetHeight);
                    }
                }
            }

            return optimalSize;
        }
    }

我现在要做的是,当单击“捕获”按钮时,我想从 SharedProject 的 CameraPreviewRenderer 中检索一些数据。我在这样的共享项目中的 CameraPage.xaml.cs 中创建了这样的捕获按钮事件。

void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
        {

        }

在该事件中,我想从 Android 项目中的 CameraPreviewRenderer 检索数据。例如,我在 CameraPreviewRenderer 类中创建了一个属性,如下所示。

public string Data { get { return "Message from  the Android Project"; } }

在事件内部,也许我会像这样检索该属性。

Camera.Data //Data would be "Message from the Android Project" and Camera is the custom view.

我怎样才能做到这一点?

【问题讨论】:

  • 在您的渲染器中说CameraPreview cameraPreview; 变量不是MementoApp.Views.CameraPreview 实例是正确的吗?你有 2 个类叫做 CameraPreview?

标签: android xamarin xamarin.forms custom-view


【解决方案1】:

Xamarin.Forms 消息中心是一种方法。只需订阅自定义渲染器类中的事件即可。

另一种方法是在您的 CameraPreview 类而不是 CameraPreviewRenderer 中创建 Data 属性,然后您可以在单击按钮时设置该属性。即,

void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
{
    Camera.Data = ...;
}

现在,您将能够在 CameraPreviewRenderer 类的 OnElementPropertyChanged 方法中获取 Data

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == CameraPreview.DataProperty.PropertyName)
    {
        ... do what you need to do ...
    }
}

更新:最好的方法是使用事件处理程序。在您的 CameraPreview 类中,创建一个事件处理程序和调用它的方法:

public event eventHandler<MyDataObject> OnData;

public void InvokeOnData(MyDataObject obj)
{
    OnData?.Invoke(this, obj);
}

然后在你CameraPreviewRendererOnElementChanged方法中,你可以设置事件处理程序:

protected override void OnElementPropertyChanged(...)
{
    var cameraPreview = (CameraPreview)this.Element;
    cameraPreview.OnData += HandleData;
}

private void HandleData(object sender, MyCameraObject e)
{
    ... do what you need to do ...
}

然后,您可以这样调用它:

void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
{
    Camera.InvokeOnData(your data);
}

【讨论】:

  • 尽管我确信消息传递中心可以解决问题,但我不太希望它能为他的CameraView 提供一个干净、可重用和解耦的解决方案... =/
  • 是的,我个人在我的应用程序中根本不使用消息中心
  • 我也没有。我尽我所能避免它。
  • 在CameraPreview 类中添加Data 属性,您的意思是共享项目中的CameraPreview 类。是吗?
  • 是的,类似于您已经设置了Camera BindableProperty。这样当属性值发生变化时,您可以在自定义渲染器的 OnElementPropertyChanged 方法中监听它
【解决方案2】:

你可以在ButtonCapturePhoto_Clicked方法中使用DependencyService,下面是我的demo:

在SharedProject中,定义接口IGetData

namespace MementoApp
{
    public interface IGetData
    {
        string getData();
    }
}

在Android项目中,实现接口:

[assembly: Xamarin.Forms.Dependency(typeof(GetDataImpl))]
namespace MementoApp.Droid
{
    public class GetDataImpl :IGetData
    {
        public string getData()
        {
            //in this method, you can also use other approaches to get the data, like delegate/event/interface.
            CameraPreviewRenderer cpr= new CameraPreviewRenderer(Application.Context);
            cpr.initData();
            return cpr.Data;
        }
    }
}

你的CameraPreviewRenderer加这些(我用一个方法-initData模拟Data的生成):

public string Data;
public void initData() {
    Data= "Message from  the Android Project";
}

最后,在ButtonCapturePhoto_Clicked 中调用它:

private void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
{
    string data = DependencyService.Get<IGetData>().getData();
    System.Diagnostics.Debug.Write("data====="+data);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    • 2017-10-01
    • 1970-01-01
    • 2017-11-23
    • 2016-05-31
    • 1970-01-01
    相关资源
    最近更新 更多