I've been trying out shared element transition on Lollipop. i have a recyclerview which loads some cards and one click the card expands to its details in the next activity.
I have set a ripple effect and a StateListAnimator on the card. But those are not visible cause the transition starts before these effects are completed.
Is there any way to delay the transition so that it can wait for the statelist animator and ripple to complete?
Here is the code I use
ActivityOptions options = null;
if (Utilities.isLollipop()) {
options = ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(view, "hero_view"), Pair.create((View) fab, "fab"));
startActivity(detailIntent, options.toBundle());
}
Thanks in advance
I ended up using a work around, but I still would want to know what is the right way of doing this and therefore leaving the question open.
The work around I did was
1. remove the state list animator and add that as an animator in the onclick method.
2. Used a Handler to post a delayed call to the activity
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Intent i=new Intent(SearxhJobs.this,JobsTypes.class);
startActivity(i);
}
}, 200);
P.S- I did end up removing the effect as it was not very intuitive.
The way that may help some that already have the Transitions setup in Second Activity:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().getSharedElementEnterTransition().setDuration(500);
getWindow().getSharedElementReturnTransition().setDuration(500)
.setInterpolator(new DecelerateInterpolator());
}
Related
We have a landing page that has this layout:
When you click on one of the section buttons, the ObservableCollection in the ViewModel is set to another list as so:
private async void processtableOfContentsListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var tocItem = e?.Item as TableOfContentsItem;
if (viewModel.SelectedTableOfContentsItem == tocItem)
{
return;
}
viewModel.CurrentSectionItems = tocItem.SectionItems;
viewModel.SelectedTableOfContentsItem = tocItem;
}
This is simplified as there's other operations going on.
My problem is that the CollectionView is taking too long to change sections. There's a noticeable delay when trying to load up the new items. To be fair, the DataTemplate's for the items are a little complex. I've simplified them as much as possible, but you can still notice a delay in trying to draw them.
I'd like to speed this change up as much as possible, but its starting to look like I may need to just deal with the delay itself instead of freezing. It would be nice to have a loading indication in between changing sections. Like an ActivityIndicator. But I can't seem to find how to make UI changes in the beginning of the EventHandler for the buttons. Even if I clear the ObservableCollection in the beginning of the event handler, it still will wait until the method finishes before showing any UI changes.
I've checked and the event handler is running on the main UI thread. I've even tried running the rest of the code on a background thread after changing the section to see if it would before continuing the method, but no luck:
private async void processtableOfContentsListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
if (MainThread.IsMainThread) //is true
{
}
var tocItem = e?.Item as TableOfContentsItem;
await Task.Run(async () =>
{
await MainThread.InvokeOnMainThreadAsync(() => {
viewModel.SelectedTableOfContentsItem = tocItem;
viewModel.CurrentSectionItems = tocItem.SectionItems;
});
});
Task.Run(() => DoWork(sender, e));
}
My question is two parted.
Does anyone have an idea how to speed up the redrawing of the CollectionView?
If not, how do I set off the UI change to clear the CollectionView in the beginning of the event handler so I can add a loading indicator?
I ended up making separate collection views for each section. There is a delay in initializing the list when you first switch to it, but switching between sections has become instant.
I need to add back navigation on swipe. I can do that fairly easily by just adding a swipe listener to the page view and calling goBack. But I really would like the animation that goes with it (in Instagram or FB) where as soon as you start dragging your thumb, the page translates to the right and the previous page starts to translate into view. And then once you get to a certain point it actually performs the navigation.
I tried animating the page, as well as the frame to the right figuring since the view isn't being destroyed it might work. But it doesn't display the page Im navigating back to.
Looking for help on how to accomplish this!
I guess you might have come across the other SO thread answering this question natively.
All you have to do is modify the default gesture recogniser on iOS frame.
export function onNavigatedFrom(args: EventData) {
console.log("Adding gesture...");
const frame = (<Page>args.object).frame;
if (frame.ios && !(<any>frame)._gestureRecognizer) {
const controller = frame.ios.controller;
const popGestureRecognizer = controller.interactivePopGestureRecognizer;
const targets = popGestureRecognizer.valueForKey("targets");
if (targets) {
let gestureRecognizer = UIPanGestureRecognizer.alloc().init();
gestureRecognizer.setValueForKey(targets, "targets");
frame.nativeView.addGestureRecognizer(gestureRecognizer);
(<any>frame)._gestureRecognizer = gestureRecognizer;
}
}
}
export function onNavigatedTo(args: EventData) {
console.log("Back to root page, removing gesture...");
const frame = (<Page>args.object).frame;
if (frame.ios && (<any>frame)._gestureRecognizer) {
frame.nativeView.removeGestureRecognizer((<any>frame)._gestureRecognizer);
(<any>frame)._gestureRecognizer = null;
}
}
Playground Sample
FirstScreen.hpp
FirstScreen.cpp
void FirstScreen:: launchSecondScreen(){
SecondScreen secondScreen;
}
firstscreen.qml
Page {
Container {
Button {
onClicked: {
//Launch second screen
console.log("This will print")
_app.launchSecondScreen();
}
}
}
}
SecondScreen.hpp
SecondScreen.cpp
void SecondScreen:: launchThirdScreen(){
ThirdScreen thirdScreen;
}
secondscreen.qml
Page {
Container {
Button {
onClicked: {
//Launch third screen
console.log("This will not print")
_app.launchThirdScreen();
}
}
}
}
What happen once I launch second screen on top of first one click event not trigger. Please go through the link for complete project https://github.com/amityadav1984/BB10MultiscreenIssue
Because of that I have to use NavigationPane.
Thanks for providing a Minimal, Complete, and Verifiable example, it really helps to get all the information needed in order to test and replicate.
First off, let me explain what's happening and then I'll explain how to fix it.
The reason it's not working :
You're creating Screen2 object in a method, the object will be deleted at the end of the method. So basically, you're on Screen1, you click on the button which triggers your launchScreen2 method, this method creates a Screen2 object that sets the new visual to Screen2.qml and then the method delete Screen2 object before exiting. When you click on the button on Screen2.qml page, the _app context doesn't exists anymore.
Why is it working with Screen1 in main.cpp then? Because main.cpp will enter the main event loop before the end of the method, so Screen1 will not be deleted until the main event loop finishes (app exit).
How to fix :
1) Create Screen2 as a pointer :
Screen2* screen2 = new Screen2();
While this will work, be aware that you're responsible for deleting the object when you're done with it, otherwise you'll have memory leaks. A good practice is also to set parent whenever possible, that way you're sure that if the parent is deleted, all children will be too.
screen2->setParent(this);
Of course, while setting parent is a good practice, an even better practice is to delete any object as soon as they are not needed anymore, keeping your memory footprint as low as possible.
2) Declare Screen2 as private variable in Screen1.hpp, to make it a global variable.
Note that by doing so, you need to move the code in Screen2 constructor inside a public method that you'll have to call in Screen1::launchScreen2(), something like screen2.setScene().
Let me know if you need more information.
I load a Canvas prefab at runtime when an event occurs. The canvas simply has a Panel inside it, which in turn has 2 buttons. I'm trying to add OnClick events to both these buttons in the script, but it only works for the first button somehow!
I have the following two lines of code one after the other:
GameObject.Find("RestartButton").GetComponent<Button>().onClick.AddListener(() => RestartClicked());
GameObject.Find("ViewButton").GetComponent<Button>().onClick.AddListener(() => ViewClicked());
The callback only works for the RestartButton, and not for the ViewButton.
It may well be a very small thing, but I searched Google and Bing extensively and remain clueless, so any help would be appreciated.
Thanks!
Edit:
The script instantiates the prefab and tries to reference the two buttons in the prefab through GameObject.Find(), given that once Instantiate is called the Canvas should be active in the heirarchy.
I also opened up the part of the code that attaches buttons to the listener for debugging. I dont think it attaches the listener at all.
void Start () {
SetupScene();
var prefab = Resources.Load("RestartOrViewPrefab");
if (prefab == null)
{
Debug.LogAssertion("Prefab missing!");
return;
}
restartCanvas = (GameObject)Instantiate(prefab);
Button btn = GameObject.Find("ViewButton").GetComponent<Button>();
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() => ViewToggle());
Button btn2 = GameObject.Find("RestartButton").GetComponent<Button>();
btn2.onClick.AddListener(() => RestartClicked());
}
private void RestartClicked()
{
Debug.Log("RESTARTING");
SetupScene();
}
private void ViewToggle()
{
Debug.Log("TOGGLE");
if (freshStart)
{
//Something here
freshStart = false;
}
else
{
//Something else here
freshStart = true;
}
}
So I took two days to solve my problem. Apparently, doing a GameObject.Find() for a GameObject within a prefab that you just instantiated doesn't work. We always need to use the GameObject that the Instantiate() method returns to find any component within the prefab.
The code I used to make my scene work may not be the best solution if your prefab is very big/complex (say you have 15 buttons and need to do something with one), but it sure does work. :)
I replaced the following lines of code:
Button btn = GameObject.Find("ViewButton").GetComponent<Button>();
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() => ViewToggle());
Button btn2 = GameObject.Find("RestartButton").GetComponent<Button>();
btn2.onClick.AddListener(() => RestartClicked());
With the following lines of code:
Button[] buttons = restartCanvas.GetComponentsInChildren<Button>();
foreach(Button but in buttons)
{
if(but.gameObject.name == "RestartButton")
but.onClick.AddListener(() => RestartClicked());
else if(but.gameObject.name == "ViewButton")
but.onClick.AddListener(() => ViewToggle());
}
So I just reused the restartCanvas that I got a reference to.
restartCanvas = (GameObject)Instantiate(prefab);
Hope this helps someone. :)
I answered this on another post, but I'll answer it here for people that still need this info.
The GameObject your instantiated button is a child of must have a CanvasRenderer component on it.
I'm not sure why this works, but it does.
Once the parent of the button GameObject has the CanvasRenderer on it, you can call myButton.onClick.AddListener(() => MyMethod(MyArgs)); as you normally would.
Ok make sure you are not getting any null reference error, and check tht spelling of you buttons , output some debug logs in your second function to see if its even reaching there
In trying to figure out how to start and stop Sprite animations from a SpriteSheet, I tried this:
// other code...
// define animations in SpriteSheet:
"animations": {"intro": [0, 19, "false"]}
// other code...
var spriteSheet = new createjs.SpriteSheet(data);
var intro = new createjs.Sprite(spriteSheet, "intro");
stage.addChild(intro);
createjs.Ticker.addEventListener("tick", stage);
intro.stop();
var btnStart = document.getElementById("btnStart");
btnStart.onclick = function() {
console.log("btnStart clicked");
intro.on("animationend", onStartAnimEnd);
intro.play();
};
function onStartAnimEnd(e) {
intro.removeEventListener("animationend", onStartAnimEnd);
console.log("Start anim ended");
}
In the above example, if the user clicks the btnStart button, the onStartAnimEnd callback fires indefinitely, even though by defining "false" in the animation configuration to signal we want to stop on the last frame, and the animation does in fact stop, and I'm removing the event listener in the first line of the callback.
If I add:
function onStartAnimEnd(e) {
intro.removeEventListener("animationend", onStartAnimEnd);
intro.stop();
console.log("Start anim ended");
}
The problem goes away, but that doesn't seem right... So, if I change the listener assignment of the animationend event from:
intro.on("animationend", onStartAnimEnd);
to:
ask.addEventListener("animationend", onAskAnimEnd);
...and with this change, the indefinite event captures goes away. So my questions are:
What's the difference with these two event listener assignments?
Is this animationend event continually firing in the background because we're updating the stage on the tick event, even though nothing needs re-rendering?
Thanks for your time!
This is actually a duplicated question. As I answered you previous question, your animation definition is wrong, you need to use the boolean value (false) and not the string value ("false").
Now sure what ask is, but the method on is a wrapper for addEventListener, and where you can specify things such as the scope of the callback and if it will run only once. Take a look at the API to know more:
http://www.createjs.com/Docs/EaselJS/classes/EventDispatcher.html#method_on