Are there any easy ways to do the following when tickering on a Label?
Pause for 3 sec every time the text gets back to its original position?
Ease in and ease out the intervals between ticks. Similar to the JavaFX Motion for easing in and out?
Make the ticking flow more smoothly instead of being a little jumpy?
Yes but you will need to do it manually.
Just override Label and override its animate() method. I haven't tried this but something like this can work for all of your requirements:
Label tickeredLabel = new Label(myText) {
Motion tickeringMotion;
long pauseTime = System.currentTimeMillis();
public boolean animate() {
long currentTime = System.currentTimeMillis();
// wait 3 seconds for tickering
if(currentTime - pauseTime < 3000) {
return false;
}
// use ease in/out motion over 5 seconds
if(tickeringMotion == null) {
tickeringMotion = Motion.createEaseInOutMotion(0, getStringWidth(getStyle().getFont(), 5000);
tickeringMotion.start();
} else {
// when motion is finished return to 3 second delay
if(tickeringMotion.isFinished()) {
tickeringMotion = null;
pauseTime = System.currentTimeMillis();
}
}
setShiftText(tickeringMotion.getValue());
return changed;
}
};
To smooth out tickering further just make it move one pixel at a time specifically
Related
Im creating ccountdown timer in xamarin.forms.
The problem is when I pause the timer and next I'll start the timer, it speeds up and slows down and it happens the whole time. It should goes every second but why does it speed up sometimes ever 2/4 seconds ?
The code below:
bool Value =false;
int counter =120;
private void RunTimer(Boolean Value)
{
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
If(Value)
{
counter--;
if(counter <=0)
{
counter = 120;
MainText.Text = dt.AddSeconds(counter).ToString("mm:ss");
}
MainText.Text = dt.AddSeconds(counter).ToString("mm:ss");
}
return true;
}
return false;
});
private void Start_Pause(object sender, EventArgs e)
{
if(value == false)
{
RunTimer(true);
Value = true;
}
else
{
RunTimer(false);
Value = false;
}
}
MainText.Text = dt.AddSeconds(counter).ToString("mm:ss"); <-- This is incorrect. Computer timers are never precise. Instead compute the DateTime end value once, and recompute remaining time by subtracing DateTime.Now on every tick (instead of counting imprecise time periods yourself).
Change your code to something like this:
private void RunTimer(Boolean value)
{
DateTime start = DateTime.Now;
DateTime end = start.AddSeconds( 120 );
Device.StartTimer(
interval: TimeSpan.FromMilliseconds( 100 ),
callback: () =>
{
TimeSpan remaining = end - DateTime.Now;
Device.BeginInvokeOnMainThread( () =>
{
this.MainText.Text = remaining.ToString("mm:ss");
});
return remaining >= TimeSpan.Zero;
});
}
I'm using TimeSpan.FromMilliseconds( 100 ) to ensure the label's text is invalidated closer to when the remaining value actually changes to counteract jitter in the system.
Using return remaining >= TimeSpan.Zero; will stop the countdown as soon as end is passed.
The documentation says that all UI interactions inside StartTimer must be done with Device.BeginInvokeOnMainThread.
I would like to display a progress indicator while recording sound in my app.
The amount of time allocated for the recording is predefined. I set that up in code, lets say 10 seconds maximum recording time, but the user can stop the recording in less time, and of course he progress indicator would stop and reset.
I have been trying to make it work right could you please offer some guidance.
Note: I am using the NateRickard AudioRecorder nuget package.
if (!recorder.IsRecording)
{
buttonRecord.IsEnabled = false;
buttonPlay.IsEnabled = false;
DependencyService.Get<IAudioService>().PrepareRecording();
// start recording
var recordTask = await recorder.StartRecording();
// set up progress bar
//progressBarRecordTime.Progress = 1.0;
//await progressBarRecordTime.ProgressTo(1.0, 10000, Easing.Linear);
buttonRecord.Text = "Stop Recording";
buttonRecord.IsEnabled = true;
// get the recorded file
var recordedAudioFile = await recordTask;
buttonRecord.Text = "Record";
buttonPlay.IsEnabled = true;
if (recordedAudioFile != null)
{
var recordingFileDestinationPath = Path.Combine(FileSystem.AppDataDirectory, AppConstants.CUSTOM_ALERT_FILENAME);
if (File.Exists(recordingFileDestinationPath))
{
File.Delete(recordingFileDestinationPath);
}
File.Copy(recordedAudioFile, recordingFileDestinationPath);
}
}
Place an ActivityIndicator (name it ind) in your xaml code (view)
At the top of your code above:
ind.IsRunning = true;
//
if (!recorder.IsRecording)
//
when you are done, add this below your code
//
}
File.Copy(recordedAudioFile, recordingFileDestinationPath);
}
}
ind.IsRunning = false;
I was looking at Netflix app and their scrolling behaviour.I would like to do the same but don't know where to start. I know how to override LayoutManager for RecyclerView(though I don't to save that as last resort). Is there easier way to control scrolling speed using RowPresenter ?
Implement the logic on your activity class, ovverride keys (OnKeyDown) on a way that will ignore the key event for 3-4 millis or sth.
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
long current = System.currentTimeMillis();
boolean res = false;
if (current - mLastKeyDownTime < 300) {
res = true;
} else {
res = super.onKeyDown(keyCode, event);
mLastKeyDownTime = current;
}
return res;
}
Leanback supports setting scrolling speed for BaseGridView from version 1.1.0-alpha03. You can set scrolling speed by using a method named setSmoothScrollSpeedFactor in all sub-classes of BaseGridView.
Generalities : explanations about my program and its functioning
I am working on a photo-retouching JavaFX application. The final user can load several images. When he clicks on the button REVERSE, a Task is launched for each image using an Executor. Each of these Task executes the reversal algorithm : it fills an ArrayBlockingQueue<Pixel> (using add method).
When the final user clicks on the button REVERSE, as I said, these Task are launched. But just after these statements, I tell the JavaFX Application Thread to draw the Pixel of the ArrayBlockingQueue<Pixel> (using remove method).
Thus, there are parallelism and concurrency (solved by the ArrayBlockingQueue<Pixel>) between the JavaFX Application Thread and the Task, and between the Task themselves.
To draw the Pixel of the ArrayBlockingQueue<Pixel>, the JavaFX Application Thread starts an AnimationTimer. The latter contains the previously-mentionned remove method. This AnimationTimer is started for each image.
I think you're wondering yourself how this AnimationTimer can know to what image belongs the Pixel it has removed ? In fact, each Pixel has an attribute writable_image that specifies the image to what it belongs.
My problems
Tell me if I'm wrong, but my program should work. Indeed :
My JavaFX Application Thread is the only thread that change the GUI (and it's required in JavaFX) : the Task just do the calculations.
There is not concurrency, thanks to the BlockingQueue I use (in particular, there isn't possibility of draining).
The AnimationTimer knows to what image belongs each Pixel.
However, it's (obviously !) not the case (otherwise I wouldn't have created this question haha !).
My problem is that my JavaFX Application freezes (first problem), after having drawn only some reversed pixels (not all the pixels). On the last loaded image moreover (third problem).
A detail that could be the problems' cause
But I would need your opinion.
The AnimationTimer of course doesn't draw the reversed pixels of each image directly : this is animated. The final user can see each pixel of an image being reversed, little by little. It's very practical in other algorithms as the creation of a circle, because the user can "look" how the algorithm works.
But to do that, the AnimationTimer needs to read a variable called max. This variable is modified (writen) in... each Task. But it's an AtomicLong. So IF I AM NOT WRONG, there isn't any problem of concurrency between the Task themselves, or between the JavaFX Application Thread and these Task.
However, it could be the problem : indeed, the max's value could be 2000 in Task n°1 (= in image n°1), and 59 in Task n°2 (= in image n°2). The problem is the AnimationTimer must use 2000 for the image n°1, and 59 for the n°2. But if the Task n°1 et n°2 have finished, the only value known by the AnimationTimer would be 59...
Sources
When the user clicks on the button REVERSE
We launch the several Task and start several times the AnimationTimer. CLASS : RightPane.java
WritableImage current_writable_image;
for(int i = 0; i < this.gui.getArrayListImageViewsImpacted().size(); i++) {
current_writable_image = (WritableImage) this.gui.getArrayListImageViewsImpacted().get(i).getImage();
this.gui.getGraphicEngine().executor.execute(this.gui.getGraphicEngine().createTask(current_writable_image));
}
for(int i = 0; i < this.gui.getArrayListImageViewsImpacted().size(); i++) {
current_writable_image = (WritableImage) this.gui.getArrayListImageViewsImpacted().get(i).getImage();
this.gui.getImageAnimation().setWritableImage(current_writable_image);
this.gui.getImageAnimation().startAnimation();
}
The Task are part of the CLASS GraphicEngine, which contains an Executor :
public final Executor executor = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
public Task createTask(WritableImage writable_image) {
int image_width = (int) writable_image.getWidth(), image_height = (int) writable_image.getHeight();
Task ret = new Task() {
protected Void call() {
switch(operation_to_do) {
case "reverse" :
gui.getImageAnimation().setMax(image_width*image_height); // USE OF "MAX" VARIABLE
reverseImg(writable_image);
break;
}
return null;
}
};
return ret;
}
The same CLASS, GraphicEngine, also contains the reversal algorithm :
private void reverseImg(WritableImage writable_image) {
int image_width = (int) writable_image.getWidth(), image_height = (int) writable_image.getHeight();
BlockingQueue<Pixel> updates = gui.getUpdates();
PixelReader pixel_reader = writable_image.getPixelReader();
double[] rgb_reversed;
for (int x = 0; x < image_width; x++) {
for (int y = 0; y < image_height; y++) {
rgb_reversed = PhotoRetouchingFormulas.reverse(pixel_reader.getColor(x, y).getRed(), pixel_reader.getColor(x, y).getGreen(), pixel_reader.getColor(x, y).getBlue());
updates.add(new Pixel(x, y, Color.color(rgb_reversed[0], rgb_reversed[1], rgb_reversed[2], pixel_reader.getColor(x, y).getOpacity()), writable_image));
}
}
}
Finally, here is the code of the CLASS AnimationTimer. There is nothing particular. Note the variable max is used here too (and in the CLASS GraphicEngine : setMax).
public class ImageAnimation extends AnimationTimer {
private Gui gui;
private AtomicLong max, speed, max_delay;
private long count, start;
private WritableImage writable_image;
ImageAnimation (Gui gui) {
this.gui = gui;
this.count = 0;
this.start = -1;
this.max = new AtomicLong(Long.MAX_VALUE);
this.max_delay = new AtomicLong(999_000_000);
this.speed = new AtomicLong(this.max_delay.get());
}
public void setMax(long max) {
this.max.set(max);
}
public void setSpeed(long speed) { this.speed.set(speed); }
public double getMaxDelay() { return this.max_delay.get(); }
#Override
public void handle(long timestamp) {
if (start < 0) {
start = timestamp ;
return ;
}
ArrayList<Pixel> list_sorted_pixels = new ArrayList<>();
BlockingQueue<Pixel> updates = this.gui.getUpdates();
for(Pixel new_pixel : updates) {
if(new_pixel.getWritableImage() == writable_image) {
list_sorted_pixels.add(new_pixel);
}
}
while (list_sorted_pixels.size() > 0 && timestamp - start > (count * this.speed.get()) / (writable_image.getWidth()) && !updates.isEmpty()) {
Pixel update = list_sorted_pixels.remove(0);
updates.remove(update);
count++;
if (update.getX() >= 0 && update.getY() >= 0) {
writable_image.getPixelWriter().setColor(update.getX(), update.getY(), update.getColor());
}
}
if (count >= max.get()) {
this.count = 0;
this.start = -1;
this.max.set(Long.MAX_VALUE);
stop();
}
}
public void setWritableImage(WritableImage writable_image) { this.writable_image = writable_image; }
public void startAnimation() {
this.start();
}
}
the code i'm using works just fine in swift for iPhone apps but not in the WatchKit 7.0 beta. the outlets and actions are different. I'm not sure what needs to change to make it work in WatchKit. please help!
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
#IBOutlet var spinButton: WKInterfaceButton!
var isRotating = false
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
#IBAction func spinAction() {
if !isRotating {
// create a spin animation
let spinAnimation = CABasicAnimation()
// starts from 0
spinAnimation.fromValue = 0
// goes to 360 ( 2 * π )
spinAnimation.toValue = M_PI*2
// define how long it will take to complete a 360
spinAnimation.duration = 1
// make it spin infinitely
spinAnimation.repeatCount = Float.infinity
// do not remove when completed
spinAnimation.removedOnCompletion = false
// specify the fill mode
spinAnimation.fillMode = kCAFillModeForwards
// and the animation acceleration
spinAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// add the animation to the button layer
spinButton.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
} else {
// remove the animation
spinButton.layer.removeAllAnimations()
}
// toggle its state
isRotating = !isRotating
}
}
You are limited to a subset of all the APIs available on iOS when developing for the watchOS.
If you want to do basic animations try out a WKInterfacePicker and change images when the digital crown is moved.
IBOutlet WKInterfacePicker *myPicker;
- (void)willActivate {
[super willActivate];
WKPickerItem *item1 = [[WKPickerItem alloc] init];
item1.contentImage = [WKImage imageWithImageName:#"Unknown.png"];
WKPickerItem *item2 = [[WKPickerItem alloc] init];
item2.contentImage = [WKImage imageWithImageName:#"Unknown-2.png"];
[self.myPicker setItems:array];
}
When the value exceeds the array count start over from index 0.
- (IBAction)myPickerAction:(NSInteger)value {
if (value % 2 == 0) {
[self.myPicker setSelectedItemIndex:-1];
}
}
This will make the WKInterfacePicker change between your images when the digital crown is rotated.