在画布上绘图时出现致命信号11错误

时间:2017-10-18 11:08:07

标签: c# android canvas xamarin.android

我遇到了一个问题,即在我的回收站视图中滑动单元格导致应用程序抛出信号11错误。

在谷歌/ stackoverflow上查看这个问题后,我发现有些人似乎能够通过将视图层设置为软件渲染而不是硬件加速渲染来修复它 - 不幸的是在我的情况下没有修复问题对我来说,我也找不到任何与修复它的方式完全不同的建议。

我试图通过设置"容器"来解决这个问题。使用该方法以及viewholder的ItemView - 两者都没有阻止错误。

有人能够告诉我我的情景有什么特别之处,我无法让它发挥作用吗?

错误发生在LeftRightSwipeController.cs的第242行

完整的复制样本可在github上获得: https://github.com/devsolvum/Signal-11-CanvasDraw

LeftRightSwipeController.cs

using System;
using System.Collections.Generic;
using System.Threading;
using Android.Graphics;
using Android.Support.V7.Widget;
using Android.Support.V7.Widget.Helper;
using Android.Views;
using Math = Java.Lang.Math;
using Object = Java.Lang.Object;

namespace App1.Domain
{
    // https://medium.com/@fanfatal/android-swipe-menu-with-recyclerview-8f28a235ff28
    // https://github.com/FanFataL/swipe-controller-demo/blob/master/app/src/main/java/pl/fanfatal/swipecontrollerdemo/SwipeController.java

    public enum SwipeState
    {
        Default,
        LeftOpen,
        RightOpen
    }

    public class LeftRightSwipeController : ItemTouchHelper.Callback
    {
        private bool _swipeBack = false;

        private SwipeState _currentSwipeState = SwipeState.Default;

        private RectF _buttonInstance = null;

        public override int ConvertToAbsoluteDirection(int flags, int layoutDirection)
        {
            if (_swipeBack)
            {
                _swipeBack = _currentSwipeState != SwipeState.Default;
                return 0;
            }
            return base.ConvertToAbsoluteDirection(flags, layoutDirection);
        }

        public override void OnChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
            float dY, int actionState, bool isCurrentlyActive)
        {
            var measuredsize = GetMeasuredSize(GetContainerArea(dX), viewHolder);
            if (measuredsize.width <= 0 || measuredsize.height <= 0)
                return;

            var clippedX = dX >= 0 ? Math.Min(dX, measuredsize.width) : Math.Max(dX, -measuredsize.width);

            if (actionState == ItemTouchHelper.ActionStateSwipe)
            {
                if (_currentSwipeState != SwipeState.Default)
                {
                    if (_currentSwipeState == SwipeState.LeftOpen)
                        clippedX = Math.Max(clippedX, measuredsize.width);
                    if (_currentSwipeState == SwipeState.RightOpen)
                        clippedX = Math.Min(clippedX, -measuredsize.width);

                    base.OnChildDraw(c, recyclerView, viewHolder, clippedX, dY, actionState, isCurrentlyActive);
                }
                else
                {
                    SetTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
                }
            }

            if (_currentSwipeState == SwipeState.Default)
            {
                base.OnChildDraw(c, recyclerView, viewHolder, clippedX, dY, actionState, isCurrentlyActive);
            }

            DrawButtons(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive, _currentSwipeState, false);
        }

        private (int width, int height) GetMeasuredSize(int containerArea, RecyclerView.ViewHolder viewHolder)
        {
            if (viewHolder is IViewHolderButtonContainer buttonContainer)
            {
                int widthSpec = View.MeasureSpec.MakeMeasureSpec(viewHolder.ItemView.Width, MeasureSpecMode.AtMost);
                int heightSpec = View.MeasureSpec.MakeMeasureSpec(viewHolder.ItemView.Height, MeasureSpecMode.AtMost);

                var container = buttonContainer.CreateButtonContainer(containerArea);
                container.Measure(widthSpec, heightSpec);

                return (container.MeasuredWidth, container.MeasuredHeight);
            }

            return (0, 0);
        }

        private void SetItemsClickable(RecyclerView recyclerView, bool isClickable)
        {
            for (int i = 0; i < recyclerView.ChildCount; ++i)
            {
                recyclerView.GetChildAt(i).Clickable = isClickable;
            }
        }

        private void SetTouchListener(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
            float dY, int actionState, bool isCurrentlyActive)
        {
            recyclerView.SetOnTouchListener(
                new TouchListenerDelegate(
                    (v, @event) =>
                    {
                        _swipeBack = @event.Action == MotionEventActions.Cancel || @event.Action == MotionEventActions.Up;
                        if (_swipeBack)
                        {
                            float containerWidth = GetMeasuredSize(GetContainerArea(dX), viewHolder).width;

                            if (dX <= -containerWidth)
                            {
                                _currentSwipeState = SwipeState.RightOpen;
                            }
                            else if (dX >= containerWidth)
                            {
                                _currentSwipeState = SwipeState.LeftOpen;
                            }

                            if (_currentSwipeState != SwipeState.Default)
                            {
                                DrawButtons(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive, _currentSwipeState, true);
                                SetTouchDownListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
                                SetItemsClickable(recyclerView, false);
                            }
                        }

                        return false;
                    }));
        }

        private void SetTouchDownListener(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
            float dY, int actionState, bool isCurrentlyActive)
        {
            recyclerView.SetOnTouchListener(
                new TouchListenerDelegate(
                    (view, args) =>
                    {
                        if (args.Action == MotionEventActions.Down)
                        {
                            SetTouchUpListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
                        }
                        return false;
                    }));
        }

        private void SetTouchUpListener(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
            float dY, int actionState, bool isCurrentlyActive)
        {
            recyclerView.SetOnTouchListener(
                new TouchListenerDelegate(
                    (recyclerViewInDelegate, outerArgs) =>
                    {
                        if (outerArgs.Action == MotionEventActions.Up)
                        {
                            recyclerView.SetOnTouchListener(
                                new TouchListenerDelegate(
                                    (innerView, innerArgs) => { return false; }));

                            SetItemsClickable(recyclerView, true);
                            _swipeBack = false;

                            //                          if (_buttonsActions != null && _buttonInstance != null &&
                            //                              _buttonInstance.Contains(outerArgs.GetX(), outerArgs.GetY()))
                            //                          {
                            //                              if (_currentSwipeState == SwipeState.LeftOpen)
                            //                              {
                            //                                  _buttonsActions.OnLeftClicked(viewHolder.AdapterPosition);
                            //                              }
                            //                              else if (_currentSwipeState == SwipeState.RightOpen)
                            //                              {
                            //                                  _buttonsActions.OnRightClicked(viewHolder.AdapterPosition);
                            //                              }
                            //                          }

                            _currentSwipeState = SwipeState.Default;


                            // folgende codezeile behebt einen darstellungsbug, wenn man eine schaltfläche klickt
                            Thread.Sleep(100);
                            base.OnChildDraw(c, recyclerView, viewHolder, 0, dY, actionState, isCurrentlyActive);
                        }

                        return false;
                    }));
        }


        private void DrawButtons(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
            float dY, int actionState, bool isCurrentlyActive, SwipeState swipeState, bool swipeEnd)
        {
            var validState = isCurrentlyActive || swipeState != SwipeState.Default;


            if (!validState)
                return;

            if (viewHolder is IViewHolderButtonContainer buttonContainer)
            {
                //              int widthSpec = View.MeasureSpec.MakeMeasureSpec(ViewGroup.LayoutParams.WrapContent, MeasureSpecMode.Unspecified);

                int widthSpec = View.MeasureSpec.MakeMeasureSpec(viewHolder.ItemView.Width, MeasureSpecMode.AtMost);
                int heightSpec = View.MeasureSpec.MakeMeasureSpec(viewHolder.ItemView.Height, MeasureSpecMode.AtMost);

                var containerArea = GetContainerArea(dX);
                var container = buttonContainer.CreateButtonContainer(containerArea);
                container.Measure(widthSpec, heightSpec);

                var clippedWidth = GetClippedWidth(viewHolder.ItemView.Width, dX, container.MeasuredWidth);
                if (clippedWidth <= 0)
                    return;
                var clippedHeight = GetClippedHeight(viewHolder.ItemView.Height, dY, container.MeasuredHeight);
                if (clippedHeight <= 0)
                    return;

                var viewLeft = dX >= 0 ? viewHolder.ItemView.Left : viewHolder.ItemView.Right - clippedWidth;
                var viewTop = viewHolder.ItemView.Top;
                var viewRight = viewLeft + clippedWidth;
                var viewBottom = viewTop + clippedHeight;

                container.Layout(viewLeft, viewTop, viewRight, viewBottom);

                container.SetBackgroundColor(Color.Orange);

                //              var canvasX = dX >= 0 ? viewHolder.ItemView.Left : viewHolder.ItemView.Left + viewHolder.ItemView.Width;

                //                  var paint = new Paint();
                //                  paint.Color = Color.Aqua;
                //                  canvas.DrawRect(viewLeft, viewTop, viewRight, viewBottom, paint);

                // Translate the canvas so the view is drawn at the proper coordinates
                canvas.Save();
                canvas.Translate(viewLeft, viewHolder.ItemView.Top);

                if (swipeEnd)
                {
                    viewHolder.ItemView.Invalidate();
                    container.Invalidate();
                }

                //Draw the View and clear the translation
                container.Draw(canvas);
                canvas.Restore();
            }
        }

        private static int GetContainerArea(float dX)
        {
            return dX >= 0 ? 0 : 1;
        }

        private int GetClippedHeight(int viewHolderHeight, float dY, int measured)
        {
            return viewHolderHeight;
        }

        private int GetClippedWidth(int viewHolderWidth, float dX, int measured)
        {
            if (viewHolderWidth <= 0)
                return 0;

            return Math.Min(measured, (int) Math.Ceil(Math.Abs(dX)));
        }


        public override int GetMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
        {
            return MakeMovementFlags(0, ItemTouchHelper.Left | ItemTouchHelper.Right);
        }

        public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
            RecyclerView.ViewHolder target)
        {
            return false;
        }

        public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction)
        {
        }

        private class TouchListenerDelegate : Object, View.IOnTouchListener
        {
            public void Dispose()
            {
                Callback = null;
            }

            public TouchListenerDelegate(Func<View, MotionEvent, bool> callback)
            {
                Callback = callback;
            }

            public Func<View, MotionEvent, bool> Callback { get; set; }

            public bool OnTouch(View v, MotionEvent e)
            {
                return Callback(v, e);
            }
        }
    }

    public interface IViewHolderButtonContainer
    {
        /// <summary>
        /// Erstellt container für die Buttons
        /// </summary>
        /// <param name="containerArea">0 = links, 1 = rechts</param>
        /// <returns></returns>
        View CreateButtonContainer(int containerArea);
    }
}

MainActivity.cs

using System;
using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Widget;
using Android.OS;
using Android.Runtime;
using Android.Support.V7.Widget;
using Android.Support.V7.Widget.Helper;
using Android.Views;
using App1.Domain;

namespace App1
{
    [Activity(Label = "App1", MainLauncher = true)]
    public class MainActivity : Activity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Set our view from the "main" layout resource
            var mainView = LayoutInflater.Inflate(Resource.Layout.Main, null, true) as LinearLayout;
            SetContentView(mainView);
            mainView.SetBackgroundColor(Color.Red);
            var recyclerView = new RecyclerView(this);
            var recyclerAdapter = new RecyclerAdapter();
            for (int i = 0; i < 30; i++)
            {
                recyclerAdapter.Items.Add($"Entry {i}.");
            }
            recyclerView.SetAdapter(recyclerAdapter);
            var linearLayoutManager = new LinearLayoutManager(this);
            var swipeController = new LeftRightSwipeController();
            var ith = new ItemTouchHelper(swipeController);
            ith.AttachToRecyclerView(recyclerView);
            recyclerView.SetLayoutManager(linearLayoutManager);
            recyclerView.SetBackgroundColor(Color.Orange);
            mainView.AddView(recyclerView);
        }

        public class RecyclerAdapter : RecyclerView.Adapter
        {
            public List<string> Items { get; set; } = new List<string>();

            public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
            {
                var data = Items[position];
                var textView = holder.ItemView.FindViewById<TextView>(Android.Resource.Id.Text1);
                if (textView != null)
                {
                    textView.Text = data;
                }
            }

            public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
            {
                var inflater = Android.Views.LayoutInflater.FromContext(parent.Context);
                var view = inflater.Inflate(Android.Resource.Layout.SimpleListItem1, parent, false);
                return new CustomViewHolder(view);
            }

            public override int ItemCount => Items.Count;
        }

        public class CustomViewHolder : RecyclerView.ViewHolder, IViewHolderButtonContainer
        {
            public CustomViewHolder(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
            {
            }

            public CustomViewHolder(View itemView) : base(itemView)
            {
            }

            private View _leftContainer;
            private View _rightContainer;

            public View CreateButtonContainer(int containerArea)
            {
                if (containerArea == 0)
                {
                    return _leftContainer ?? (_leftContainer = CreateContainer(containerArea));
                }
                else
                {
                    return _rightContainer ?? (_rightContainer = CreateContainer(containerArea));
                }
            }

            private string GetButtonText(int containerArea)
            {
                return $"Button area {containerArea}";
            }

            private View CreateContainer(int containerArea)
            {
                var textView = new TextView(Android.App.Application.Context);
                textView.SetBackgroundColor(Color.Blue);
                textView.Text = GetButtonText(containerArea);
                return textView;
            }
        }
    }
}

任何尝试和解决此问题的建议都是非常受欢迎的。

1 个答案:

答案 0 :(得分:0)

将recyclerview上的图层类型设置为软件修复了该问题。结果我选择了错误的视图来设置它。