Android Open Source - GestureViews State Controller






From Project

Back to project page GestureViews.

License

The source code is released under:

Apache License

If you think the Android project GestureViews listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.alexvasilkov.gestures;
/*from   ww w  . j  a v a  2  s .  c  om*/
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import com.alexvasilkov.gestures.utils.MovementBounds;

public class StateController {

    private final Settings mSettings;

    // Temporary objects
    private final State mTmpState = new State();
    private final Matrix mMatrix = new Matrix();
    private final RectF mRectF = new RectF();
    private final MovementBounds mMovementBounds = new MovementBounds();

    private boolean mIsResetRequired = true;

    /**
     * Values to store calculated values for min / max zoom levels
     */
    private float mMinZoom, mMaxZoom;

    public StateController(Settings settings) {
        mSettings = settings;
    }

    /**
     * Resets to initial state (min zoom, position according to gravity)
     */
    public void resetState(State state) {
        mIsResetRequired = true;
        updateState(state);
    }

    public void updateState(State state) {
        if (mIsResetRequired) {
            // We can correctly reset state only when we have both view size and viewport size
            // but there can be a delay before we have all values properly set
            // (waiting for layout or waiting for image to be loaded)
            boolean updated = adjustZoomLevels(state);

            state.set(0f, 0f, mMinZoom, 0f);
            MovementBounds.setupInitialMovement(state, mSettings);

            mIsResetRequired = !updated;
        } else {
            restrictStateBounds(state);
        }
    }

    public float getMinZoom() {
        return mMinZoom;
    }

    public float getMaxZoom() {
        return mMaxZoom;
    }

    /**
     * Maximizes zoom if it closer to min zoom or minimizes it if it closer to max zoom
     *
     * @return End state for toggle animation
     */
    public State toggleMinMaxZoom(State state, float pivotX, float pivotY) {
        final float middleZoom = (mMinZoom + mMaxZoom) / 2f;
        final float targetZoom = state.getZoom() < middleZoom ? mMaxZoom : mMinZoom;

        State end = state.copy();
        end.zoomTo(targetZoom, pivotX, pivotY);
        return end;
    }

    /**
     * Restricts state's translation and zoom bounds, disallowing overscroll / overzoom.
     */
    public boolean restrictStateBounds(State state) {
        return restrictStateBounds(state, null, 0f, 0f, false, false);
    }

    /**
     * Restricts state's translation and zoom bounds.
     *
     * @return End state to animate changes or null if no changes are required
     */
    public State restrictStateBoundsCopy(State state, float pivotX, float pivotY,
                                         boolean allowOverscroll, boolean allowOverzoom) {
        mTmpState.set(state);
        boolean changed = restrictStateBounds(mTmpState, null, pivotX, pivotY, allowOverscroll, allowOverzoom);
        return changed ? mTmpState.copy() : null;
    }

    /**
     * Restricts state's translation and zoom bounds. If {@code prevState} is not null and
     * {@code allowOverscroll (allowOverzoom)} parameter is true than resilience will be applied to translation (zoom)
     * changes if they are out of bounds.
     *
     * @return true if state was changed, false otherwise
     */
    public boolean restrictStateBounds(State state, State prevState, float pivotX, float pivotY,
                                       boolean allowOverscroll, boolean allowOverzoom) {

        if (!mSettings.isRestrictBounds()) return false;

        if (prevState != null && !State.equals(state.getRotation(), prevState.getRotation())) {
            // Rotation will change view bounds, so we should adjust zoom levels
            adjustZoomLevels(state);
        }

        boolean isStateChanged = false;

        float overzoom = allowOverzoom ? mSettings.getOverzoomFactor() : 1f;

        float zoom = restrict(state.getZoom(), mMinZoom / overzoom, mMaxZoom * overzoom);

        // Applying elastic overzoom
        if (prevState != null) {
            zoom = applyZoomResilience(zoom, prevState.getZoom(), overzoom);
        }

        if (!State.equals(zoom, state.getZoom())) {
            state.zoomTo(zoom, pivotX, pivotY);
            isStateChanged = true;
        }

        MovementBounds bounds = getMovementBounds(state);
        float overscrollX = allowOverscroll ? mSettings.getOverscrollDistanceX() : 0f;
        float overscrollY = allowOverscroll ? mSettings.getOverscrollDistanceY() : 0f;

        PointF tmpPos = bounds.restrict(state.getX(), state.getY(), overscrollX, overscrollY);
        float x = tmpPos.x;
        float y = tmpPos.y;

        if (zoom < mMinZoom) {
            // Decreasing overscroll if zooming less than minimum zoom
            float minZoom = mMinZoom / overzoom;
            float factor = (zoom - minZoom) / (mMinZoom - minZoom);
            factor = (float) Math.sqrt(factor);

            tmpPos = bounds.restrict(x, y);
            float strictX = tmpPos.x;
            float strictY = tmpPos.y;

            x = strictX + factor * (x - strictX);
            y = strictY + factor * (y - strictY);
        }

        if (prevState != null) {
            RectF extBounds = bounds.getExternalBounds();
            x = applyTranslationResilience(x, prevState.getX(), extBounds.left, extBounds.right, overscrollX);
            y = applyTranslationResilience(y, prevState.getY(), extBounds.top, extBounds.bottom, overscrollY);
        }

        if (!State.equals(x, state.getX()) || !State.equals(y, state.getY())) {
            state.translateTo(x, y);
            isStateChanged = true;
        }

        return isStateChanged;
    }

    private float applyZoomResilience(float zoom, float prevZoom, float overzoom) {
        if (overzoom == 1f) return zoom;

        float minZoom = mMinZoom / overzoom;
        float maxZoom = mMaxZoom * overzoom;

        float resilience = 0f;

        if (zoom < mMinZoom && zoom < prevZoom) {
            resilience = (mMinZoom - zoom) / (mMinZoom - minZoom);
        } else if (zoom > mMaxZoom && zoom > prevZoom) {
            resilience = (zoom - mMaxZoom) / (maxZoom - mMaxZoom);
        }

        if (resilience == 0f) {
            return zoom;
        } else {
            float factor = zoom / prevZoom;
            factor += (float) Math.sqrt(resilience) * (1f - factor);
            return prevZoom * factor;
        }
    }

    private float applyTranslationResilience(float value, float prevValue,
                                             float boundsMin, float boundsMax, float overscroll) {
        if (overscroll == 0) return value;

        float resilience = 0f;

        float avg = (value + prevValue) * 0.5f;

        if (avg < boundsMin && value < prevValue) {
            resilience = (boundsMin - avg) / overscroll;
        } else if (avg > boundsMax && value > prevValue) {
            resilience = (avg - boundsMax) / overscroll;
        }

        if (resilience == 0f) {
            return value;
        } else {
            if (resilience > 1f) resilience = 1f;
            float delta = value - prevValue;
            delta *= (1f - (float) Math.sqrt(resilience));
            return prevValue + delta;
        }
    }


    /**
     * Do note store returned object, since it will be reused next time this method is called.
     */
    public MovementBounds getMovementBounds(State state) {
        mMovementBounds.setup(state, mSettings);
        return mMovementBounds;
    }

    /**
     * Adjusting min and max zoom levels.
     *
     * @return true if zoom levels was correctly updated (viewport and view size are known), false otherwise
     */
    private boolean adjustZoomLevels(State state) {
        mMaxZoom = mSettings.getMaxZoom();

        float fittingZoom = 1f;

        boolean isCorrectSize = mSettings.hasViewSize() && mSettings.hasViewportSize();

        if (isCorrectSize) {
            float w = mSettings.getViewW(), h = mSettings.getViewH();
            float areaW = mSettings.getMovementAreaW(), areaH = mSettings.getMovementAreaH();

            if (mSettings.getFitMethod() == Settings.Fit.OUTSIDE) {
                // Computing movement area size taking rotation into account.
                // We will inverse rotation, since it will be applied to area, not to view itself.
                mMatrix.setRotate(-state.getRotation());
                mRectF.set(0, 0, areaW, areaH);
                mMatrix.mapRect(mRectF);
                areaW = mRectF.width();
                areaH = mRectF.height();
            } else {
                // Computing view size taking rotation into account.
                mMatrix.setRotate(state.getRotation());
                mRectF.set(0, 0, w, h);
                mMatrix.mapRect(mRectF);
                w = mRectF.width();
                h = mRectF.height();
            }

            switch (mSettings.getFitMethod()) {
                case HORIZONTAL:
                    fittingZoom = areaW / w;
                    break;
                case VERTICAL:
                    fittingZoom = areaH / h;
                    break;
                case OUTSIDE:
                    fittingZoom = Math.max(areaW / w, areaH / h);
                    break;
                case INSIDE:
                default:
                    fittingZoom = Math.min(areaW / w, areaH / h);
                    break;
            }
        }

        if (fittingZoom > mMaxZoom) {
            if (mSettings.isFillViewport()) {
                // zooming to fill entire viewport
                mMinZoom = mMaxZoom = fittingZoom;
            } else {
                // restricting min zoom
                mMinZoom = mMaxZoom;
            }
        } else {
            mMinZoom = fittingZoom;
        }

        return isCorrectSize;
    }

    public static float restrict(float value, float minValue, float maxValue) {
        return Math.max(minValue, Math.min(value, maxValue));
    }

    /**
     * Interpolates from start state to end state by given factor (from 0 to 1), storing result into out state.
     */
    public static void interpolate(State out, State start, State end, float factor) {
        float x = interpolate(start.getX(), end.getX(), factor);
        float y = interpolate(start.getY(), end.getY(), factor);
        float zoom = interpolate(start.getZoom(), end.getZoom(), factor);
        float rotation = interpolate(start.getRotation(), end.getRotation(), factor);
        out.set(x, y, zoom, rotation);
    }

    private static float interpolate(float start, float end, float factor) {
        return start + (end - start) * factor;
    }

}




Java Source Code List

com.alexvasilkov.gestures.GesturesAdapter.java
com.alexvasilkov.gestures.GesturesControllerPagerFix.java
com.alexvasilkov.gestures.GesturesController.java
com.alexvasilkov.gestures.Settings.java
com.alexvasilkov.gestures.StateController.java
com.alexvasilkov.gestures.State.java
com.alexvasilkov.gestures.detectors.RotationGestureDetector.java
com.alexvasilkov.gestures.detectors.ScaleGestureDetectorFixed.java
com.alexvasilkov.gestures.sample.activities.BaseActivity.java
com.alexvasilkov.gestures.sample.activities.ImageCroppingActivity.java
com.alexvasilkov.gestures.sample.activities.ImageSnapshotActivity.java
com.alexvasilkov.gestures.sample.activities.ImagesPagerActivity.java
com.alexvasilkov.gestures.sample.activities.LayoutPagerActivity.java
com.alexvasilkov.gestures.sample.activities.MainActivity.java
com.alexvasilkov.gestures.sample.activities.TextViewActivity.java
com.alexvasilkov.gestures.sample.items.Painting.java
com.alexvasilkov.gestures.sample.items.PaintingsImagesAdapter.java
com.alexvasilkov.gestures.sample.items.PaintingsLayoutsAdapter.java
com.alexvasilkov.gestures.sample.utils.PicassoHelper.java
com.alexvasilkov.gestures.utils.FloatScroller.java
com.alexvasilkov.gestures.utils.MovementBounds.java
com.alexvasilkov.gestures.utils.SmoothViewPagerScroller.java
com.alexvasilkov.gestures.utils.Snapshot.java
com.alexvasilkov.gestures.widgets.GestureImageView.java
com.alexvasilkov.gestures.widgets.GestureLayout.java
com.alexvasilkov.gestures.widgets.GestureTextView.java