Xamarin Android: как создать панель отмены
Я хочу знать, как создать панель отмены для Android Xamarin примерно так.
Я использовал этот код, созданный Dan Ardelean для Xamarin Android, который основывает код из примера Roman Nurik для Android, но он не работает для меня. Я получаю это сообщение:
Невозможно активировать экземпляр типа SwipeDismissListView.MainActivity из собственного дескриптора 0x200019 (key_handle 0xf9f4bcb). --->
System.MissingMethodException: No constructor found for SwipeDismissListView.MainActivity::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership) ---> Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown. --- End of inner exception stack trace --- at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) [0x00054] in <d855bac285f44dda8a0d8510b679b1e2>:0 at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType) [0x00111] in <d855bac285f44dda8a0d8510b679b1e2>:0 --- End of inner exception stack trace --- at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType) [0x0017d] in <d855bac285f44dda8a0d8510b679b1e2>:0 at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type) [0x000b9] in <d855bac285f44dda8a0d8510b679b1e2>:0 at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) [0x00017] in <d855bac285f44dda8a0d8510b679b1e2>:0 at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) [0x00000] in <d855bac285f44dda8a0d8510b679b1e2>:0 at Android.Views.View.get_Context () [0x0001f] in <d855bac285f44dda8a0d8510b679b1e2>:0 at XpressCode.SwipeDismissListViewTouchListener..ctor (Android.Widget.ListView listView, XpressCode.IDismissCallbacks callbacks) [0x00021] in D:\...\Xamarin-SwipeToDismiss-master\SwipeDismissListView\SwipeDismissListViewTouchListener.cs:58 at SwipeDismissListView.MainActivity.OnCreate (Android.OS.Bundle bundle) [0x00086]
Я искал в Интернете примеры кода, но я не нашел ничего подобного.
Я пытался добавить блок-цитату конструктора,
public MainActivity()
{
}
но я получаю ошибку:
{System.InvalidCastException: Specified cast is not valid.
at Android.Widget.AbsListView.SetOnScrollListener (Android.Widget.AbsListView+IOnScrollListener l) [0x0000c] in <d855bac285f44dda8a0d8510b679b1e2>:0
at SwipeDismissListView.MainActivity.OnCreate (Android.OS.Bundle bundle) [0x000a0] in D:\...\Xamarin-SwipeToDismiss-master\SwipeDismissListView\MainActivity.cs:45 }
Затем я удалил добавленный конструктор и добавил блок try... cacth. Приложение перерыв в этой строке:
list.SetOnScrollListener(touchListener.MakeScrollListener());
ЭТО КОДЕКС ССЫЛКИ:
MainActivity.cs
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Collections.Generic;
using XpressCode;
namespace SwipeDismissListView
{
[Activity (Label = "SwipeDismissListView", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity,IDismissCallbacks,IUndoListener
{
UndoBarController mUndoBarController;
ArrayAdapter<String> mAdapter;
ListView list;
protected override void OnCreate (Bundle bundle)
{
try
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
String[] items = new String[20];
for (int i = 0; i < items.Length; i++)
{
items[i] = "Item " + (i + 1);
}
list = FindViewById<ListView>(Resource.Id.lstItems);
mAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, new List<String>(items));
list.Adapter = mAdapter;
SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(list, this);
list.SetOnTouchListener(touchListener);
list.SetOnScrollListener(touchListener.MakeScrollListener());
mUndoBarController = new UndoBarController(FindViewById(Resource.Id.undobar), this);
}
catch (Exception ex)
{
Toast.MakeText(this,"error: " + ex,ToastLength.Short);
}
}
protected override void OnSaveInstanceState (Bundle outState)
{
base.OnSaveInstanceState (outState);
mUndoBarController.onSaveInstanceState(outState);
}
protected override void OnRestoreInstanceState (Bundle savedInstanceState)
{
base.OnRestoreInstanceState (savedInstanceState);
mUndoBarController.onRestoreInstanceState(savedInstanceState);
}
public bool canDismiss (int position)
{
return true;
}
int _position;
public void onDismiss (ListView listView, int[] reverseSortedPositions)
{
foreach (int position in reverseSortedPositions) {
var item = mAdapter.GetItem (position);
mAdapter.Remove(item);
_position = position;
mUndoBarController.ShowUndoBar(false,GetString(Resource.String.undobar_sample_message),item);
}
mAdapter.NotifyDataSetChanged();
}
public void OnUndo(object token) {
mAdapter.Insert ((string)token, _position);
// Perform the undo
}
}
}
SwipeDismissListViewTouchListener.cs
using System;
using Android.Views;
using Android.Widget;
using System.Collections.Generic;
using Android.Graphics;
using Android.Animation;
using Java.Util;
using Android.OS;
namespace XpressCode
{
public interface IDismissCallbacks
{
/**
* Called to determine whether the given position can be dismissed.
*/
bool canDismiss (int position);
/**
* Called when the user has indicated they she would like to dismiss one or more list item
* positions.
*
* @param listView The originating {@link ListView}.
* @param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismiss (ListView listView, int[] reverseSortedPositions);
}
public class SwipeDismissListViewTouchListener:Java.Lang.Object,Android.Views.View.IOnTouchListener
{
// Cached ViewConfiguration and system-wide constant values
int mSlop;
int mMinFlingVelocity;
int mMaxFlingVelocity;
long mAnimationTime;
// Fixed properties
internal ListView mListView;
internal IDismissCallbacks mCallbacks;
internal int mViewWidth = 1;
// Transient properties
internal List<PendingDismissData> mPendingDismisses = new List<PendingDismissData> ();
internal int mDismissAnimationRefCount = 0;
internal float mDownX;
internal float mDownY;
internal bool mSwiping;
internal int mSwipingSlop;
internal VelocityTracker mVelocityTracker;
internal int mDownPosition;
internal View mDownView;
internal bool mPaused;
public SwipeDismissListViewTouchListener (ListView listView, IDismissCallbacks callbacks)
{
ViewConfiguration vc = ViewConfiguration.Get (listView.Context);
mSlop = vc.ScaledTouchSlop;
mMinFlingVelocity = vc.ScaledMinimumFlingVelocity * 16;
mMaxFlingVelocity = vc.ScaledMaximumFlingVelocity;
mAnimationTime = listView.Context.Resources.GetInteger (Android.Resource.Integer.ConfigShortAnimTime);
mListView = listView;
mCallbacks = callbacks;
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
*
* @param enabled Whether or not to watch for gestures.
*/
public void SetEnabled (bool enabled)
{
mPaused = !enabled;
}
/**
* Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the {@link
* ListView} using {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}.
* If a scroll listener is already assigned, the caller should still pass scroll changes through
* to this listener. This will ensure that this {@link SwipeDismissListViewTouchListener} is
* paused during list view scrolling.</p>
*
* @see SwipeDismissListViewTouchListener
*/
public AbsListView.IOnScrollListener MakeScrollListener() {
return new OnScrollListener (this);
}
class OnScrollListener:AbsListView.IOnScrollListener
{
SwipeDismissListViewTouchListener _listener;
public OnScrollListener(SwipeDismissListViewTouchListener listener)
{
_listener = listener;
}
public void OnScroll (AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
}
public void OnScrollStateChanged (AbsListView view, ScrollState scrollState)
{
_listener.SetEnabled(scrollState != ScrollState.TouchScroll);
}
public void Dispose ()
{
throw new NotImplementedException ();
}
public IntPtr Handle {
get {
return new IntPtr ();
}
}
}
public bool OnTouch(View view, MotionEvent motionEvent)
{
if (mViewWidth < 2) {
mViewWidth = mListView.Width;
}
switch (motionEvent.ActionMasked) {
case MotionEventActions.Down:
if (mPaused) {
return false;
}
// TODO: ensure this is a finger, and set a flag
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect ();
int childCount = mListView.ChildCount;
int[] listViewCoords = new int[2];
mListView.GetLocationOnScreen (listViewCoords);
int x = (int)motionEvent.RawX - listViewCoords [0];
int y = (int)motionEvent.RawY - listViewCoords [1];
View child;
for (int i = 0; i < childCount; i++) {
child = mListView.GetChildAt (i);
child.GetHitRect (rect);
if (rect.Contains (x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.RawX;
mDownY = motionEvent.RawY;
mDownPosition = mListView.GetPositionForView (mDownView);
if (mCallbacks.canDismiss (mDownPosition)) {
mVelocityTracker = VelocityTracker.Obtain ();
mVelocityTracker.AddMovement (motionEvent);
} else {
mDownView = null;
}
}
return false;
case MotionEventActions.Cancel: {
if (mVelocityTracker == null) {
break;
}
if (mDownView != null && mSwiping) {
// cancel
mDownView.Animate()
.TranslationX(0)
.Alpha(1)
.SetDuration(mAnimationTime)
.SetListener(null);
}
mVelocityTracker.Recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.InvalidPosition;
mSwiping = false;
break;
}
case MotionEventActions.Up: {
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.RawX - mDownX;
mVelocityTracker.AddMovement(motionEvent);
mVelocityTracker.ComputeCurrentVelocity(1000);
float velocityX = mVelocityTracker.XVelocity;
float absVelocityX = Math.Abs(velocityX);
float absVelocityY = Math.Abs(mVelocityTracker.YVelocity);
bool dismiss = false;
bool dismissRight = false;
if (Math.Abs(deltaX) > mViewWidth / 2) {
dismiss = true;
dismissRight = deltaX > 0;
} else
if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity&& absVelocityY < absVelocityX&&mSwiping) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.XVelocity > 0;
}
if (dismiss&& mDownPosition != ListView.InvalidPosition) {
// dismiss
View downView = mDownView; // mDownView gets null'd before animation ends
int downPosition = mDownPosition;
++mDismissAnimationRefCount;
var anim = mDownView.Animate ()
.TranslationX (dismissRight ? mViewWidth : -mViewWidth)
.Alpha (0)
.SetDuration (mAnimationTime)
.SetListener (new DownAnimatorListenerAdapter(this,downView,downPosition));
}
else {
// cancel
mDownView.Animate()
.TranslationX(0)
.Alpha(1)
.SetDuration(mAnimationTime)
.SetListener(null);
}
mVelocityTracker.Recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.InvalidPosition;
mSwiping = false;
break;
}
case MotionEventActions.Move: {
if (mVelocityTracker == null || mPaused) {
break;
}
mVelocityTracker.AddMovement(motionEvent);
float deltaX = motionEvent.RawX - mDownX;
float deltaY = motionEvent.RawY - mDownY;
if (Math.Abs(deltaX) > mSlop && Math.Abs(deltaY) < Math.Abs(deltaX) / 2) {
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
mListView.RequestDisallowInterceptTouchEvent(true);
// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.Obtain(motionEvent);
cancelEvent.Action= (Android.Views.MotionEventActions) ((int) MotionEventActions.Cancel |
((int)motionEvent.ActionIndex <<(int) MotionEventActions.PointerIndexShift));
mListView.OnTouchEvent(cancelEvent);
cancelEvent.Recycle();
}
if (mSwiping) {
mDownView.TranslationX=deltaX - mSwipingSlop;
mDownView.Alpha=Math.Max(0f, Math.Min(1f,1f - 2f * Math.Abs(deltaX) / mViewWidth));
return true;
}
break;
}
}
return false;
}
/**
* Manually cause the item at the given position to be dismissed (trigger the dismiss
* animation).
*/
public void PerformDismiss(View dismissView, int dismissPosition) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
ViewGroup.LayoutParams lp = dismissView.LayoutParameters;
int originalHeight = dismissView.Height;
ValueAnimator animator = ValueAnimator.OfInt (originalHeight, 1);
animator.SetDuration(mAnimationTime);
animator.AddListener (new DismissAnimatorListenerAdapter (this,originalHeight));
animator.AddUpdateListener(new AnimatorUpdateListener(dismissView,lp));
mPendingDismisses.Add(new PendingDismissData(dismissPosition, dismissView));
animator.Start();
}
public void DismissAnimationEnded(int originalHeight)
{
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
mPendingDismisses.Sort ();
//Collections.Sort(mPendingDismisses);
int[] dismissPositions = new int[mPendingDismisses.Count];
for (int i = mPendingDismisses.Count - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses[i].position;
}
mCallbacks.onDismiss(mListView, dismissPositions);
// Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
// animation with a stale position
mDownPosition = ListView.InvalidPosition;
foreach (PendingDismissData pendingDismiss in mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.Alpha=1f;
pendingDismiss.view.TranslationX=0;
var cc = pendingDismiss.view.LayoutParameters;
cc.Height = originalHeight;
pendingDismiss.view.LayoutParameters=cc;
}
// Send a cancel event
long time = SystemClock.UptimeMillis();
MotionEvent cancelEvent = MotionEvent.Obtain(time, time,MotionEventActions.Cancel, 0, 0, 0);
mListView.DispatchTouchEvent(cancelEvent);
mPendingDismisses.Clear();
}
}
}
class AnimatorUpdateListener : Java.Lang.Object, ValueAnimator.IAnimatorUpdateListener
{
View dismissView;
ViewGroup.LayoutParams lp;
public AnimatorUpdateListener (View dismissView, ViewGroup.LayoutParams lp)
{
this.dismissView = dismissView;
this.lp = lp;
}
public void OnAnimationUpdate (ValueAnimator valueAnimator)
{
lp.Height = (int) valueAnimator.AnimatedValue;
dismissView.LayoutParameters=lp;
}
}
class DownAnimatorListenerAdapter:AnimatorListenerAdapter
{
View _downView; // mDownView gets null'd before animation ends
int _downPosition ;
SwipeDismissListViewTouchListener _parent;
public DownAnimatorListenerAdapter(SwipeDismissListViewTouchListener parent,View downView,int downPosition)
{
_downView = downView;
_parent = parent;
_downPosition = downPosition;
}
public override void OnAnimationEnd (Animator animation)
{
base.OnAnimationEnd (animation);
_parent.PerformDismiss (_downView, _downPosition);
}
}
class DismissAnimatorListenerAdapter:AnimatorListenerAdapter
{
SwipeDismissListViewTouchListener _parent;
int _originalHeight;
public DismissAnimatorListenerAdapter(SwipeDismissListViewTouchListener parent,int originalHeight)
{
_parent = parent;
_originalHeight = originalHeight;
}
public override void OnAnimationEnd (Animator animation)
{
base.OnAnimationEnd (animation);
_parent.DismissAnimationEnded (_originalHeight);
}
}
class PendingDismissData:IComparable<PendingDismissData>
{
public int position;
public View view;
public PendingDismissData(int position, View view) {
this.position = position;
this.view = view;
}
public int CompareTo(PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
}
}
UndoBarController.cs
using System;
using Android.Views;
using Android.Views;
using Android.OS;
using Android.Widget;
using Android.Text;
using Android.Runtime;
using Android.Animation;
using Java.Lang;
using Newtonsoft.Json;
using System.Collections.Generic;
using SwipeDismissListView;
namespace XpressCode
{
public interface IUndoListener
{
void OnUndo (object obj);
}
public class UndoBarController
{
View mBarView;
TextView mMessageView;
ViewPropertyAnimator mBarAnimator;
Handler mHideHandler = new Handler ();
internal IUndoListener mUndoListener;
// State objects
internal object mUndoToken;
string mUndoMessage;
public UndoBarController (View undoBarView, IUndoListener undoListener)
{
mBarView = undoBarView;
mBarAnimator = mBarView.Animate ();
mUndoListener = undoListener;
mMessageView = (TextView)mBarView.FindViewById (Resource.Id.undobar_message);
mBarView.FindViewById<Button> (Resource.Id.undobar_button).Click += (object sender, EventArgs e) => {
HideUndoBar (false);
mUndoListener.OnUndo (mUndoToken);
};
HideUndoBar (true);
}
public void ShowUndoBar (bool immediate, string message, object undoToken)
{
mUndoToken = undoToken;
mUndoMessage = message;
mMessageView.Text = mUndoMessage;
mBarView.Visibility = ViewStates.Visible;
if (immediate) {
mBarView.Alpha = 1;
} else {
mBarAnimator.Cancel ();
mBarAnimator
.Alpha (1)
.SetDuration (mBarView.Resources.GetInteger (Android.Resource.Integer.ConfigShortAnimTime))
.SetListener (null);
}
t = new System.Timers.Timer (mBarView.Resources.GetInteger (Resource.Integer.undobar_hide_delay));
t.Elapsed += HandleTimeoutElapsed;
t.Start ();
}
void HandleTimeoutElapsed (object sender, System.Timers.ElapsedEventArgs e)
{
DisposeTimer ();
HideUndoBar (false);
}
public void DisposeTimer()
{
lock(this)
{
if (t==null)
return;
t.Stop ();
t.Elapsed -= HandleTimeoutElapsed;
t.Dispose ();
t = null;
}
}
System.Timers.Timer t;
public void HideUndoBar (bool immediate)
{
DisposeTimer ();
if (immediate) {
mBarView.Visibility = ViewStates.Gone;
mBarView.Alpha = 0;
mUndoMessage = null;
mUndoToken = null;
} else {
mBarAnimator.Cancel ();
var animator = mBarAnimator
.Alpha (0)
.SetDuration (mBarView.Resources.GetInteger (Android.Resource.Integer.ConfigShortAnimTime))
.SetListener (new CloseAnimatorListenerAdapter (this));
}
}
public void ClosePopup ()
{
mBarView.Visibility = ViewStates.Gone;
mUndoMessage = null;
mUndoToken = null;
}
public void onSaveInstanceState (Bundle outState)
{
outState.PutCharSequence ("undo_message", mUndoMessage);
outState.PutString ("undo_token", JsonConvert.SerializeObject (mUndoToken));
}
public void onRestoreInstanceState (Bundle savedInstanceState)
{
if (savedInstanceState != null) {
mUndoMessage = savedInstanceState.GetCharSequence ("undo_message");
mUndoToken = JsonConvert.DeserializeObject<object> (savedInstanceState.GetString ("undo_token"));
if (mUndoToken != null || !TextUtils.IsEmpty (mUndoMessage)) {
ShowUndoBar (true, mUndoMessage, mUndoToken);
}
}
}
}
class CloseAnimatorListenerAdapter:AnimatorListenerAdapter
{
UndoBarController controller;
public CloseAnimatorListenerAdapter (UndoBarController controller)
{
this.controller = controller;
}
public override void OnAnimationEnd (Animator animation)
{
controller.HideUndoBar (false);
}
}
}
Main.axml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:listSeparatorTextViewStyle"
android:text="@string/heading_dismissable_list_view" />
<ListView
android:id="@+id/lstItems"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
<LinearLayout
android:id="@+id/undobar"
style="@style/UndoBar">
<TextView
android:id="@+id/undobar_message"
style="@style/UndoBarMessage" />
<Button
android:id="@+id/undobar_button"
style="@style/UndoBarButton" />
</LinearLayout>
</FrameLayout>
Что я делаю неправильно? Спасибо за вашу помощь!