【问题标题】:Custom View Cell with Status具有状态的自定义视图单元格
【发布时间】:2017-11-03 14:25:57
【问题描述】:

我正在构建一个 Xamarin Forms 应用程序,该应用程序将有一个 ListView,其中包含一个具有以下布局的自定义单元格:

红色部分旨在作为状态指示器,而不是披露按钮。我很高兴我需要做些什么来将值绑定到它以改变颜色,但是我不确定实际绘制形状的最佳方法?我不想使用图像,我宁愿在代码中创建形状。

【问题讨论】:

    标签: listview xamarin.forms custom-view skiasharp


    【解决方案1】:

    像这样的简单形状不应该保证使用 SkiaSharp 画布。您可以使用平台渲染器和原生绘图 API 作为路径来绘制此形状。

    表单控件

    public class StatusIndicator : View
    {
        public static readonly BindableProperty BorderWidthProperty =
            BindableProperty.Create(
                "BorderWidth", typeof(int), typeof(StatusIndicator),
                defaultValue: 2);
    
        public int BorderWidth
        {
            get { return (int)GetValue(BorderWidthProperty); }
            set { SetValue(BorderWidthProperty, value); }
        }
    
        public static readonly BindableProperty BorderColorProperty =
            BindableProperty.Create(
                "BorderColor", typeof(Color), typeof(StatusIndicator),
                defaultValue: Color.Black);
    
        public Color BorderColor
        {
            get { return (Color)GetValue(BorderColorProperty); }
            set { SetValue(BorderColorProperty, value); }
        }
    
        public static readonly BindableProperty FillColorProperty =
            BindableProperty.Create(
                "FillColor", typeof(Color), typeof(StatusIndicator),
                defaultValue: Color.Gray);
    
        public Color FillColor
        {
            get { return (Color)GetValue(FillColorProperty); }
            set { SetValue(FillColorProperty, value); }
        }
    }
    

    Android 渲染器

    public class StatusIndicatorRenderer : ViewRenderer<StatusIndicator, AView>
    {
        protected override void OnElementPropertyChanged(
            object sender,
            PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
    
            if (e.PropertyName == nameof(StatusIndicator.FillColor) 
                || e.PropertyName == nameof(StatusIndicator.BorderColor)
                || e.PropertyName == nameof(StatusIndicator.BorderWidth))
            Invalidate();
        }
    
        protected override void DispatchDraw(global::Android.Graphics.Canvas canvas)
        {
            var indicator = this.Element;
            if (indicator == null)
            {
                base.DispatchDraw(canvas);
                return;
            }
    
            var borderColor = indicator.BorderColor;
            var fillColor = indicator.FillColor;
    
            var widthInDp = (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, indicator.BorderWidth, Context.Resources.DisplayMetrics);
            var clipBounds = canvas.ClipBounds;
            var bounds = new Rect(clipBounds.Left, clipBounds.Top, clipBounds.Right, clipBounds.Bottom);
            bounds.Inset(widthInDp / 2, widthInDp / 2);
    
            var midPoint = new float[] { (bounds.Right - bounds.Left) / 2, (bounds.Bottom - bounds.Top) / 2 };
            var points = new float[,] {
                { bounds.Left, bounds.Top },
                { midPoint[0], bounds.Top },
                { bounds.Right, midPoint[1] },
                { midPoint[0], bounds.Bottom },
                { bounds.Left, bounds.Bottom },
                { bounds.Left, bounds.Top },
            };
    
            var paint = new Paint();
            paint.AntiAlias = true;
    
            var path = new Path();
            SetPath(path, points);
            path.SetFillType(Path.FillType.EvenOdd);
            path.Close();
    
            paint.Color = fillColor.ToAndroid();
            paint.SetStyle(Paint.Style.Fill);
            canvas.DrawPath(path, paint);
    
            paint.StrokeWidth = widthInDp;
            paint.Color = borderColor.ToAndroid();
            paint.SetStyle(Paint.Style.Stroke);
            canvas.DrawPath(path, paint);
    
            base.DispatchDraw(canvas);
        }
    
        private static void SetPath(Path path, float[,] points)
        {
            path.MoveTo(points[0, 0], points[0, 1]);
            path.LineTo(points[1, 0], points[1, 1]);
            path.LineTo(points[2, 0], points[2, 1]);
            path.LineTo(points[3, 0], points[3, 1]);
            path.LineTo(points[4, 0], points[4, 1]);
            path.LineTo(points[5, 0], points[5, 1]);
        }
    }
    

    iOS 渲染器

    public class StatusIndicatorRenderer
        : ViewRenderer<StatusIndicator, UIView>
    {
        protected override void OnElementChanged(
            ElementChangedEventArgs<StatusIndicator> e)
        {
            base.OnElementChanged(e);
        }
    
        protected override void OnElementPropertyChanged(
            object sender,
            PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
    
            if (e.PropertyName == nameof(StatusIndicator.FillColor)
                || e.PropertyName == nameof(StatusIndicator.BorderColor)
                || e.PropertyName == nameof(StatusIndicator.BorderWidth))
                SetNeedsDisplay();
        }
    
        public override void Draw(CoreGraphics.CGRect rect)
        {
            base.Draw(rect);
            RemoveLayer<IndicatorLayer>();
    
            if (Element == null)
                return;
    
            var strokeColor = Element.BorderColor.ToCGColor();
            var fillColor = Element.FillColor.ToCGColor();
    
            var offset = Element.BorderWidth / 2;
            var bounds = new CoreGraphics.CGRect(rect.Left + offset, 
                                                 rect.Top + offset, 
                                                 rect.Width + Element.BorderWidth, 
                                                 rect.Height + Element.BorderWidth);
    
            var midPoint = new CoreGraphics.CGPoint((bounds.Right - bounds.Left) / 2, (bounds.Bottom - bounds.Top) / 2);
            var points = new List<CoreGraphics.CGPoint>() {
                new CoreGraphics.CGPoint(bounds.Left, bounds.Top),
                new CoreGraphics.CGPoint(midPoint.X, bounds.Top),
                new CoreGraphics.CGPoint(bounds.Right, midPoint.Y),
                new CoreGraphics.CGPoint(midPoint.X, bounds.Bottom),
                new CoreGraphics.CGPoint(bounds.Left, bounds.Bottom),
                new CoreGraphics.CGPoint(bounds.Left, bounds.Top),
            };
    
            var path = new CoreGraphics.CGPath();
            path.AddLines(points.ToArray());
            path.CloseSubpath();
    
            var shapeLayer = new IndicatorLayer()
            {
                Path = path,
                StrokeColor = strokeColor,
                LineWidth = Element.BorderWidth,
                FillColor = fillColor,
            };
    
            ReplaceOrInsertLayer(shapeLayer);
        }
    
        void RemoveLayer<T>() where T : CAShapeLayer
        {
            var existingLayer = NativeView.Layer.Sublayers?.OfType<T>().FirstOrDefault();
            //This is needed to get every background redrawn if the color changes on runtime
            if (existingLayer != null)
            {
                existingLayer.RemoveFromSuperLayer();
            }
        }
    
        void ReplaceOrInsertLayer<T>(T layer) where T : CAShapeLayer
        {
            var existingLayer = NativeView.Layer.Sublayers?.OfType<T>().FirstOrDefault();
            //This is needed to get every background redrawn if the color changes on runtime
            if (existingLayer != null)
            {
                NativeView.Layer.ReplaceSublayer(existingLayer, layer);
            }
            else
            {
                NativeView.Layer.InsertSublayer(layer, 0);
            }
        }
    
        public class IndicatorLayer : CAShapeLayer { }
    }
    

    示例用法

    <ViewCell ..>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="50" />
            </Grid.ColumnDefinitions>
            <Label Text="Random Text" HorizontalOptions="Fill" />
            <local:StatusIndicator Grid.Column="1" FillColor="{Binding}" BorderColor="Black" />
        </Grid>
    </ViewCell>
    

    【讨论】:

    • 这太棒了!非常感谢您非常完整的回复!
    • @markpirvine:太好了!很高兴知道答案很有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 1970-01-01
    • 1970-01-01
    • 2012-07-10
    • 1970-01-01
    相关资源
    最近更新 更多