RecyclerView滚动滞后

时间:2018-02-20 22:21:36

标签: android xamarin.android mvvmcross

我正在使用Xamarin.Android和MvvmCross编写Android应用程序。但我认为我的问题一般适用于原生Android。

我写了一个自定义RecyclerView,这样当我在一行上滑动时,我可以在旁边放置按钮。这一切都运作良好,除非我滚动列表时它会滞后很多。我唯一可以解决的问题是删除行布局的xml中除了单个TextView之外的所有内容,但我确信有一种方法可以在保留行内容的同时修复滞后布局。这是我的(相关)代码:

public class SwipeMvxRecyclerView : MvxRecyclerView
{
    private SwipeMvxRecyclerStateHandler _stateHandler;

    private ICommand _leftButtonClick;
    private ICommand _rightButtonClick;
    private ICommand _mainButtonClick;

    public ICommand LeftButtonClick
    {
        get { return _leftButtonClick; }
        set
        {
            if (ReferenceEquals(_leftButtonClick, value))
            {
                return;
            }

            _leftButtonClick = value;
        }
    }

    public ICommand RightButtonClick
    {
        get { return _rightButtonClick; }
        set
        {
            if (ReferenceEquals(_rightButtonClick, value))
            {
                return;
            }

            _rightButtonClick = value;
        }
    }

    public ICommand MainButtonClick
    {
        get { return _mainButtonClick; }
        set
        {
            if (ReferenceEquals(_mainButtonClick, value))
            {
                return;
            }

            _mainButtonClick = value;
        }
    }

    public View SwipeView { get; set; }

    public SwipeMvxRecyclerView(Context context, IAttributeSet attr) : base(context, attr)
    {
        Initialize();
    }

    public SwipeMvxRecyclerView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
    {
        Initialize();
    }

    public override bool OnTouchEvent(MotionEvent e)
    {
        if (e.Action == MotionEventActions.Down)
        {
            var v = FindChildViewUnder(e.GetX(), e.GetY());
            SwipeView = v.FindViewById<View>(Resource.Id.swipe_view);
        }

        switch(e.Action)
        {
            case MotionEventActions.Down: _stateHandler.Down(SwipeView, e.GetX(), e.GetY()); break;
            case MotionEventActions.Move: _stateHandler.Move(e.GetX(), e.GetY()); break;
            case MotionEventActions.Up: _stateHandler.Up(e.GetX()); break;
        }

        if (_stateHandler.DisableScroll)
        {
            return true;
        }
        else
        {
            return base.OnTouchEvent(e);
        }
    }

    private void Initialize()
    {
        _stateHandler = new SwipeMvxRecyclerStateHandler(Context.Resources.GetDimension(Resource.Dimension.search_result_item_button_width));
        _stateHandler.MainItemClicked += OnMainButtonClicked;
        _stateHandler.LeftItemClicked += OnLeftButtonClicked;
        _stateHandler.RightItemClicked += OnRightButtonClicked;
    }

    private void OnMainButtonClicked(object sender, EventArgs e)
    {
        var viewHolder = FindContainingViewHolder(SwipeView);

        var item = Adapter.GetItem(viewHolder.LayoutPosition); // What different is viewHolder.AdapterPosition? I tested it with 100 items and it's always the same, but I'm not sure if this will never break...

        MainButtonClick.Execute(item);
    }

    private void OnLeftButtonClicked(object sender, EventArgs e)
    {
        var viewHolder = FindContainingViewHolder(SwipeView);

        var item = Adapter.GetItem(viewHolder.LayoutPosition); // What different is viewHolder.AdapterPosition? I tested it with 100 items and it's always the same, but I'm not sure if this will never break...

        LeftButtonClick.Execute(item);
    }

    private void OnRightButtonClicked(object sender, EventArgs e)
    {
        var viewHolder = FindContainingViewHolder(SwipeView);

        var item = Adapter.GetItem(viewHolder.LayoutPosition); // What different is viewHolder.AdapterPosition? I tested it with 100 items and it's always the same, but I'm not sure if this will never break...

        RightButtonClick.Execute(item);
    }
}

我写了这个类来处理我的自定义Recycleview上的滑动/滚动事件:

public class SwipeMvxRecyclerStateHandler
{
    private enum SwipeState
    {
        Idle = 0,
        Scrolling = 1,
        Swiping = 2
    }

    private enum SwipePosition
    {
        Center = 0,
        Left = 1,
        Right = 2
    }

    private SwipeState _currentSwipeState;
    private SwipePosition _currentSwipePosition;

    private View _swipeView;

    private bool _isClicking;
    private float _originalClickX;
    private float _originalClickY;
    private float _originalPositionX;

    private float _buttonWidth;
    private float _swipingThreshold = 20;
    private float _scrollingThreshold = 20;
    private int _snapAnimationDuration = 150;

    public event EventHandler MainItemClicked;
    public event EventHandler LeftItemClicked;
    public event EventHandler RightItemClicked;

    public bool DisableScroll { get { return _currentSwipeState == SwipeState.Swiping; }}

    public SwipeMvxRecyclerStateHandler(float buttonWidth)
    {
        _buttonWidth = buttonWidth;
    }

    public void Down(View swipeView, float x, float y)
    {
        _isClicking = true;
        var previousSwipeView = _swipeView;
        _swipeView = swipeView;

        // Close it if it's still open
        if (previousSwipeView != null && previousSwipeView != _swipeView)
        {
            previousSwipeView.Animate()
                             .X(0)
                             .SetDuration(_snapAnimationDuration)
                             .Start();

            _originalPositionX = 0;
        }

        _currentSwipeState = SwipeState.Idle;
        _originalClickX = x;
        _originalClickY = y;
    }

    public void Move(float x, float y)
    {
        _isClicking = false;

        switch(_currentSwipeState)
        {
            case SwipeState.Idle: MoveOnIdleState(x, y); break;
            case SwipeState.Swiping: MoveOnSwipingState(x); break;
            // Nothing to do on SwipeState.Scrolling
        }
    }

    public void Up(float x)
    {
        if (_currentSwipeState != SwipeState.Scrolling)
        {
            if (_isClicking)
            {
                FindAndExecuteClickedButton(x);
            }
            else
            {
                SnapToClosestPosition(x);
            }
        }

        _isClicking = false;
        _currentSwipeState = SwipeState.Idle;
    }

    private void FindAndExecuteClickedButton(float x)
    {
        // I am making the assumption that there are 2 buttons, one on the left, another on the right.
        // You will need to modify this code if you want to add more buttons.

        if(_currentSwipePosition == SwipePosition.Center)
        {
            MainItemClicked?.Invoke(null, EventArgs.Empty);
        }
        else if(_currentSwipePosition == SwipePosition.Left && x > _swipeView.Width - _buttonWidth && x < _swipeView.Width)
        {
            RightItemClicked?.Invoke(null, EventArgs.Empty);
        }
        else if(_currentSwipePosition == SwipePosition.Right && x > 0 && x < _buttonWidth)
        {
            LeftItemClicked?.Invoke(null, EventArgs.Empty);
        }
        else
        {
            _swipeView.Animate()
                  .X(0)
                  .SetDuration(_snapAnimationDuration)
                  .Start();

            _originalPositionX = 0;
        }
    }

    private void SnapToClosestPosition(float x)
    {
        float moveX = x - _originalClickX;
        float newPositionX = _originalPositionX + moveX;

        float distanceToShowLeftButton = Math.Abs(_buttonWidth - newPositionX);
        float distanceToShowRightButton = Math.Abs(-_buttonWidth - newPositionX);
        float distanceToCenter = Math.Abs(newPositionX);

        float positionToSnapTo;

        if (distanceToShowLeftButton > distanceToCenter)
        {
            if (distanceToShowRightButton > distanceToCenter)
            {
                positionToSnapTo = 0;
                _currentSwipePosition = SwipePosition.Center;
            }
            else
            {
                positionToSnapTo = -_buttonWidth;
                _currentSwipePosition = SwipePosition.Left;
            }
        }
        else if (distanceToShowRightButton > distanceToCenter)
        {
            positionToSnapTo = _buttonWidth;
            _currentSwipePosition = SwipePosition.Right;
        }
        else
        {
            positionToSnapTo = 0;
            _currentSwipePosition = SwipePosition.Center;
        }

        _swipeView.Animate()
                  .X(positionToSnapTo)
                  .SetDuration(_snapAnimationDuration)
                  .Start();

        _originalPositionX = positionToSnapTo;
    }

    private void MoveOnIdleState(float x, float y)
    {
        if (Math.Abs(x - _originalClickX) > _swipingThreshold)
        {
            _currentSwipeState = SwipeState.Swiping;
        }
        else if (Math.Abs(y - _originalClickY) > _scrollingThreshold)
        {
            _currentSwipeState = SwipeState.Scrolling;
        }
    }

    private void MoveOnSwipingState(float x)
    {
        float moveX = x - _originalClickX;
        float newPositionX = _originalPositionX + moveX;

        if(x < _originalClickX)
        {
            newPositionX += _swipingThreshold;
        }
        else
        {
            newPositionX -= _swipingThreshold;
        }

        if (newPositionX > _buttonWidth)
        {
            _swipeView.Animate()
                      .X(_buttonWidth)
                      .SetDuration(0)
                      .Start();
        }
        else if (newPositionX < -_buttonWidth)
        {
            _swipeView.Animate()
                      .X(-_buttonWidth)
                      .SetDuration(0)
                      .Start();
        }
        else
        {
            _swipeView.Animate()
                      .X(newPositionX)
                      .SetDuration(0)
                      .Start();
        }
    }
}

}

这是行布局的xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
    android:id="@+id/left_button"
    android:layout_width="@dimen/search_result_item_button_width"
    android:layout_height="@dimen/search_result_item_height"
    android:gravity="center"
    android:background="@color/add_to_wishlist_button_background">
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="@dimen/text_huge"
            android:textColor="@color/white"
            local:MvxBind="Text IsAddedToWishlist, Converter=BoolToFontAwesome, ConverterParameter=fa-heart-o|fa-heart; Style ., Converter=String, ConverterParameter=fonts/fontawesome.ttf" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/margin_medium"
            android:textSize="@dimen/text_tiny"
            android:textColor="@color/white"
            local:MvxBind="Style ., Converter=String, ConverterParameter=fonts/roboto/Roboto-Regular.ttf"
            local:MvxLang="Text add_to_wishlist" />
    </LinearLayout>
</RelativeLayout>
<RelativeLayout
    android:id="@+id/right_button"
    android:layout_width="@dimen/search_result_item_button_width"
    android:layout_height="@dimen/search_result_item_height"
    android:layout_alignParentRight="true"
    android:gravity="center"
    android:background="@color/add_to_cart_button_background">
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="@dimen/text_huge"
            android:textColor="@color/white"
            local:MvxBind="Text IsAddedToCart, Converter=BoolToFontAwesome, ConverterParameter=fa-shopping-cart|fa-cart-plus; Style ., Converter=String, ConverterParameter=fonts/fontawesome.ttf" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/margin_medium"
            android:textSize="@dimen/text_tiny"
            android:textColor="@color/white"
            local:MvxBind="Style ., Converter=String, ConverterParameter=fonts/roboto/Roboto-Regular.ttf"
            local:MvxLang="Text add_to_cart" />
    </LinearLayout>
</RelativeLayout>
<RelativeLayout
    android:id="@+id/swipe_view"
    android:layout_width="match_parent"
    android:layout_height="@dimen/search_result_item_height"
    android:background="@color/white">
    <FrameLayout
        android:id="@+id/thumbnail_container"
        android:layout_width="@dimen/search_result_item_thumbnail_container_width"
        android:layout_height="match_parent"
        android:padding="@dimen/padding_tiny">
        <Mvx.MvxImageView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:scaleType="fitXY"
            android:adjustViewBounds="true"
            local:MvxBind="ImageUrl ThumbnailUrl" />
    </FrameLayout>
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/thumbnail_container"
        android:textSize="@dimen/text_medium"
        android:textColor="@color/black"
        local:MvxBind="Text Title; Style ., Converter=String, ConverterParameter=fonts/roboto/Roboto-Bold.ttf" />
    <TextView
        android:id="@+id/manufacturer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/thumbnail_container"
        android:layout_below="@id/title"
        android:layout_marginTop="@dimen/margin_tiny"
        android:textSize="@dimen/text_tiny"
        android:textColor="@color/text_gray"
        local:MvxBind="Text Manufacturer; Style ., Converter=String, ConverterParameter=fonts/roboto/Roboto-Regular.ttf" />
    <MyProject.Droid.Components.Rating
        android:id="@+id/rating"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/manufacturer"
        android:layout_toRightOf="@id/thumbnail_container"
        android:layout_marginTop="@dimen/margin_tiny"
        local:MvxBind="NumberOfStars NumberOfStars" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/rating"
        android:layout_toRightOf="@id/thumbnail_container"
        android:layout_marginTop="@dimen/margin_tiny"
        android:textSize="@dimen/text_small"
        android:textColor="@color/black"
        local:MvxBind="Text Price, Converter=MoneySign; Style ., Converter=String, ConverterParameter=fonts/roboto/Roboto-Bold.ttf" />
</RelativeLayout>
<View
    android:layout_width="match_parent"
    android:layout_height="@dimen/horizontal_line_height"
    android:layout_alignParentBottom="true"
    android:background="@color/horizontal_line" />

即使我删除了Mvx.MvxImageView,它仍然会滞后。我的行布局中是否只有太多东西?

2 个答案:

答案 0 :(得分:1)

在AndroidManifest.xml中尝试一次

android:hardwareAccelerated="true"

答案 1 :(得分:0)

我意识到它是滞后的,因为它在每次滚动浏览列表时都会加载.ttf文件。加载它只解决了一次问题。