我正在使用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
,它仍然会滞后。我的行布局中是否只有太多东西?
答案 0 :(得分:1)
在AndroidManifest.xml中尝试一次
android:hardwareAccelerated="true"
答案 1 :(得分:0)
我意识到它是滞后的,因为它在每次滚动浏览列表时都会加载.ttf文件。加载它只解决了一次问题。