Question

In continuation of this similar type of effect, [Creating animation] (Creating animation for images from small to large when scrolling vertical) I need to tap to open the image instead of dragging the image till top and down. How to do that? any idea?

private double scrollProgress = 0.0;
private double topViewScaleFactor = 2.0;
private double collapsedViewHeight = 200.0;
private double expandedViewHeight = 700.0;
private double scrollProgressPerView = expandedViewHeight;

private final ScrollTouchListener touchListener = new ScrollTouchListener() {
    @Override
    protected void onScroll(float x, float y) {
        scrollProgress += y;
        if(scrollProgress < 0.0) {
            scrollProgress = 0.0;
        }

This just makes scroll from image to top. I need to tap to open image. How to do that?

Thanks in advance.

Was it helpful?

Solution

Ok I played around the custom view from the question you are linking to (Creating animation for images from small to large when scrolling vertical) and adding animations is pretty easy. The result looks like this:

Scrolling Clicking

First we have to make the custom view snap to a certain item. For that we have to modify the ScrollTouchListener like this:

public abstract class ScrollTouchListener implements View.OnTouchListener {

    private boolean touching = false;
    private boolean scrolling = false;
    private double x = 0;
    private double y = 0;

    private double scrollPositionX = 0;
    private double scrollPositionY = 0;

    @Override
    public boolean onTouch(View view, MotionEvent event) {


        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x = event.getX();
                y = event.getY();
                touching = true;
                return true;

            case MotionEvent.ACTION_UP:
                touching = false;
                if(scrolling) {
                    scrolling = false;
                    onScrollEnded(scrollPositionX, scrollPositionY);
                } else {
                    onClick(x, y); 
                }
                return true;

            case MotionEvent.ACTION_MOVE:
                double newX = event.getX();
                double newY = event.getY();

                double difX =  x - newX;
                double difY =  y - newY;

                if (scrolling) {
                    performScroll(difX, difY);
                } else if(difX > 0 || difY > 0)  {
                    scrolling = true;
                    onScrollStarted(scrollPositionX, scrollPositionY);
                    performScroll(difX, difY);
                }

                x = newX;
                y = newY;
                return true;

            default:
                return false;
        }
    }

    protected abstract void onScrollStarted(double scrollPositionX, double scrollPositionY);
    protected abstract void onScroll(double scrollPositionX, double scrollPositionY, double deltaX, double deltaY);
    protected abstract void onScrollEnded(double scrollPositionX, double scrollPositionY);
    protected abstract void onClick(double x, double y);

    private void performScroll(double difX, double difY) {
        scrollPositionX += difX;
        scrollPositionY += difY;

        onScroll(scrollPositionX, scrollPositionY, difX, difY);
    }

    public double getScrollPositionX() {
        return scrollPositionX;
    }

    public void setScrollPositionX(double scrollPositionX) {
        this.scrollPositionX = scrollPositionX;
    }

    public double getScrollPositionY() {
        return scrollPositionY;
    }

    public void setScrollPositionY(double scrollPositionY) {
        this.scrollPositionY = scrollPositionY;
    }
}

Essentially I just added callback for onScrollStarted() which is called when the user starts scrolling - in this case we don't need that - and onScrollEnded() which is called when the user stops scrolling. I moved the responsibility of keeping track of the scroll progress to the ScrollTouchListener for convenience and I also added getters and setters so we can modify the scroll progress if we need to - in our case so the views can snap into a certain position. Another thing I added is click detection in the form of the onClick callback. We need that so we can click a view to make it expand. We can't use a normal OnClickListener for this because we are adding a OnTouchListener in our case the ScrollTouchListener which consumes all touch events and so click listeners stop working. But the click detection is not perfect as it is implemented there. The main problem is that a click only counts if you do not move your finger. If you move your finger even by only one pixel it already counts as a scroll. There should be some extra logic here that you can move your finger by a few pixel and it still counts as a click, if necessary this can be implemented later.

Then in the custom view we add a calculation to the implementation of the onScroll() method in the ScrollTouchListener which calculates the index of the currently expanded view and of the first visible view at the top:

double relativeScroll = scrollPositionY / scrollProgressPerView;
currentItemIndex = (int)Math.round(relativeScroll);

currentItemIndex is a member variables of the custom view. After that add the implementation of the onScrollEnded() and onClick() methods. All we do there is calculate the how far the custom view should be scrolled. Either to snap to a certain item or to scroll to the clicked item:

@Override
protected void onScrollEnded(double scrollPositionX, double scrollPositionY) {
    scrollProgress = currentItemIndex * scrollProgressPerView + 1;
    setScrollPositionY(scrollProgress);
    updateChildViews();
}

@Override
protected void onClick(double x, double y) {
    int clickedIndex = (int) Math.round((getScrollPositionY() + y) / scrollProgressPerView) + 1;
    scrollProgress = clickedIndex * scrollProgressPerView + 1;
    setScrollPositionY(scrollProgress);
    updateChildViews();
}

And that's it. This is all you need to enable the snapping and the selecting of Views. As a result the whole implementation of the ScrollTouchListener in the custom view should look something like this:

private final ScrollTouchListener touchListener = new ScrollTouchListener() {

    @Override
    protected void onScrollStarted(double scrollPositionX, double scrollPositionY) {
        // Nothing to do here
    }

    @Override
    protected void onScroll(double scrollPositionX, double scrollPositionY, double deltaX, double deltaY) {
        scrollProgress += deltaY;
        if(scrollProgress < 0.0) {
            scrollProgress = 0.0;
        }

        if(scrollProgress > maxScrollProgress) {
            scrollProgress = maxScrollProgress;
        }

        double relativeScroll = scrollPositionY / scrollProgressPerView;
        currentItemIndex = (int)Math.round(relativeScroll);

        updateChildViews();
    }

    @Override
    protected void onScrollEnded(double scrollPositionX, double scrollPositionY) {
        scrollProgress = currentItemIndex * scrollProgressPerView + 1;
        setScrollPositionY(scrollProgress);
        updateChildViews();
    }

    @Override
    protected void onClick(double x, double y) {
        int clickedIndex = (int) Math.round((getScrollPositionY() + y) / scrollProgressPerView) + 1;
        scrollProgress = clickedIndex * scrollProgressPerView + 1;
        setScrollPositionY(scrollProgress);
        updateChildViews();
    }
};

You might wonder know where the actual animation stuff comes in, but we actually don't have to take care of that. There is one very convenient feature that handles all the animation for us. You just need to add this to attribute to the custom view in the xml layout:

android:animateLayoutChanges="true"

For example the layout I used for testing purposes looked like this:

<at.test.app.customviews.accordion.view.Accordion xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:animateLayoutChanges="true"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <ImageView android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:scaleType="centerCrop"
               android:src="@drawable/alpen"/>

    <ImageView android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:scaleType="centerCrop"
               android:src="@drawable/alpen"/>

    <ImageView android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:scaleType="centerCrop"
               android:src="@drawable/alpen"/>

    <ImageView android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:scaleType="centerCrop"
               android:src="@drawable/alpen"/>

</at.test.app.customviews.accordion.view.Accordion>

Notice the android:animateLayoutChanges="true" I added to the custom view. That is all you need to get this view to animate any layout change.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top