I have class Model
public class Model : INotifyPropertyChanged
...
private GridLength detailsPanelHeight { get; set; }
public GridLength DetailsPanelHeight
{
get { return detailsPanelHeight; }
set
{
if (!GridLength.Equals(detailsPanelHeight, value))
{
detailsPanelHeight = value;
OnPropertyChanged("DetailsPanelHeight");
}
}
}
...
part of XAML code:
<RowDefinition Height="{Binding DetailsPanelHeight}" />
code to do animation (changing row height smoothly):
var animate = new Animation(d => currentItem.DetailsPanelHeight = d, 0, 100);
animate.Commit(this, "ExpandAnimation", 50, 1000, Easing.SpringOut);
code to collapse the row:
var animate = new Animation(d => currentItem.DetailsPanelHeight = d, 100, 0);
animate.Commit(this, "CollapseAnimation", 50, 1000, Easing.SpringOut);
It works for the first time, but for the second time i get an error: "value is less than 0 or is not a number\nParameter name: value". I see d value is less than zero.
What can i do to fix this problem?
I've used something like this that worked perfectly for me. I hope it fits for you too.
This animation collapses the view cell while calling a command after a delete action invocation. Here's the code:
The tap event handler:
private async void RemoveButtonTapped(object sender, EventArgs e)
{
Parallel.Invoke(() =>
{
if (RemoveCommand?.CanExecute(RemoveCommandParameter) ?? false)
RemoveCommand.Execute(RemoveCommandParameter);
},
AnimatedDestruction);
}
Animation method
private async void AnimatedDestruction()
{
uint transitionTime = 300;
decimal delayFactor = 1.2m;
// Note: stackPanel is the viewCell's top-level container
await Task.WhenAll(
stackPanel.FadeTo(0, Convert.ToUInt32(transitionTime * delayFactor), Easing.CubicInOut),
View.InterpolateValue(stackPanel.Height, 0, Transition, transitionTime, Easing.CubicInOut)
);
}
Transition callback function
private void Transition(double value)
{
const double minHeightValue = 0.001;
value = value <= minHeightValue ? minHeightValue : value;
Height = value;
ForceUpdateSize();
}
The InterpolateValue function as an extension method (very reusable)
public static Task<bool> InterpolateValue(this View view, double initialValue, double endValue, Action<double> transformIteration, uint length, Easing easing)
{
Task<bool> ret = new Task<bool>(() => false);
if (!view.AnimationIsRunning(nameof(InterpolateValue)))
{
try
{
easing = easing ?? Easing.Linear;
var taskCompletionSource = new TaskCompletionSource<bool>();
view.Animate(nameof(InterpolateValue), ((_double) => initialValue - (initialValue * _double)), transformIteration, 16, length, easing, (v, c) => taskCompletionSource.SetResult(c));
ret = taskCompletionSource.Task;
}
catch
{
// supress animation overlapping errors
}
}
return ret;
}
I hope it works for you.
Related
I'm making app with using Xamarin.forms PCL.
I'm having really hard time to solve this issue.
I've spent several days but couldn't solve.
I'm using this nuget
PullToRefresh
https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout
xabre ble plugin
https://github.com/xabre/xamarin-bluetooth-le
When I use this at the same time AND Add some animation on the page.
It gives this runtime exception when I try to refresh. (It happens very rare like 1 time on 10 times)
The Crash happens ONLY iOS.
It's very rare. You can try to refresh more than 10 times. You will see.
It happens only actual iOS Device.
Foundation.MonoTouchException: Objective-C exception thrown. Name:
NSGenericException Reason: *** Collection <__NSSetM: 0x14feb8b0> was
mutated while being enumerated.
I know well what this is and when this happen.
It happens when I try to access deleted item on list or different thread.
So I made very simple source code to look this issue simply.
I don't have any list or array on my code.
Well, it happens again.
https://github.com/myallb/test_pulltorefresh
This is my sample source code for reproducing this issue. If you can help me, please look this code.
The Crash happens ONLY iOS.
It's very rare. You can try to refresh more than 10 times. You will see.
It happens only actual iOS Device.
Thanks so much.
Full source code
using Xamarin.Forms;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE;
using Plugin.BLE.Abstractions.EventArgs;
using System;
using System.Diagnostics;
using Refractored.XamForms.PullToRefresh;
using System.Threading.Tasks;
namespace test
{
public partial class testPage : ContentPage
{
public static IAdapter Adapter { set; get; }
public PullToRefreshLayout RefreshView = null;
AbsoluteLayout layout;
public testPage()
{
InitializeComponent();
Adapter = CrossBluetoothLE.Current.Adapter;
if (Adapter != null)
{
Adapter.DeviceAdvertised += OnEvent_DeviceAdvertised;
Adapter.DeviceConnected += OnEvent_DeviceConnected;
Adapter.DeviceConnectionLost += OnEvent_DeviceConnectionLost;
Adapter.DeviceDisconnected += OnEvent_DeviceDisconnected;
Adapter.DeviceDiscovered += OnEvent_DeviceDiscovered;
Device.StartTimer(TimeSpan.FromSeconds(5), Timer_ScanDevice);
}
else {
}
layout = new AbsoluteLayout()
{
BackgroundColor = Color.Purple,
};
ScrollView scrollview = new ScrollView()
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Content = layout
};
RefreshView = new PullToRefreshLayout
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Content = scrollview,
RefreshColor = Color.Red,
RefreshCommand = new Command(RefreshStart)
};
RefreshView.IsPullToRefreshEnabled = true;
Content = RefreshView;
Device.StartTimer(new TimeSpan(0, 0, 1), ani);
}
bool ani()
{
Label z = new Label()
{
Text = "Z",
TextColor = Color.White,
FontAttributes = FontAttributes.Bold,
FontSize = new Random().Next(22, 35)
};
AbsoluteLayout.SetLayoutBounds(z, new Rectangle(0.67 + new Random().Next(0, 10) / 100.0, 0.13 + new Random().Next(0, 10) / 100.0, 40, 40));
AbsoluteLayout.SetLayoutFlags(z, AbsoluteLayoutFlags.PositionProportional);
layout.Children.Add(z);
Device.BeginInvokeOnMainThread(async () =>
{
Task t1 = z.FadeTo(0, 3500);
Task t2 = z.TranslateTo(0, -70, 3500, Easing.SinInOut);
await Task.WhenAll(t1, t2);
layout.Children.Remove(z);
});
return true;
}
void RefreshStart()
{
Debug.WriteLine("RefreshStart");
if (RefreshView != null)
RefreshView.IsRefreshing = true;
Device.BeginInvokeOnMainThread(async () =>
{
await Task.Delay(20);
Debug.WriteLine("RefreshEnd");
RefreshView.IsRefreshing = false;
});
}
bool Timer_ScanDevice()
{
Adapter.StartScanningForDevicesAsync();
return true;
}
void OnEvent_DeviceAdvertised(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceAdvertised");
}
void OnEvent_DeviceDiscovered(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceDiscovered");
}
void OnEvent_DeviceConnected(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceConnected");
}
void OnDeviceProcessError(IDevice device, string message)
{
Debug.WriteLine("OnDeviceProcessError");
}
void OnEvent_DeviceConnectionLost(object sender, DeviceErrorEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceConnectionLost");
}
void OnEvent_DeviceDisconnected(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceDisconnected");
}
}
}
I'm trying to implement a solution to increase the size of a ListView Cell when tapped using Xamarin Forms (and custom renderers if required).
I'm still pretty new to C#, and the idea of data binding is still a little unclear to me, however, it seems like that is the way to go to solve this problem (perhaps something along the lines of binding the Height / HeightRequest properties of the cell?).
My attempts thus far have been unsuccessful.
If anyone could give me a push in the right direction it would be much appreciated.
Thank you!
ViewCell does not expose Height as a BindableProperty in Xamarin.Forms 1.4.2x
However if you create your own BindableProperty in your Model you can achieve changing the height still as shown below:-
Model:-
public class MenuItem2 : BindableObject
{
public static readonly BindableProperty TextProperty = BindableProperty.Create<MenuItem2, string>(p => p.Text, default(string));
public static readonly BindableProperty CellHeightProperty = BindableProperty.Create<MenuItem2, int>(p => p.CellHeight, default(int));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public int CellHeight
{
get { return (int)GetValue(CellHeightProperty); }
set { SetValue(CellHeightProperty, value); }
}
}
XAML:-
<StackLayout>
<Button x:Name="cmdButton1" Text="Change Cell Heights" Clicked="cmdButton1_Clicked"/>
<ListView x:Name="lstItems" />
</StackLayout>
XAML Code-Behind:-
lstItems.HasUnevenRows = true;
lstItems.ItemTemplate = new DataTemplate(typeof(Classes.MenuCell2));
//
lstItems.ItemsSource = new List<MenuItem2>
{
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
};
If you don't set .HasUnevenRows you will not be able to change the cell height.
void cmdButton1_Clicked(object sender, EventArgs e)
{
Random objRandom = new Random();
//
var objItems = lstItems.ItemsSource;
//
foreach (MenuItem2 objMenuItem in objItems)
{
int intNewCellHeight = objRandom.Next(80, 160);
objMenuItem.CellHeight = intNewCellHeight;
objMenuItem.Text = "Cell Height = " + intNewCellHeight.ToString();
}
}
Custom ViewCell:-
public class MenuCell2 : ViewCell
{
public MenuCell2()
{
Label objLabel = new Label
{
YAlign = TextAlignment.Center,
TextColor = Color.Yellow,
};
objLabel.SetBinding(Label.TextProperty, new Binding("Text"));
StackLayout objLayout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.StartAndExpand,
Children = { objLabel }
};
Frame objFrame_Inner = new Frame
{
Padding = new Thickness(15, 15, 15, 15),
HeightRequest = 36,
OutlineColor = Color.Accent,
BackgroundColor = Color.Blue,
Content = objLayout,
};
Frame objFrame_Outer = new Frame
{
Padding = new Thickness(0, 0, 0, 10),
Content = objFrame_Inner
};
View = objFrame_Outer;
this.BindingContextChanged += MenuCell2_BindingContextChanged;
}
void MenuCell2_BindingContextChanged(object sender, EventArgs e)
{
MenuItem2 objMenuItem = (MenuItem2)this.BindingContext;
objMenuItem.PropertyChanged += objMenuItem_PropertyChanged;
}
void objMenuItem_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CellHeight":
this.Height = (this.BindingContext as MenuItem2).CellHeight;
(this.View as Frame).ForceLayout();
break;
}
}
Remember to call ForceLayout on the root element of the ViewCell's View property, so it can redraw correctly.
This will give you a result something similar to the following (tested only on WindowsPhone at present):-
In order to do it on a ViewCell being tapped, on the XAML Page add:-
lstItems.ItemTapped += lstItems_ItemTapped;
and then change the model for the item to something like this:-
void lstItems_ItemTapped(object sender, ItemTappedEventArgs e)
{
(e.Item as MenuItem2).CellHeight = 200;
}
Xamarin now has an official example of doing this right within xaml and xaml code behind:
Overview:
https://developer.xamarin.com/samples/xamarin-forms/UserInterface/ListView/DynamicUnevenListCells/
Code:
https://github.com/xamarin/xamarin-forms-samples/tree/master/UserInterface/ListView/DynamicUnevenListCells
I need to create a gtk.Entry which accepts only numbers. but I can't overwrite the key_press_event event in an heredited class. It only works if I use the original Entry by means of connect function.
What am I doing wrong?
using Gtk;
public class NumberEntry : Entry {
public void NumberEntry(){
add_events (Gdk.EventMask.KEY_PRESS_MASK);
}
//With customized event left entry editing is not possible
public override bool key_press_event (Gdk.EventKey event) {
string numbers = "0123456789.";
if (numbers.contains(event.str)){
return false;
} else {
return true;
}
}
}
public class Application : Window {
public Application () {
// Window
this.title = "Entry Issue";
this.window_position = Gtk.WindowPosition.CENTER;
this.destroy.connect (Gtk.main_quit);
this.set_default_size (350, 70);
Grid grid = new Grid();
grid.set_row_spacing(8);
grid.set_column_spacing(8);
Label label_1 = new Label ("Customized Entry, useless:");
grid.attach (label_1,0,0,1,1);
//Customized Entry:
NumberEntry numberEntry = new NumberEntry ();
grid.attach(numberEntry, 1, 0, 1, 1);
Label label_2 = new Label ("Working only numbers Entry:");
grid.attach (label_2,0,1,1,1);
//Normal Entry
Entry entry = new Entry();
grid.attach(entry, 1, 1, 1, 1);
this.add(grid);
//With normal Entry this event works well:
entry.key_press_event.connect ((event) => {
string numbers = "0123456789.";
if (numbers.contains(event.str)){
return false;
} else {
return true;
}
});
}
}
public static int main (string[] args) {
Gtk.init (ref args);
Application app = new Application ();
app.show_all ();
Gtk.main ();
return 0;
}
The key_press_event of the superclass is no longer being called. You need to call the base class and return true when you have consumed the key.
public override bool key_press_event (Gdk.EventKey event) {
string numbers = "0123456789.";
if (numbers.contains(event.str)){
return base.key_press_event (event);
} else {
return true;
}
}
If you return false in a signal, this can be passed to an alternate handler, but only if you use connect and not override the signal method.
I'm using this code to scroll programatically my pager
public void MoveNext(View view) {
pager.setCurrentItem(pager.getCurrentItem() + 1);
}
public void MovePrevious(View view) {
pager.setCurrentItem(pager.getCurrentItem() - 1);
}
The code work perfect but the transition is too fast. How can I introduce a delay so that the scrolling would be done more smoothly?
I believe there are two ways can achieve this goal, I've tried by ViewPager's fakeDrag() but which doesn't work perfect, luckly, another way does, by simulate touch motion event and use ObjectAnimator to specify the animation duration then make control the scroll speed become true.
public class ViewPagerActivity extends FragmentActivity
implements View.OnClickListener, Animator.AnimatorListener {
private ViewPager mViewPager;
private View btnTriggerNext;
private View btnTriggerPrev;
#Override
protected void onCreate(...) {
super...;
setContentView(R.layout.layout_xml);
mViewPager = findViewById(...);
btnTriggerNext = findViewById(R.id.btnTriggerNext);
btnTriggerNext.setOnClickListener(this);
btnTriggerPrev = findViewById(R.id.btnTriggerPrev);
btnTriggerPrev.setOnClickListener(this);
}
private boolean mIsInAnimation;
private long mMotionBeginTime;
private float mLastMotionX;
#Override
public void onClick(View v) {
if (mIsInAnimation) return;
ObjectAnimator anim;
if (v == btnTriggerPrev) {
if (!hasPrevPage()) return;
anim = ObjectAnimator.ofFloat(this, "motionX", 0, mViewPager.getWidth());
}
else if (v == btnTriggerNext) {
if (!hasNextPage()) return;
anim = ObjectAnimator.ofFloat(this, "motionX", 0, -mViewPager.getWidth());
}
else return;
anim.setInterpolator(new LinearInterpolator());
anim.addListener(this);
anim.setDuration(300);
anim.start();
}
public void setMotionX(float motionX) {
if (!mIsInAnimation) return;
mLastMotionX = motionX;
final long time = SystemClock.uptimeMillis();
simulate(MotionEvent.ACTION_MOVE, mMotionBeginTime, time);
}
#Override
public void onAnimationEnd(Animator animation) {
mIsInAnimation = false;
final long time = SystemClock.uptimeMillis();
simulate(MotionEvent.ACTION_UP, mMotionBeginTime, time);
}
#Override
public void onAnimationStart(Animator animation) {
mLastMotionX = 0;
mIsInAnimation = true;
final long time = SystemClock.uptimeMillis();
simulate(MotionEvent.ACTION_DOWN, time, time);
mMotionBeginTime = time;
}
// method from http://stackoverflow.com/a/11599282/1294681
private void simulate(int action, long startTime, long endTime) {
// specify the property for the two touch points
MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
MotionEvent.PointerProperties pp = new MotionEvent.PointerProperties();
pp.id = 0;
pp.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[0] = pp;
// specify the coordinations of the two touch points
// NOTE: you MUST set the pressure and size value, or it doesn't work
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
MotionEvent.PointerCoords pc = new MotionEvent.PointerCoords();
pc.x = mLastMotionX;
pc.pressure = 1;
pc.size = 1;
pointerCoords[0] = pc;
final MotionEvent ev = MotionEvent.obtain(
startTime, endTime, action, 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);
mViewPager.dispatchTouchEvent(ev);
}
private boolean hasPrevPage() {
return mViewPager.getCurrentItem() > 0;
}
private boolean hasNextPage() {
return mViewPager.getCurrentItem() + 1 < mViewPager.getAdapter().getCount();
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
}
cause it was simulating touch event, so please use a proper duration(less than 600ms will be nice) to do scrolling, when scroll in progress, put down finger would stop it and cause some bugs.
Change
pager.setCurrentItem(pager.getCurrentItem() + 1);
to
pager.setCurrentItem(pager.getCurrentItem() + 1,true);
This will call the method setCurrentItem(int item,boolean smoothScroll) which will scroll smoothly to the mentioned item instead of transitioning immediately
I have a custom class as follows which works fine, the button grows/shrinks to accomodate the text and the bg image changes on a click.
Probem I want to solve is how to "fadeIN" one or other image when clicked/notClicked is called
Here is my code
public ExpandingOvalButton(String text) {
if (text.length() > 15) {
label.getElement().getStyle().setFontSize(20, Unit.PX);
} else {
label.getElement().getStyle().setFontSize(30, Unit.PX);
}
int width = 120;
initWidget(panel);
label.setText(text);
// width = width + (text.length() * 8);
String widthStr = width + "px";
image.setWidth(widthStr);
image.setHeight("100px");
button = new PushButton(image);
button.setWidth(widthStr);
button.setHeight("50px");
panel.add(button, 0, 0);
panel.add(label, 18, 14);
}
public void isClicked()
{
image.setUrl("images/rectangle_green.png");
}
public void unClicked()
{
image.setUrl("images/rectangle_blue.png");
}
#Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
public void setButtonEnabled(boolean enabled) {
// panel.setVisible(enabled);
// this.label.setVisible(enabled);
this.button.setVisible(enabled);
}
Here's a general utility class to fade any element:
public class ElementFader {
private int stepCount;
public ElementFader() {
this.stepCount = 0;
}
private void incrementStep() {
stepCount++;
}
private int getStepCount() {
return stepCount;
}
public void fade(final Element element, final float startOpacity, final float endOpacity, int totalTimeMillis) {
final int numberOfSteps = 30;
int stepLengthMillis = totalTimeMillis / numberOfSteps;
stepCount = 0;
final float deltaOpacity = (float) (endOpacity - startOpacity) / numberOfSteps;
Timer timer = new Timer() {
#Override
public void run() {
float opacity = startOpacity + (getStepCount() * deltaOpacity);
DOM.setStyleAttribute(element, "opacity", Float.toString(opacity));
incrementStep();
if (getStepCount() == numberOfSteps) {
DOM.setStyleAttribute(element, "opacity", Float.toString(endOpacity));
this.cancel();
}
}
};
timer.scheduleRepeating(stepLengthMillis);
}
}
Calling code for instance:
new ElementFader().fade(image.getElement(), 0, 1, 1000); // one-second fade-in
new ElementFader().fade(image.getElement(), 1, 0, 1000); // one-second fade-out
You could use GwtQuery. It provides fadeIn & fadeOut effects (and many other JQuery goodies), it is cross-browser compatible and seems to be pretty active.