How to animate on item list replacement

Hi,

I’m trying to create some kind of list re-arragement / ordering with swipe gesture, so far it’s working as workaround without native drag n drop list order/sort. But in this solution, what is lack is the animation when the item is switched. There are several alternative way that I could think of that but I don’t know how to achieve that

  1. Triggering animation layout when the item position in the list has changed (listening to replaceAt)
  2. Triggering remove and add event for replaceAt method in the list
  3. Adding a method to add an item in the list for specific index (such as splice) (so I could use removeAt and addAt instead of replace)
  4. Add an animation trigger detecting when the property in an item has changed

Here is the code I’m working with. Currently I use the replaceAll method items.replaceAll(items.toArray()); to re arrange the list within the layout, but the layout animation won’t work as expected and add / remove animation would make the animation looks wierd.

Any suggestion are welcome :slight_smile:

Thanks

<App Theme="Basic">
  <Panel>

  <JavaScript>
  var Observable = require("FuseJS/Observable");
  var col = Observable(3);
  var items = Observable();

  for(var i = 0;i<10;i++){
    var o = new Item(i);
    items.add(setup(o,i));
  }

  function Item(id){
    var d = (10000000 + id * 100000);
    this.color = '#' + d.toString(16);
    this.id = id;
  }

  function setup(item,n){
     item.pos = Observable(n);

     item.right = Observable(function(){
       if((item.pos.value + 1)%col.value===0||(item.pos.value + 1)>=items.length)return false
       else return true;
     })

     item.left = Observable(function(){
       if((item.pos.value + 1 )%col.value===1)return false
       else return true;
     })

     item.top = Observable(function(){
       if((item.pos.value +1) - col.value <= 0)return false
       else return true;
     })

     item.bottom = Observable(function(){
       if((item.pos.value +1) + col.value > items.length)return false
       else return true;
     })

     return item;
  }

  function switchPos(currentPos,targetPos){
     var targetItem = items.getAt(targetPos);
     var currentItem = items.getAt(currentPos);
     targetItem.pos.value = currentPos;
     currentItem.pos.value = targetPos;
     items.replaceAt(targetPos, currentItem);
     items.replaceAt(currentPos , targetItem);
     items.replaceAll(items.toArray());
     return currentItem;
  }

  function addItem(arg){
    var len = items.length;
    var newItem = new Item(len);
    items.add(setup(newItem,len));
  }

  function swipeRight(arg){
    var n = items.indexOf(arg.data);
    var r = switchPos(n, n + 1);
  }

  function swipeLeft(arg){
    var n = items.indexOf(arg.data);
    switchPos(n, n - 1);
  }

  function swipeDown(arg){
    var n = items.indexOf(arg.data);
    switchPos(n, n + col.value);
  }

  function swipeUp(arg){
    var n = items.indexOf(arg.data);
    switchPos(n, n - col.value);
  }

  module.exports = {
    items: items,
    col : col,
    addItem : addItem,
    swipeRight : swipeRight,
    swipeLeft : swipeLeft,
    swipeUp : swipeUp,
    swipeDown : swipeDown
  };

  </JavaScript>

    <Panel Alignment="Top">
     <ColumnLayout ColumnCount="{col}" />
     <Each Items="{items}">
     <Panel ux:Name="panelBox">
          <DoubleClicked Handler="{addItem}"/>
          <LayoutAnimation>
            <Move RelativeTo="PositionChange" X="1" Y="1" Duration="0.5" Easing="Linear" />
          </LayoutAnimation>

          <Viewbox>
             <Rectangle ux:Name="box" Margin="3"  Fill="{color}" Width="200" Height="200">
                <Circle ux:Name="circ" Width="50" Height="50" Fill="#fff" Opacity="0.5">
                  <Text Value="{id}" Alignment="Center" FontSize="24" />
                </Circle>
             </Rectangle>
          </Viewbox>

          <WhileTrue Value="{left}">
             <SwipeGesture ux:Name="swipeL" Direction="Left" Length="100" Type="Active"/>
          </WhileTrue>
          <WhileTrue Value="{right}">
             <SwipeGesture ux:Name="swipeR" Direction="Right" Length="100" Type="Active"/>
          </WhileTrue>
          <WhileTrue Value="{top}">
             <SwipeGesture ux:Name="swipeU" Direction="Up" Length="100" Type="Active"/>
          </WhileTrue>
          <WhileTrue Value="{bottom}">
             <SwipeGesture ux:Name="swipeD" Direction="Down" Length="100" Type="Active"/>
          </WhileTrue>

         <SwipingAnimation Source="swipeR">
            <BringToFront/>
            <Move X="1" RelativeTo="Size" RelativeNode="panelBox" />
          </SwipingAnimation>
         <SwipingAnimation Source="swipeL">
            <BringToFront/>
            <Move X="-1" RelativeTo="Size" RelativeNode="panelBox" />
         </SwipingAnimation>
         <SwipingAnimation Source="swipeU">
           <BringToFront/>
           <Move Y="-1" RelativeTo="Size" RelativeNode="panelBox" />
         </SwipingAnimation>
         <SwipingAnimation Source="swipeD">
           <BringToFront/>
           <Move Y="1" RelativeTo="Size" RelativeNode="panelBox" />
         </SwipingAnimation>

         <Swiped Source="swipeR">
             <Callback Handler="{swipeRight}"/>
         </Swiped>
         <Swiped Source="swipeL">
             <Callback Handler="{swipeLeft}"/>
         </Swiped>
         <Swiped Source="swipeU">
             <Callback Handler="{swipeUp}"/>
         </Swiped>
         <Swiped Source="swipeD">
             <Callback Handler="{swipeDown}"/>
         </Swiped>

       </Panel>
     </Each>
    </Panel>
  </Panel>
</App>

Hi Sahal,

Animating reorder is a tricky use case, and I can’t think of any easy way to do it from just UX/JS.

We need Uno-level tweaks to accommodate this use case, so unless you want to hack around with that, you will have to wait for a fix/example from the community or the Fuse team.

One solution could be to mod the Each operator so it can detect rearrangements and handle them differently from add/remove pairs. Then you could be able to use LayoutAnimation for this case.

Even more fancy, it would be cool to have a DraggableReorder behavior similar to Draggable which would let you simply encapsulate all of this magic.

In any case, these features don’t exist yet (as of 0.12/0.20) but I’m filing a ticket for this and we will look into it later.

Hi Anders,

Thanks for the insight. I was planning to try some hacking, and digging up the generated android source code and found out the undocumented observable API from the assets folder, turns out you have insertAt method , the last ingridient I need

/* ----- Observable ------

    Message API:

    set(value) - Set a single value (at index 0)
    newAt(index, value) - New value at the given index
    newAll(array) - Assume all values dirty. Array length must be != 1 (use set() for length == 1)
    add(value) - Add to end of list
    remove(object, index) - Remove object at index
    insertAt(index, value) - Insert value at index
*/

And then I finish my code with this


function switchPos(currentPos,targetPos,direction){
     var targetItem = items.getAt(targetPos);
     var currentItem = items.getAt(currentPos);
     targetItem.pos.value = currentPos;
     currentItem.pos.value = targetPos;

     currentItem.moveX.value = currentItem.moveY.value = 0;
     if(direction==='right'){
        targetItem.moveX.value = 0.5;
        targetItem.moveY.value = 0;
     }
     if(direction==='left'){
        targetItem.moveX.value = -0.5;
        targetItem.moveY.value = 0;
     }
     if(direction==='up'){
        targetItem.moveY.value = -0.5;
        targetItem.moveX.value = 0;
     }
     if(direction==='down'){
         targetItem.moveY.value = 0.5;
         targetItem.moveX.value = 0;
     }

     items.removeAt(currentPos);
     items.insertAt(currentPos,targetItem);
     items.removeAt(targetPos);
     items.insertAt(targetPos,currentItem);

     return currentItem;
  }

and put the add animation in the UX

<AddingAnimation>
                <Move RelativeTo="Size" X="{moveX}" Y="{moveY}" Duration="0.5" Easing="CubicIn"/>
            </AddingAnimation>

It’s not the perfect solution, but at least I’m happy with this while waiting for the real DraggableReoder to come :slight_smile:

https://github.com/zean00/fuse-swipeorder-sample

Thanks for the quick response

Any news about DraggableReorder?

Juanlu: Nope, sorry. It hasn’t reached the top of our priority list yet.

This is something that could just as well be a community package though (doesn’t have to be built into the core platform), so nothings stopping you or someone else from giving it a go in Uno code.

Heads up!

We now have a Drag to reorder example on the site.