com.evilduck.animtest.DraggedPanelLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.evilduck.animtest.DraggedPanelLayout.java

Source

/*
 * Copyright 2013 Alexander Osmanov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package com.evilduck.animtest;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;

public class DraggedPanelLayout extends FrameLayout {

    private static final float PARALLAX_FACTOR = 0.2f;

    private static DecelerateInterpolator sDecelerator = new DecelerateInterpolator();

    private float parallaxFactor;

    private int bottomPanelPeekHeight;

    private float touchY;

    private boolean touching;

    private boolean opened = false;

    private VelocityTracker velocityTracker = null;

    private View bottomPanel;

    private View slidingPanel;

    private Drawable shadowDrawable;

    private boolean animating = false;

    private boolean willDrawShadow = false;

    private int touchSlop;

    private boolean isBeingDragged = false;

    public DraggedPanelLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        initAttrs(context, attrs);
    }

    public DraggedPanelLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        initAttrs(context, attrs);
    }

    public DraggedPanelLayout(Context context) {
        super(context);

        bottomPanelPeekHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100,
                getResources().getDisplayMetrics());
        parallaxFactor = PARALLAX_FACTOR;

        if (!isInEditMode()) {
            shadowDrawable = getResources().getDrawable(R.drawable.shadow_np);
            willDrawShadow = true;
        }

        touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    public void initAttrs(Context context, AttributeSet attrs) {
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DraggedPanelLayout, 0, 0);

        try {
            parallaxFactor = a.getFloat(R.styleable.DraggedPanelLayout_parallax_factor, PARALLAX_FACTOR);
            if (parallaxFactor < 0.1 || parallaxFactor > 0.9) {
                parallaxFactor = PARALLAX_FACTOR;
            }

            int defaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100,
                    getResources().getDisplayMetrics());
            bottomPanelPeekHeight = a.getDimensionPixelSize(R.styleable.DraggedPanelLayout_bottom_panel_height,
                    defaultHeight);
            int shadowDrawableId = a.getResourceId(R.styleable.DraggedPanelLayout_shadow_drawable, -1);
            if (shadowDrawableId != -1) {
                shadowDrawable = getResources().getDrawable(shadowDrawableId);
                willDrawShadow = true;
                setWillNotDraw(!willDrawShadow);
            }
        } finally {
            a.recycle();
        }

        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        touchSlop = configuration.getScaledTouchSlop();
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        if (!isInEditMode() && willDrawShadow) {
            int top = (int) (slidingPanel.getTop() + slidingPanel.getTranslationY());
            shadowDrawable.setBounds(0, top - shadowDrawable.getIntrinsicHeight(), getMeasuredWidth(), top);
            shadowDrawable.draw(canvas);

        }
        if (animating) {
            ViewCompat.postInvalidateOnAnimation(DraggedPanelLayout.this);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (getChildCount() != 2) {
            throw new IllegalStateException("DraggedPanelLayout must have 2 children!");
        }

        bottomPanel = getChildAt(0);
        bottomPanel.layout(left, top, right, bottom - bottomPanelPeekHeight);

        slidingPanel = getChildAt(1);
        if (!opened) {
            int panelMeasuredHeight = slidingPanel.getMeasuredHeight();
            slidingPanel.layout(left, bottom - bottomPanelPeekHeight, right,
                    bottom - bottomPanelPeekHeight + panelMeasuredHeight);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            touchY = event.getY();
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (Math.abs(touchY - event.getY()) > touchSlop) {
                isBeingDragged = true;
                startDragging(event);
            }
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            isBeingDragged = false;
        }

        return isBeingDragged;
    }

    public void startDragging(MotionEvent event) {
        touchY = event.getY();
        touching = true;

        bottomPanel.setVisibility(View.VISIBLE);

        obtainVelocityTracker();
        velocityTracker.addMovement(event);
        allowShadow();
    }

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

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            startDragging(event);
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (touching) {
                velocityTracker.addMovement(event);

                float translation = event.getY() - touchY;
                translation = boundTranslation(translation);

                slidingPanel.setTranslationY(translation);
                bottomPanel.setTranslationY((float) (opened
                        ? -(getMeasuredHeight() - bottomPanelPeekHeight - translation) * parallaxFactor
                        : translation * parallaxFactor));

                if (willDrawShadow) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
            }
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            isBeingDragged = false;
            touching = false;

            velocityTracker.addMovement(event);
            velocityTracker.computeCurrentVelocity(1);
            float velocityY = velocityTracker.getYVelocity();
            velocityTracker.recycle();
            velocityTracker = null;

            finishAnimateToFinalPosition(velocityY);
        }

        return true;
    }

    public float boundTranslation(float translation) {
        if (!opened) {
            if (translation > 0) {
                translation = 0;
            }
            if (Math.abs(translation) >= slidingPanel.getMeasuredHeight() - bottomPanelPeekHeight) {
                translation = -slidingPanel.getMeasuredHeight() + bottomPanelPeekHeight;
            }
        } else {
            if (translation < 0) {
                translation = 0;
            }
            if (translation >= slidingPanel.getMeasuredHeight() - bottomPanelPeekHeight) {
                translation = slidingPanel.getMeasuredHeight() - bottomPanelPeekHeight;
            }
        }
        return translation;
    }

    public void obtainVelocityTracker() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        }
    }

    public void finishAnimateToFinalPosition(float velocityY) {
        final boolean flinging = Math.abs(velocityY) > 0.5;

        boolean opening;
        float distY;
        long duration;

        if (flinging) {
            // If fling velocity is fast enough we continue the motion starting
            // with the current speed

            opening = velocityY < 0;

            distY = calculateDistance(opening);
            duration = Math.abs(Math.round(distY / velocityY));

            animatePanel(opening, distY, duration);
        } else {
            // If user motion is slow or stopped we check if half distance is
            // traveled and based on that complete the motion

            boolean halfway = Math
                    .abs(slidingPanel.getTranslationY()) >= (getMeasuredHeight() - bottomPanelPeekHeight) / 2;
            opening = opened ? !halfway : halfway;

            distY = calculateDistance(opening);
            duration = Math.round(300 * (double) Math.abs((double) slidingPanel.getTranslationY())
                    / (double) (getMeasuredHeight() - bottomPanelPeekHeight));

        }

        animatePanel(opening, distY, duration);
    }

    public float calculateDistance(boolean opening) {
        float distY;
        if (opened) {
            distY = opening ? -slidingPanel.getTranslationY()
                    : getMeasuredHeight() - bottomPanelPeekHeight - slidingPanel.getTranslationY();
        } else {
            distY = opening ? -(getMeasuredHeight() - bottomPanelPeekHeight + slidingPanel.getTranslationY())
                    : -slidingPanel.getTranslationY();
        }

        return distY;
    }

    public void animatePanel(final boolean opening, float distY, long duration) {
        ObjectAnimator slidingPanelAnimator = ObjectAnimator.ofFloat(slidingPanel, View.TRANSLATION_Y,
                slidingPanel.getTranslationY(), slidingPanel.getTranslationY() + distY);
        ObjectAnimator bottomPanelAnimator = ObjectAnimator.ofFloat(bottomPanel, View.TRANSLATION_Y,
                bottomPanel.getTranslationY(), bottomPanel.getTranslationY() + (float) (distY * parallaxFactor));

        AnimatorSet set = new AnimatorSet();
        set.playTogether(slidingPanelAnimator, bottomPanelAnimator);
        set.setDuration(duration);
        set.setInterpolator(sDecelerator);
        set.addListener(new MyAnimListener(opening));
        set.start();
    }

    class MyAnimListener implements AnimatorListener {

        int oldLayerTypeOne;

        int oldLayerTypeTwo;

        boolean opening;

        public MyAnimListener(boolean opening) {
            super();
            this.opening = opening;
        }

        @Override
        public void onAnimationStart(Animator animation) {
            oldLayerTypeOne = slidingPanel.getLayerType();
            oldLayerTypeOne = bottomPanel.getLayerType();

            slidingPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
            bottomPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);

            bottomPanel.setVisibility(View.VISIBLE);

            if (willDrawShadow) {
                animating = true;
                ViewCompat.postInvalidateOnAnimation(DraggedPanelLayout.this);
            }
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            setOpenedState(opening);

            bottomPanel.setTranslationY(0);
            slidingPanel.setTranslationY(0);

            slidingPanel.setLayerType(oldLayerTypeOne, null);
            bottomPanel.setLayerType(oldLayerTypeTwo, null);

            requestLayout();

            if (willDrawShadow) {
                animating = false;
                ViewCompat.postInvalidateOnAnimation(DraggedPanelLayout.this);
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            if (willDrawShadow) {
                animating = false;
                ViewCompat.postInvalidateOnAnimation(DraggedPanelLayout.this);
            }
        }

    };

    private void setOpenedState(boolean opened) {
        this.opened = opened;
        bottomPanel.setVisibility(opened ? View.GONE : View.VISIBLE);
        hideShadowIfNotNeeded();
    }

    private void allowShadow() {
        willDrawShadow = shadowDrawable != null;
        setWillNotDraw(!willDrawShadow);
    }

    private void hideShadowIfNotNeeded() {
        willDrawShadow = shadowDrawable != null && !opened;
        setWillNotDraw(!willDrawShadow);
    }

}