Scroll to select: image gallery tutorial

scroll-to-select-image

 

 

Every now and then in a UI you need the user to select an option among many, there are several ways to do so, one method could be the good’ole command line, another could be the use of toggles in a group (or radio button in Android/HTML environment), but that’s not nearly as cool as just looking at the thing you want and it being already selected. So today we’ll implement a scroll to select UI interface in unity3d.

We’ll do this in the image gallery that we’ve been building here for quite a while, here I’ll also give a unified version of the code with all of the features in it.

The idea is to define an area where the selection happens, so that whatever image happens to be in the area gets selected automatically. Of course the snapping behaviour works well with this but it’s not essential, since we are defining an area, not a single position.

Scroll to select area: let’s build it

It will go something like this:

select a ninja by scrolling a gallery of images
Don’t move and select, but select by moving

Let’s get into the code. First we’ll need a new animation curve and a place to store the selected image information:

    public AnimationCurve selectionArea;
    public RectTransform selected;

We’ll use a very narrow spike function, it’s supposed to only be over .75 in the selection area (with respect to the alpha we used all along).

Narrow spike function
Make it larger to have a larger area of selection. Caos will ensue if two images can be both in the area at a time

And to complete the opera at the very end of our refresh image foreach loop we’ll insert this

        if (selectionArea.Evaluate(transformedPosition) >= 0.75f)
        {
            selected = img;
        }

This way only the image that’s been scrolled to the center is in the range for which the selectionArea curve evaluates to more than .75, which means only that image gets selected. It’s important to use a very narrow area so that only one image at time can get selected.

And that’s all for the scroll to select part of this tutorial!

This one was very easy, wasn’t it? So, at long last we are over this whole guide and you now have a fully functional scroll-to-select image gallery that looks fucking awesome! Of course there’s a new tutorial coming soon, don’t lose anything, join my newsletter.

A recap to put EVERYTHING in one place for you to copy-paste:

    public AnimationCurve orderFactor;
    public AnimationCurve positionCurve;
    public AnimationCurve zoomCurve;
    public AnimationCurve selectionArea;
    public RectTransform selected;

    public RectTransform[] imgset;
    public float rotationSensibility = 1f; //how sensible is to swipe input
    protected float alphaStep = 1f; //will host the image-to-image distance.
    public float swipeFieldWidth = 300f;//Total distance from leftmost position to rightmost one
    [SerializeField]    protected float alpha; //the parameter around wich all things revolve
    protected float lastFrameAlpha; //last frame's alpha value
    public CatchSwipe inputSource; //A module to handle input that gives us the net x-axis swipe value
    Dictionary<RectTransform, float> alphaoff = new Dictionary<RectTransform, float>();//a dictionary to store all image's offsets

    [Header("inertia Parameters")]
    public bool useInertia;
    protected float inertia;
    public float inertialDampening = 0.9f; 
    public float inertiaPersistenceFactor = 10f;

    [Header("Magnetizaiton Parameters")]
    public bool doMagnetize = true;
    public float magnetizationMaxForceFactor = 0.09f;
    public float magnetizationRangeAlphaStepFraction = 0.5f;
    protected float magneticForce;

    public virtual void Awake()
    {
        if (useInertia)
            doMagnetize = false;
    }

    public virtual void Start()
    {
        alphaInitialization();
        refreshImages();
    }

    protected virtual void alphaInitialization()
    {
        alpha = 1;
        if (imgset.Length > 1)
            alphaStep = 1f / (imgset.Length - 1f);
        float minAlpha = -(imgset.Length - 1f) * alphaStep / 2f;
        alphaoff.Clear();
        foreach (var item in imgset)
        {
            alphaoff.Add(item, minAlpha);
            minAlpha += alphaStep;
        }
        lastFrameAlpha = alpha;
    }

    protected virtual void Update()
    {
        updateAlpha();
        updateInertia();
        updateMagnetization();
        if (Mathf.Abs(lastFrameAlpha - alpha) > 0.001f)
        {
            refreshImages();
        }
        lastFrameAlpha = alpha;
    }
    protected virtual void updateAlpha()
    {
        alpha = Mathf.Clamp(
                alpha + (Mathf.Abs(inputSource.NetSwipe.x) > 0 ?
                    inputSource.NetSwipe.x * rotationSensibility
                    :
                    inertia + magneticForce), 
                0,
                1
                );
    }
    protected virtual void updateInertia()
    {
        if (useInertia)
        {
            inertia = Mathf.Abs(inputSource.NetSwipe.x) > 0 ? alpha - lastFrameAlpha : inertialDampening * inertia;
            if (Mathf.Abs(inertia) < rotationSensibility / inertiaPersistenceFactor)
                inertia = 0;
        }
    }


    protected virtual void updateMagnetization()
    {
        if ((Mathf.Abs(inputSource.NetSwipe.x) <= 0.001) && (doMagnetize))
        {
            float rest = alpha % alphaStep;
            if (rest < magnetizationRangeAlphaStepFraction * alphaStep)
            {
                if (rest < rotationSensibility)
                    magneticForce = -rest;
                else
                    magneticForce = -rest * magnetizationMaxForceFactor;
            }
            else if (rest > alphaStep * (1 - magnetizationRangeAlphaStepFraction))
            {
                if (rest < rotationSensibility)
                    magneticForce = rest;
                else
                    magneticForce = (alphaStep - rest) * magnetizationMaxForceFactor;
            }
        }
    }

    protected virtual void refreshImages()
    {
        List<KeyValuePair<RectTransform, float>> orderingLayerList = new List<KeyValuePair<RectTransform, float>>();
        foreach (var img in imgset)
        {
            float transformedPosition = positionCurve.Evaluate(alpha + alphaoff[img]);
            img.localPosition = Vector3.right * (transformedPosition * swipeFieldWidth - (swipeFieldWidth / 2f));
            img.localScale = Vector3.one * (zoomCurve.Evaluate(transformedPosition));
            orderingLayerList.Add(new KeyValuePair<RectTransform, float>(img, orderFactor.Evaluate(alpha + alphaoff[img])));
            if (selectionArea.Evaluate(transformedPosition) >= 0.75f)
            {
                selected = img;
            }
        }
        var orderByVal = orderingLayerList.OrderBy(kvp => kvp.Value);
        foreach (var item in orderByVal)
        {
            item.Key.SetAsLastSibling();
        }
    }

And this finally ends this lenghty tutorial. I hope you will find it useful 🙂
Tell me what you think about it and follow me on twitter for more useful stuff.

 

Share
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Leave a Reply

Your email address will not be published.