SoFunction
Updated on 2025-04-10

Android realizes rubber band rebound and pan zoom effects

This example shares the specific code for Android to implement elastic band rebound and pan zoom for your reference. The specific content is as follows

Preface

Since I have been doing a view's translation and scaling function and rubber band effect recently, most of the ones I found online are implemented separately, so I have integrated these two functions together

Code implementation

Here I write the effects separately and finally merge them

Pan, zoom

import ;
import ;
import ;
import ;
import ;

/**
 * Created by ChenZehao
 * on 2019/8/4
 */
public class mLayout extends FrameLayout{

    // Attribute variables    private float translationX; // Mobile X    private float translationY; // Mobile Y    private float scale = 1; // Scaling ratio
    // Temporary variables during movement    private float actionX;
    private float actionY;
    private float spacing;
    private int moveType; // 0=Not selected, 1=Drag, 2=Scale    private float firstX;
    private float firstY;


    public mLayout(Context context) {
        this(context, null);
    }

    public mLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public mLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        (event);

        switch (() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                moveType = 1;
                actionX = ();
                actionY = ();
                firstX = actionX;
                firstY = actionY;

                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                moveType = 2;
                spacing = getSpacing(event);
                break;
            case MotionEvent.ACTION_MOVE:
                if (moveType == 1) {
                    translationX = translationX + () - actionX;
                    translationY = translationY + () - actionY;
                    ();
                    setTranslationX(translationX);
                    setTranslationY(translationY);
                    actionX = ();
                    actionY = ();
                }
                else if (moveType == 2) {
                    scale = scale * getSpacing(event) / spacing;

                    if(scale >= 1){
                        setScaleX(scale);
                        setScaleY(scale);
                    }else{
                        scale = 1;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                moveType = 0;
                break;
        }
        return true;
    }

    // Touch the distance between two points    private float getSpacing(MotionEvent event) {
        //Get the distance between two points through the trigonometric function        float x = (0) - (1);
        float y = (0) - (1);
        return (float) (x * x + y * y);
    }

}

Rubber band rebound

import ;
import ;
import ;
import ;
import ;

/**
 * Created by ChenZehao
 * on 2019/8/4
 */
public class mLayout extends FrameLayout{

    //The coefficient can be changed by yourself    private static final float DEFAULT_FATOR = 0.4f;
    /**
 * Damping factor
 */
    private float mFator = DEFAULT_FATOR;
    private Scroller mScroller;
    /**
 * Record the last touch event
 */
    private MotionEvent mLastMotionEvent;


    public mLayout(Context context) {
        this(context, null);
    }

    public mLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public mLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScroller = new Scroller(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        (event);

        switch (()) {
            case MotionEvent.ACTION_DOWN:
                mLastMotionEvent = (event);
                break;

            case MotionEvent.ACTION_MOVE:
                int dx = (int) (() - ());
                int dy = (int) (() - ());

                //If you don't want to add damping effect to the four directions, just delete it
                //Play up                if (((dx) < (dy)) && dy < 0){
                    smoothScrollBy(0, -(int) (dy * mFator));
                }
                //Play down                else if ((dx) < (dy) && dy > 0) {
                    smoothScrollBy(0, -(int) (dy * mFator));
                }
                //Play to the left                else if ((dx) > (dy) && dx < 0){
                    smoothScrollBy(-(int) (dx * mFator), 0);
                }
                //Play to the right                else if ((dx) > (dy) && dx > 0){
                    smoothScrollBy(-(int) (dx * mFator), 0);
                }
                mLastMotionEvent = (event);
                break;
            case MotionEvent.ACTION_UP:

            case MotionEvent.ACTION_CANCEL:
                smoothScrollTo(0, 0);
                break;
        }
        return true;
    }

    private void smoothScrollBy(int dx, int dy) {

        ((), (), dx, dy);
        invalidate();
    }

    private void smoothScrollTo(int fx, int fy) {

        int dx = fx - ();
        int dy = fx - ();
        smoothScrollBy(dx, dy);
    }


    @Override
    public void computeScroll() {
        if (()) {
            scrollTo((), ());
            postInvalidate();
        }
        ();
    }

}

Translation, zoom, damping effects merge

import ;
import ;
import ;
import ;
import ;

/**
 * Created by ChenZehao
 * on 2019/8/4
 */
public class mLayout extends FrameLayout{

    private float scale = 1; // Scaling ratio
    // Temporary variables during movement    private float actionX;
    private float actionY;
    private float spacing;
    private int moveType; // 0=Not selected, 1=Drag, 2=Scale    private float firstX;
    private float firstY;

    //The coefficient can be changed by yourself    private static final float DEFAULT_FATOR = 0.4f;
    /**
 * Damping factor
 */
    private float mFator = DEFAULT_FATOR;
    private Scroller mScroller;
    /**
 * Record the last touch event
 */
    private MotionEvent mLastMotionEvent;


    public mLayout(Context context) {
        this(context, null);
    }

    public mLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public mLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScroller = new Scroller(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        (event);

        switch (() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                mLastMotionEvent = (event);
                moveType = 1;
                actionX = ();
                actionY = ();
                firstX = actionX;
                firstY = actionY;

                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                moveType = 2;
                spacing = getSpacing(event);
                break;
            case MotionEvent.ACTION_MOVE:
                if (moveType == 1) {
                    int dx = (int) (() - ());
                    int dy = (int) (() - ());

                    //If you don't want to add damping effect to the four directions, just delete it
                    //Play up                    if (((dx) < (dy)) && dy < 0){
                        smoothScrollBy(0, -(int) (dy * mFator));
                    }
                    //Play down                    else if ((dx) < (dy) && dy > 0) {
                        smoothScrollBy(0, -(int) (dy * mFator));
                    }
                    //Play to the left                    else if ((dx) > (dy) && dx < 0){
                        smoothScrollBy(-(int) (dx * mFator), 0);
                    }
                    //Play to the right                    else if ((dx) > (dy) && dx > 0){
                        smoothScrollBy(-(int) (dx * mFator), 0);
                    }
                    mLastMotionEvent = (event);

                }
                else if (moveType == 2) {
                    scale = scale * getSpacing(event) / spacing;

                    if(scale >= 1){
                        setScaleX(scale);
                        setScaleY(scale);
                    }else{
                        scale = 1;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL:
                moveType = 0;
                if(scale == 1)
                    smoothScrollTo(0, 0);
                break;
        }
        return true;
    }

    private void smoothScrollBy(int dx, int dy) {

        ((), (), dx, dy);
        invalidate();
    }

    private void smoothScrollTo(int fx, int fy) {

        int dx = fx - ();
        int dy = fx - ();
        smoothScrollBy(dx, dy);
    }


    @Override
    public void computeScroll() {
        if (()) {
            scrollTo((), ());
            postInvalidate();
        }
        ();
    }

    // Touch the distance between two points    private float getSpacing(MotionEvent event) {
        //Get the distance between two points through the trigonometric function        float x = (0) - (1);
        float y = (0) - (1);
        return (float) (x * x + y * y);
    }

}

How to use

Add mLayout layout to the xml file to pan, scale, and dampen the controls and layouts in mLayout

Functional extension – add buttons to layout

If we add buttons in mLayout layout, we will have the problem of getting focus conflicts, which will lead to the inability to pan and other operations when touching the button. Therefore, we need to rewrite the button's dispatchTouchEvent function, so we need to create a class mButton to inherit the Button

The event is retrieved by the button when clicking, so the event must be returned to the parent view through dispatchTouchEvent, and then the onInterceptTouchEvent function of the parent view is called to process the intercepted event.

The code is as follows:

import ;
import ;
import ;

public class mButton extends . {


    public mButton(Context context) {
        super(context);
    }

    public mButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public mButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                break;
        }
        return (ev);
    }

}

import ;
import ;
import ;
import ;
import ;

/**
 * Created by ChenZehao
 * on 2019/8/4
 */
public class mLayout extends FrameLayout{

    private float scale = 1; // Scaling ratio
    // Temporary variables during movement    private float actionX;
    private float actionY;
    private float spacing;
    private int moveType; // 0=Not selected, 1=Drag, 2=Scale    private float firstX;
    private float firstY;

    //The coefficient can be changed by yourself    private static final float DEFAULT_FATOR = 0.4f;
    /**
 * Damping factor
 */
    private float mFator = DEFAULT_FATOR;
    private Scroller mScroller;
    /**
 * Record the last touch event
 */
    private MotionEvent mLastMotionEvent;


    public mLayout(Context context) {
        this(context, null);
    }

    public mLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public mLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScroller = new Scroller(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        (event);

        switch (() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                mLastMotionEvent = (event);
                moveType = 1;
                actionX = ();
                actionY = ();
                firstX = actionX;
                firstY = actionY;

                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                moveType = 2;
                spacing = getSpacing(event);
                break;
            case MotionEvent.ACTION_MOVE:
                if (moveType == 1) {
                    int dx = (int) (() - ());
                    int dy = (int) (() - ());

                    //If you don't want to add damping effect to the four directions, just delete it
                    //Play up                    if (((dx) < (dy)) && dy < 0){
                        smoothScrollBy(0, -(int) (dy * mFator));
                    }
                    //Play down                    else if ((dx) < (dy) && dy > 0) {
                        smoothScrollBy(0, -(int) (dy * mFator));
                    }
                    //Play to the left                    else if ((dx) > (dy) && dx < 0){
                        smoothScrollBy(-(int) (dx * mFator), 0);
                    }
                    //Play to the right                    else if ((dx) > (dy) && dx > 0){
                        smoothScrollBy(-(int) (dx * mFator), 0);
                    }
                    mLastMotionEvent = (event);

                }
                else if (moveType == 2) {
                    scale = scale * getSpacing(event) / spacing;

                    if(scale >= 1){
                        setScaleX(scale);
                        setScaleY(scale);
                    }else{
                        scale = 1;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL:
                moveType = 0;
                if(scale == 1)
                    smoothScrollTo(0, 0);
                break;
        }
        return true;
    }

    //Intercepting the event of subbutton    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (() & MotionEvent.ACTION_MASK){
            case MotionEvent.ACTION_DOWN:
                mLastMotionEvent = (event);
                moveType = 1;
                actionX = ();
                actionY = ();
                firstX = actionX;
                firstY = actionY;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                moveType = 2;
                spacing = getSpacing(event);
                break;
            case MotionEvent.ACTION_MOVE:
                if (moveType == 1) {
                    int dx = (int) (() - ());
                    int dy = (int) (() - ());

                    //If you don't want to add damping effect to the four directions, just delete it
                    //Play up                    if (((dx) < (dy)) && dy < 0){
                        smoothScrollBy(0, -(int) (dy * mFator));
                    }
                    //Play down                    else if ((dx) < (dy) && dy > 0) {
                        smoothScrollBy(0, -(int) (dy * mFator));
                    }
                    //Play to the left                    else if ((dx) > (dy) && dx < 0){
                        smoothScrollBy(-(int) (dx * mFator), 0);
                    }
                    //Play to the right                    else if ((dx) > (dy) && dx > 0){
                        smoothScrollBy(-(int) (dx * mFator), 0);
                    }
                    mLastMotionEvent = (event);

                }
                else if (moveType == 2) {
                    scale = scale * getSpacing(event) / spacing;

                    if(scale >= 1){
                        setScaleX(scale);
                        setScaleY(scale);
                    }else{
                        scale = 1;
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                moveType = 0;
                if(scale == 1)
                    smoothScrollTo(0, 0);
                if(firstX != () || firstY != ())
                    return true;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                moveType = 0;
                if(scale == 1)
                    smoothScrollTo(0, 0);
                break;
            case MotionEvent.ACTION_CANCEL:
                moveType = 0;
                if(scale == 1)
                    smoothScrollTo(0, 0);
                break;
        }
        return (event);
    }

    private void smoothScrollBy(int dx, int dy) {

        ((), (), dx, dy);
        invalidate();
    }

    private void smoothScrollTo(int fx, int fy) {

        int dx = fx - ();
        int dy = fx - ();
        smoothScrollBy(dx, dy);
    }


    @Override
    public void computeScroll() {
        if (()) {
            scrollTo((), ());
            postInvalidate();
        }
        ();
    }

    // Touch the distance between two points    private float getSpacing(MotionEvent event) {
        //Get the distance between two points through the trigonometric function        float x = (0) - (1);
        float y = (0) - (1);
        return (float) (x * x + y * y);
    }

}

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.