I have a vertical scroll bar. When it resized triggers an event that checks if it is 100% or more to hide it but instead to get the new size value just get the old one. The visibility changes OK but not the size:
scrollBarL.heightProperty().addListener((ObservableValue<? extends Number> ov,
Number old_val, Number new_val) -> {
if (scrollBarL.getVisibleAmount() >= 1 ) {
scrollBarL.setMaxWidth(0); // Not change width immediately
scrollBarL.setPrefWidth(0); // Not change width immediately
scrollBarL.setMinWidth(0); // Not change width immediately
scrollBarL.setVisible(false); // OK. Change visibility immediately
} else {
scrollBarL.setMaxWidth(19); // Not change width immediately
scrollBarL.setPrefWidth(19); // Not change width immediately
scrollBarL.setMinWidth(19); // Not change width immediately
scrollBarL.setVisible(true); // OK. Change visibility immediately
}
});
Related
I'm trying to animate the height constraint of a UITextView inside a UIScrollView. When the user taps the "toggle" button, the text should appear in animation from top to bottom. But somehow UIKit fades in the complete view.
To ensure the "dynamic" height depending on the intrinsic content-size, I deactivate the height constraint set to zero.
#IBAction func toggle() {
layoutIfNeeded()
UIView.animate(withDuration: 0.6, animations: { [weak self] in
guard let self = self else {
return
}
if self.expanded {
NSLayoutConstraint.activate([self.height].compactMap { $0 })
} else {
NSLayoutConstraint.deactivate([self.height].compactMap { $0 })
}
self.layoutIfNeeded()
})
expanded.toggle()
}
The full code of this example is available on my GitHub Repo: ScrollAnimationExample
Reviewing your GitHub repo...
The issue is due to the view that is animated. You want to run .animate() on the "top-most" view in the hierarchy.
To do this, you can either create a new property of your ExpandableView, such as:
var topMostView: UIView?
and then set that property from your view controller, or...
To keep your class encapsulated, let it find the top-most view. Replace your toggle() func with:
#IBAction func toggle() {
// we need to run .animate() on the "top" superview
// make sure we have a superview
guard self.superview != nil else {
return
}
// find the top-most superview
var mv: UIView = self
while let s = mv.superview {
mv = s
}
// UITextView has subviews, one of which is a _UITextContainerView,
// which also has a _UITextCanvasView subview.
// If scrolling is disabled, and the TextView's height is animated to Zero,
// the CanvasView's height is instantly set to Zero -- so it disappears instead of animating.
// So, when the view is "expanded" we need to first enable scrolling,
// and then animate the height (to Zero)
// When the view is NOT expanded, we first disable scrolling
// and then animate the height (to its intrinsic content height)
if expanded {
textView.isScrollEnabled = true
NSLayoutConstraint.activate([height].compactMap { $0 })
} else {
textView.isScrollEnabled = false
NSLayoutConstraint.deactivate([height].compactMap { $0 })
}
UIView.animate(withDuration: 0.6, animations: {
mv.layoutIfNeeded()
})
expanded.toggle()
}
https://jsfiddle.net/diegojsrw/od12s9b4/14/
c.font = "100vh sans-serif";
c.fillStyle = "white";
c.fillText(fps.toFixed(0), w/2, 0);
Initially, the text's height seems fine.
However, when I resize the window, the text doesn't resize together. The text is constantly drawn on canvas (using requestAnimationFrame).
The text size is only re-adjusted when I switch to another tab then switch back to this tab.
Any clues?
You are facing a webkit bug. Both latests Chrome and Safari are concerned.
In the Canvas2D API, relative units/values should get computed at setting and this computed value will get saved and used.
This means that your relative 100vw value will get computed to its corresponding value in absolute px unit.
This is one of the reasons it is advised to always use absolute units from the canvas API (among others like tweaks in roundings etc.).
But if you really want to use this unit, then you have to set it everytime it has changed, so you could go blindly and inside your loop just set ctx.font again before calling fillText(), but for performances, I would advise you use a dirty flag, that would get raised in the window's resize event, and only update the ctx.font property when this flag is raised.
But this is only fine for browsers that do follow the specs...
I have no real clue as to when webkit browsers will compute this value, as even resetting the font property to something else won't do, and even setting it to an other value (e.g switching between 20vh and 21vh) won't do either...
So the only workaround I can see for now, is to actually compute yourself this value. For viewport size, that's not so hard (innerWidth / (100 / vw_val)), but even for other relative units, you can actually set this fontSize on the canvas directly and call getComputedStyle() on the canvas.
let dirtySize = true; // this is our resize flag
const ctx = canvas.getContext('2d');
let fontStyle = ' Arial, sans-serif';
anim(0);
// the animation loop
function anim(time) {
// call the drawing methods
draw(Math.round(time/1e2));
// lwoer the flags
dirtySize = false;
// do it again next frame
requestAnimationFrame(anim);
}
function draw(txt) {
// clear
ctx.clearRect(0,0,canvas.width, canvas.height);
// only if needed
if(dirtySize) {
// get the cpmputed style from our DOM element
const fontSize = getComputedStyle(canvas).fontSize;
// or could be
// const fontSize = (innerWidth / (100 / 20)) + 'px';
ctx.font = fontSize + fontStyle;
}
// draw everytime
ctx.fillText(txt, 0, 100);
}
// on window's resize event
window.addEventListener('resize',
evt => dirtySize = true, // raise our flag
{ passive: true }
);
#canvas {
font-size: 20vw;
}
<canvas id="canvas"></canvas>
I'm trying to remove an item from a Repeater in NativeScript by simply collapsing it. I can scale the item's height just fine, but the space is preserved so that the Repeater jumps up after the item is removed. I want the remaining items in the Repeater to move up as the item being remove is collapsed.
Here is the behaviour I have now. Notice that the rest of the items don't move until the item being deleted is finished animating.
Here is what I've got so far for the animation code:
let remove = (args: EventData) => {
let view = <View>args.object;
let item = view.parent.parent;
let todo = view.bindingContext;
item.originY = 0;
item.animate({
scale: { x: 1, y: 0 },
curve: "easeIn",
duration: 500
})
.then(() => {
item.visibility = 'collapsed';
viewModel.remove(todo);
});
}
Are you able to animate the repeater view itself up at the same time with multi-view animations?
http://docs.nativescript.org/ApiReference/ui/animation/HOW-TO#animating-multiple-views-simultaneously
What about yank the repeater and replace it with the new (free) RadListView which has this added by default, very smooth
http://docs.telerik.com/devtools/nativescript-ui/Controls/ListView/item-animations
I have a scroll view that is programmed so that if you pull down more than 100px (i.e into the negative y content offset and with bounces enabled) then let go, a view at the top of the scroll view gets larger 100px larger (pushing everything else down). To make the transition smooth I'm trying to adjust the content offset of the scrollview by 100px at this point, like this:
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
{
if scrollView.contentOffset < -100
{
scrollView.contentOffset.y += 100 //
makeTopViewTaller()
}
}
However, the change in the contentOffset doesn't stick. By logging the content offset in the scrollViewDidScroll() method I can see that the y value changes for a moment, but then goes back to where it was when the dragging ended.
Is there any way to force the content offset to change, then let the UIScrollView's natural bounce decelaration to apply to the new value?
I tried a whole bunch of different approaches to this problem, but the one that eventually worked involved creating a subclass of the UIScrollView (in my case, actually a UICollectionView) and overriding the setter for the contentOffset property. Here's my subclass:
class MyCollectionView : UICollectionView
{
var isTransitioning:Bool = false
var initialTransitioningY:CGFloat = 0
override var contentOffset:CGPoint
{
get
{
return super.contentOffset
}
// This custom setter is to make the transition to the large header state smooth
set
{
if(isTransitioning && initialTransitioningY < 0)
{
var oy = newValue.y
if(oy == 0 && contentOffset.y < -10) // This is to avoid a flicker when you first call 'reloadData' on the collection view, which sets the contentOffset.y to 0, then returns to it's previous state
{
oy = contentOffset.y
}
super.contentOffset = CGPointMake(newValue.x, (initialTransitioningY + 100) * (oy / initialTransitioningY))
}
else
{
super.contentOffset = newValue
}
}
}
}
When the user has pulled passed the transition point (100px in my case) and let go the scrollViewWillEndDragging method is called and the isTransitioning property is set to true and initialTransitioningY set to the collection view's current content offset. When scrollViewDidEndDecelerating is called the isTransitioning property is set back to false.
Works a charm.
I have a kinetic stage and some objects that each are draggable. (See fiddle for example.)
My goal is to allow the user to always be able to middle click to pan the entire stage whether or not he middle clicks on empty space or on any shape drawn to the stage. I need to do this in a way that doesn't break the drag events of existing shapes. Meaning I need the shape dragStart and dragEnd events to be processed normally, but only on left click drag.
I'm able to detect the middle click event on the stage and call dragStop() on the shape if the drag was initiated on one, but calling dragStop() means that the dragstop event is fired and processing occurs on the shape.
// let's pretend that a mouse doesn't have more than 9 buttons
var mouseDown = [0, 0, 0, 0, 0, 0, 0, 0, 0];
var mouseDownCount = 0;
document.body.onmousedown = function(evt) {
++mouseDown[evt.button];
++mouseDownCount;
}
document.body.onmouseup = function(evt) {
--mouseDown[evt.button];
--mouseDownCount;
}
stage.on('dragstart', function(e){
if(mouseDownCount){
if(mouseDown[0]){
// Left mouse button drag
if (e.target.nodeType == 'Stage'){
// stop the drag for left click
e.target.stopDrag();
}
} else if (mouseDown[1]){
// Middle mouse button drag
if (e.target.nodeType != 'Stage'){
// stop the drag of the object for left click
e.target.listening(false);
e.target.stopDrag();
e.target.listening(true);
// start the stage drag
e.target.getStage().startDrag()
}
}
}
});
Is there a way to cancel the dragStart event after it has been fired, or in some other way tell dragEnd to not fire? I've tried setting e.target.listening(false) prior to calling dragStop, but that doesn't seem to be working.
Any ideas?
Try to use latest version from GitHub. It has functionality only with left mouse button.