實現拉繩效果(類似UC瀏覽器首頁效果)

實現拉繩效果(類似UC瀏覽器首頁效果)

前段時間,由於需求需要實現一個拉繩效果的首頁,所以自己是實現了這樣一個功能,需要的同學可以參考一下

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Build;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.CycleInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Scroller;

/**
 * Created by Jesse on 2016/9/19.
 */
public class RopeView extends FrameLayout {
    private static final String DEBUG_TAG = "scrollview";
    private static final int ANIM_DURATION = 250;
    private static final int MAX_LINE_EXTEND = 200;
    private float mLastTouchY;
    private float downY;
    private int lineHeight;
    /**
     * 手指相對於剛按下位置的距離
     */
    private float distanceY = 0;
    private VelocityTracker mVelocityTracker = null;

    /**
     * 用於完成滾動操作的實例
     */
    private Scroller mScroller;
    private boolean mIsScrolling = false;
    private float mVelocityY, mVelocityX;

    private int mTouchSlop;
    private int mMinFlingVelocity;
    private int mMaxFlingVelocity;
    private View headView, contentView, bottomView;
    private ImageView ropeIV;
    private View line, ropeRL;
    private Status status = Status.top;
    private RopeListener mListener;
    private View ropeContentRL;


    public interface RopeListener {
        /**
         * 狀態改變
         *
         * @param status
         */
        public void onStausChange(Status status);
    }

    public enum Status {
        top, bottom
    }


    public RopeView(Context context) {
        super(context);
        init();
    }

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

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


    private void init() {
        prepareView();
        mScroller = new Scroller(getContext(), new AccelerateInterpolator(2));
        ViewConfiguration vc = ViewConfiguration.get(getContext());
        mTouchSlop = vc.getScaledTouchSlop();
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
        initStatus();
    }


    private OnClickListener ropeClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            switchView();
        }
    };


    private void switchView() {
        switch (status) {
            case top:
                mScroller.startScroll(ropeContentRL.getScrollX(), ropeContentRL.getScrollY(), 0, -ropeContentRL.getScrollY(), ANIM_DURATION); //向下滾
                break;

            case bottom:
                mScroller.startScroll(ropeContentRL.getScrollX(), ropeContentRL.getScrollY(), 0, getScrollHeight() - ropeContentRL.getScrollY(), ANIM_DURATION);
                break;
        }
        invalidate();
    }


    private void prepareView() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        inflater.inflate(R.layout.view_scroll, this, true);
        headView = findViewById(R.id.head);
        contentView = findViewById(R.id.content);
        bottomView = findViewById(R.id.bottom);
        ropeIV = (ImageView) findViewById(R.id.iv_rope);
        ropeContentRL = findViewById(R.id.rl_rope_content);
        line = findViewById(R.id.line);
        ropeRL = findViewById(R.id.rl_rope);
        ropeIV.setOnClickListener(ropeClickListener);
    }


    /**
     * 設置背景的Fragment
     * @param manager
     * @param fragment
     */
    public void setBackgroundFragment(FragmentManager manager,Fragment fragment) {
        if(fragment == null) return;
        FragmentTransaction ft = manager.beginTransaction();
        ft.replace(R.id.fl_background_container,fragment);
        ft.commitAllowingStateLoss();
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onTouchEvent will be called and we do the actual
         * scrolling there.
         */
        final int action = event.getActionMasked();
        // Always handle the case of the touch gesture being complete.
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll.
            mIsScrolling = false;
            return false; // Do not intercept touch event, let the child handle it
        }
        switch (action) {

            case MotionEvent.ACTION_DOWN:
                downY = event.getY();
                mScroller.abortAnimation();
                mLastTouchY = event.getY();
                initTracker(event);
                bringToFront();
                break;

            case MotionEvent.ACTION_MOVE: {
                if (mIsScrolling) {
                    // We're currently scrolling, so yes, intercept the
                    // touch event!
                    return true;
                }
                // If the user has dragged her finger horizontally more than
                // the touch slop, start the scroll

                float yDiff = Math.abs(event.getY() - downY);
                // Touch slop should be calculated using ViewConfiguration
                // constants.
                if (yDiff > mTouchSlop) {
                    // Start scrolling!
                    mIsScrolling = true;
                    return true;
                }
                break;
            }
        }
        // In general, we don't want to intercept touch events. They should be
        // handled by the child view.
        return false;
    }



    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handled = false;
        int action = event.getActionMasked();
        switch (action) {
            case (MotionEvent.ACTION_DOWN):
                Log.d(DEBUG_TAG, "onTouchEvent Action was DOWN");
                mScroller.abortAnimation();
                mLastTouchY = event.getY();
                initTracker(event);
                handled = true;
                break;
            case (MotionEvent.ACTION_MOVE):
                Log.d(DEBUG_TAG, "onTouchEvent Action was MOVE");
                calculateVelocity(event);
                float y = event.getY();
                float dy = y - mLastTouchY;
                distanceY = y - downY;
                if (dy < 0 || ropeContentRL.getScrollY() > 0) {
                    ropeContentRL.scrollBy(0, -(int) dy);
                }
                if (ropeContentRL.getScrollY() < 0) {
                    ropeContentRL.scrollTo(ropeContentRL.getScrollX(), 0); //防止滑動到上面
                }
                extendLine();
                invalidate(); //必須刷新
                mLastTouchY = y;
                if (distanceY > mTouchSlop) handled = true;
                break;
            case (MotionEvent.ACTION_UP):
            case (MotionEvent.ACTION_CANCEL):
                Log.d(DEBUG_TAG, "onTouchEvent Action was ACTION_UP or ACTION_CANCEL");
                if (mLastTouchY < downY) { //向上滑
                    if (enableFling() || (Math.abs(ropeContentRL.getScrollY()) > (getScrollHeight() / 3))) {
                        mScroller.startScroll(ropeContentRL.getScrollX(), ropeContentRL.getScrollY(), 0, getScrollHeight() - ropeContentRL.getScrollY(), ANIM_DURATION); //向上滾
                    } else {
                        mScroller.startScroll(ropeContentRL.getScrollX(), ropeContentRL.getScrollY(), 0, -ropeContentRL.getScrollY(), ANIM_DURATION); //向下滾
                    }
                } else { //向下滑
                    if (enableFling() || ((getScrollHeight() - Math.abs(ropeContentRL.getScrollY())) > (getScrollHeight() / 3))) {
                        mScroller.startScroll(ropeContentRL.getScrollX(), ropeContentRL.getScrollY(), 0, -ropeContentRL.getScrollY(), ANIM_DURATION); //向下滾
                    } else {
                        mScroller.startScroll(ropeContentRL.getScrollX(), ropeContentRL.getScrollY(), 0, getScrollHeight() - ropeContentRL.getScrollY(), ANIM_DURATION); //向上滾
                    }
                }

                invalidate(); //必須刷新
                releaseTracker();
                resetLine();
                mVelocityX = 0;
                mVelocityY = 0;
                distanceY = 0;
                break;
            case (MotionEvent.ACTION_OUTSIDE):
                Log.d(DEBUG_TAG, "Movement occurred outside bounds " +
                        "of current screen element");
                break;
        }
        super.onTouchEvent(event);
        return handled;
    }

    /**
     * 伸長下垂的那根線
     */
    private void extendLine() {
        if (distanceY < 0) return;
        double x = distanceY / getScrollHeight(); //範圍是[0-1]
        double factor = -(Math.pow(x, 2)) + 2 * x;
        int extendHeight = (int) (factor * MAX_LINE_EXTEND);
        Log.e(DEBUG_TAG, "extendHeight = " + extendHeight);
        ViewGroup.LayoutParams params = line.getLayoutParams();
        params.height = lineHeight + extendHeight;
        line.setLayoutParams(params);
        line.invalidate();
    }


    private void resetLine() {
        if (distanceY <= 0) return;
        line.setPivotY(0);
        line.setPivotX(line.getWidth() / 2);
        ObjectAnimator animator = ObjectAnimator.ofInt(line, "custom", line.getHeight(), lineHeight);
        animator.setInterpolator(new BounceInterpolator());
        animator.setDuration(ANIM_DURATION * 2);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int val = (int) animation.getAnimatedValue();
                ViewGroup.LayoutParams params = line.getLayoutParams();
                params.height = val;
                line.setLayoutParams(params);
                line.invalidate();
            }
        });
        animator.start();
    }


    private void startRopeAnim() {
        AnimatorSet animatorSet = new AnimatorSet();

        ObjectAnimator animator1 = ObjectAnimator.ofFloat(ropeRL, View.TRANSLATION_Y, -bottomView.getHeight());
        animator1.setInterpolator(new DecelerateInterpolator(3));
        animator1.setDuration(ANIM_DURATION + 100);

        ObjectAnimator animator2 = ObjectAnimator.ofFloat(ropeRL, View.TRANSLATION_Y, 0);
        animator2.setInterpolator(new BounceInterpolator());
        animator2.setDuration(ANIM_DURATION + 100);

        ObjectAnimator animator3 = ObjectAnimator.ofFloat(ropeIV, View.ROTATION_Y, 90);
        animator3.setInterpolator(new CycleInterpolator(1));
        animator3.setDuration(ANIM_DURATION * 2);

        ObjectAnimator animator4 = ObjectAnimator.ofFloat(ropeRL, View.ROTATION, -10);
        animator4.setInterpolator(new CycleInterpolator(2));
        animator4.setDuration(ANIM_DURATION * 4);
        ropeRL.setPivotX(ropeRL.getWidth() / 2);
        ropeRL.setPivotY(0);

        animatorSet.play(animator2).after(animator1);
        animatorSet.play(animator3).after(animator2);
        animatorSet.play(animator3).with(animator4);
        animatorSet.start();
    }


    private void startRopeInitAnim() {
        AnimatorSet animatorSet = new AnimatorSet();

        ObjectAnimator animator1 = ObjectAnimator.ofFloat(ropeRL, View.ALPHA, 0.2f, 1.0f);
        animator1.setInterpolator(new AccelerateInterpolator(2));
        animator1.setDuration(ANIM_DURATION * 2);

        ObjectAnimator animator2 = ObjectAnimator.ofFloat(ropeRL, View.ROTATION, -10);
        animator2.setInterpolator(new CycleInterpolator(2));
        animator2.setDuration(ANIM_DURATION * 4);
        ropeRL.setPivotX(ropeRL.getWidth() / 2);
        ropeRL.setPivotY(0);

        animatorSet.play(animator2).after(animator1);
        animatorSet.start();
    }


    /**
     * 計算手機滑動的速度
     *
     * @param event
     */
    private void calculateVelocity(MotionEvent event) {
        if (mVelocityTracker == null) return;
        mVelocityTracker.addMovement(event);
        // When you want to determine the velocity, call
        // computeCurrentVelocity(). Then call getXVelocity()
        // and getYVelocity() to retrieve the velocity for each pointer ID.
        mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
        mVelocityX = mVelocityTracker.getXVelocity();
        mVelocityY = mVelocityTracker.getYVelocity();
    }

    /**
     * 返回滾動的最大高度
     *
     * @return
     */
    private int getScrollHeight() {
        return headView.getHeight() + contentView.getHeight();
    }

    private boolean enableFling() {
        return Math.abs(mVelocityY) > Math.abs(mVelocityX) && Math.abs(mVelocityY) > mMinFlingVelocity;
    }


    private void initTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
            // Retrieve a new VelocityTracker object to watch the velocity of a motion.
            mVelocityTracker = VelocityTracker.obtain();
        } else {
            // Reset the velocity tracker back to its initial state.
            mVelocityTracker.clear();
        }
        // Add a user's movement to the tracker.
        mVelocityTracker.addMovement(event);
    }


    private void releaseTracker() {
        if (null != mVelocityTracker) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }


        @Override
        public void computeScroll() {
            // 如果返回true,表示動畫還沒有結束
            // 因爲前面startScroll,所以只有在startScroll完成時 纔會爲false
            if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
                Log.e(DEBUG_TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());
                // 產生了動畫效果,根據當前值 每次滾動一點
                ropeContentRL.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                //此時同樣也需要刷新View ,否則效果可能有誤差
                invalidate();
                return;
            }

            completeScroll();
    }


    private void completeScroll() {
        if (Math.abs(ropeContentRL.getScrollY()) == getScrollHeight()) {
            if (status == Status.bottom) { //從底部滾動高頂部播放動畫
                startRopeAnim();
                Log.e(DEBUG_TAG, "滾動到top結束");
                status = Status.top;
                if (mListener != null) {
                    mListener.onStausChange(Status.top);
                }
            }
        }

        if (Math.abs(ropeContentRL.getScrollY()) == 0) {
            if (status == Status.top) {
                Log.e(DEBUG_TAG, "滾動到bottom結束");
                status = Status.bottom;
                if (mListener != null) {
                    mListener.onStausChange(Status.bottom);
                }
            }
        }
        invalidate();
    }

    /**
     * 初始化剛開始頁面狀態
     */
    private void initStatus() {
        this.getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        if (status == Status.top) {
                            mScroller.startScroll(ropeContentRL.getScrollX(), ropeContentRL.getScrollY(), 0, getScrollHeight() - ropeContentRL.getScrollY(), 0); //立刻向上滾
                            invalidate();
                            startRopeInitAnim();
                        }
                        lineHeight = line.getHeight();
                        if (hasJellyBean()) {
                            RopeView.this.getViewTreeObserver()
                                    .removeOnGlobalLayoutListener(this);
                        } else {
                            RopeView.this.getViewTreeObserver()
                                    .removeGlobalOnLayoutListener(this);
                        }
                    }
                });
    }


    private boolean hasJellyBean() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }


    @Override
    protected void onDetachedFromWindow() {
        // To be on the safe side, abort the scroller
        if ((mScroller != null) && !mScroller.isFinished()) {
            mScroller.abortAnimation();
        }
        super.onDetachedFromWindow();
    }


    public void setOnRopeListener(RopeListener mListener) {
        this.mListener = mListener;
    }


    public void changeRopeIcon(final int resid) {
        AnimatorSet animatorSet = new AnimatorSet();

        ObjectAnimator animator1 = ObjectAnimator.ofFloat(ropeIV, View.ROTATION_Y, 180);
        animator1.setInterpolator(new DecelerateInterpolator());
        animator1.setDuration(ANIM_DURATION);

        animator1.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                ropeIV.setImageResource(resid);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });

        ObjectAnimator animator2 = ObjectAnimator.ofFloat(ropeRL, View.ROTATION, -5);
        animator2.setInterpolator(new CycleInterpolator(1));
        animator2.setDuration(ANIM_DURATION * 2);
        ropeRL.setPivotX(ropeRL.getWidth() / 2);
        ropeRL.setPivotY(0);

        animatorSet.play(animator2).with(animator1);
        animatorSet.start();
    }

}

佈局頁面如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.cw80jd2.myapplication.MainActivity">


    <com.example.cw80jd2.myapplication.RopeView
        android:id="@+id/ropeView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

效果如下:
這裏寫圖片描述
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章