Reputation: 1307
I've got a fairly simple XAML form in my Xamarin project, which is supposed to show two views packed in a vertical StackLayout
, a CameraView
at the top and a ListView
at the bottom. The CameraView
shall use up most of the space. The ListView
shall not exceed 4-5 items, but will change during runtime. Here's the XAML I've got so far:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Foo.CamPage"
xmlns:ctrl="clr-namespace:Foo.Controls">
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<ctrl:CameraView HeightRequest="2000"/>
<StackLayout VerticalOptions="FillAndExpand">
<ListView>
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Entry 1</x:String>
<x:String>Entry 2</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
As you can see, my first idea was to put the ListView
into another nested StackLayout
. I also played with the HeightRequest
of the CameraView
However, I don't want to set a specific height of the camera view, I want the ListView
to take up as much space as needed and the CameraView
to adapt accordingly. Is it possible to define percentages in the sizes?
If it helps, here's the CameraView
:
namespace Foo.Controls
{
public class CameraView : View
{
public static readonly BindableProperty CameraProperty =
BindableProperty.Create(
propertyName: "Camera",
returnType: typeof(CameraOptions),
declaringType: typeof(CameraView),
defaultValue: CameraOptions.Rear);
public CameraOptions Camera
{
get { return (CameraOptions)GetValue(CameraProperty); }
set { SetValue(CameraProperty, value); }
}
public Func<Task<ImageSource>> TakePhotoAsync { set; get; }
public Func<CameraOptions, bool> SwitchCamera { set; get; }
}
}
And this is the associated iOS renderer:
[assembly: ExportRenderer(typeof(CameraView), typeof(CamPreviewRenderer))]
namespace Foo.iOS.Renderers
{
public class CamPreviewRenderer : ViewRenderer<CameraView, UICamPreview>
{
UICamPreview uiCameraPreview;
protected override void OnElementChanged(ElementChangedEventArgs<CameraView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
uiCameraPreview = new UICamPreview(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;
}
}
}
}
And this is the UICamPreview
:
namespace Foo.iOS.Renderers
{
public class UICamPreview : UIView
{
AVCaptureVideoPreviewLayer previewLayer;
CameraOptions cameraOptions;
public event EventHandler<EventArgs> Tapped;
public AVCaptureSession CaptureSession { get; private set; }
public bool IsPreviewing { get; set; }
public UICamPreview(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;
}
}
}
Any hint appreciated.
Upvotes: 2
Views: 1460
Reputation: 496
Have you tried overriding OnLayout, usually code is like this.
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
try
{
base.OnLayout(changed, l, t, r, b);
if (!changed)
return;
int msw = 50;
int msh = 50;
int layoutWidth = r - l;
int layoutHeight = b - t;
msw = MeasureSpec.MakeMeasureSpec(layoutWidth, MeasureSpecMode.Exactly);
msh = MeasureSpec.MakeMeasureSpec(layoutHeight, MeasureSpecMode.Exactly);
view.Measure(msw, msh);
view.Layout(0, 0, layoutWidth, layoutHeight);
}
catch (System.Exception ex)
{
Logger.WriteException(ex);
}
}
Upvotes: 0
Reputation: 833
Put those views into Grid with 2 rows. The one that you want to use as much space as you require set Height to Auto and the other view that suppose to take up the remaining space set Height to *. Something like this
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ctrl:CameraView Grid.Row="0"/>
<StackLayout VerticalOptions="FillAndExpand" Grid.Row="1">
<ListView>
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Entry 1</x:String>
<x:String>Entry 2</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</Grid>
</StackLayout>
</StackLayout>
</ContentPage.Content>
Please bear in mind that ListView is a very buggy when it comes to layout. You might have to play around with setting Hard coded values for Height anyway.
Upvotes: 1