阅读类-开屏动画 位移-渐变-联动

331 阅读2分钟

最近项目需要,需要模仿其他app 的一个动画,这个动画主要是使用VelocityTracker和Scroller类。根据手势的位移不停的计算alpha。这个效果非常适合做文章阅读的开屏页的显示。

要模仿的app的gif

Kjplmd

模仿结果

KjpK6e

第一步,滑动效果处理

处理的手法也比较简单,首先是对下方的文字的移动做滑动效果,这里使用的适合做弹性滑动的scroller,将滑动的位移数据,传递出去,根据该位移数据做渐变过程的处理。 具体代码如下:

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.Scroller;
import androidx.annotation.Nullable;
public class OpenScreenBottomLayout extends LinearLayout {
    private Scroller mScroller;
    public OpenScreenBottomLayout(Context context) {
        this(context, null);
    }
    public OpenScreenBottomLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public OpenScreenBottomLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.open_screen_bottom_layout, this);
        mScroller = new Scroller(context);
    }

    public void startScroll(int startX, int startY, int dx, int dy) {
    // 1,这里设置好要移动的位置,然后调用Invalidate方法进行重新绘制。
        mScroller.startScroll(startX, startY, dx, dy, 1000);
        postInvalidate();
    }
    public void abortScrollAnimation() {
        mScroller.abortAnimation();
    }
    @Override
    public void computeScroll() {
    // 2,复写父类该方法,该方法在父类中是空实现,在父类的注释中有详细的说明,
        if (mScroller.computeScrollOffset()) {
            int currX = mScroller.getCurrX();
            int currY = mScroller.getCurrY();
            if (mRestoreListener != null) {
     // 3 , 传递出去做渐变处理。       
                mRestoreListener.onRestoreCurrY(currY);
            }
            scrollTo(currX, currY);
            postInvalidate();
            if (mScroller.isFinished()) {
                if (mRestoreListener != null) {
                    mRestoreListener.executeFinished(currY);
                }
            }

        }
    }
    private RestoreListener mRestoreListener;
    public interface RestoreListener {
        void onRestoreCurrY(int currY);
        void executeFinished(int currY);
    }
    public void setBottomLayoutRestoreListener(RestoreListener restoreListener) {
        mRestoreListener = restoreListener;
    }
} 

第二步,渐变(透明)效果和手势处理

在手势处理的move方法中,计算透明度,设置给整个layout。同时根绝手指的偏移量,给下方的标题的layout,在up的手势中调用scroller的startScroll的方法开启弹性滑动。同事在up的方法中 调用VelocityTracker.computeCurrentVelocity方法进行速度追踪。

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class OpenScreenFrameLayout extends FrameLayout implements OpenScreenBottomLayout.RestoreListener {
    private FrameLayout mTopLayout;
    private int mTouchSlop = 8;
    private float downY = 0;
    private int halfHeight;
    private OpenScreenBottomLayout bottomTranslationYLayout;
    private int mMaximumVelocity;
    private VelocityTracker mVelocityTracker;
    private int mYLastMove = 0;

    public OpenScreenFrameLayout(@NonNull Context context) {
        this(context, null);
    }

    public OpenScreenFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OpenScreenFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.open_screen_layout, this);
        mTopLayout = findViewById(R.id.topLayout);
        bottomTranslationYLayout = findViewById(R.id.bottomTranslationYLayout);
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        int height = wm.getDefaultDisplay().getHeight();
        halfHeight = height / 4;
        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();

        bottomTranslationYLayout.setBottomLayoutRestoreListener(this);
        mVelocityTracker = VelocityTracker.obtain();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mTopLayout.getVisibility() == GONE) {
            return false;
        }

        mVelocityTracker.addMovement(event);
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            downY = event.getY();
            mYLastMove = (int) downY;
        }

        if (action == MotionEvent.ACTION_MOVE) {
            int moveY = (int) event.getY();
            float diff = moveY - downY;

            // 1,只有 向上滑动有效,根据手指的位置设置背景的透明度
            if (diff < 0) {
                bottomTranslationYLayout.abortScrollAnimation();
                int scrolledY = (int) (mYLastMove - moveY);
                bottomTranslationYLayout.scrollBy(0, scrolledY);
                mYLastMove = moveY;
                int bottomMoveTranslationY = bottomTranslationYLayout.getScrollY();
                float alpha = (float) (1.0 - bottomMoveTranslationY * 1.0f / halfHeight * 1.0f);
                mTopLayout.setAlpha(alpha);
            }
        }

        if (action == MotionEvent.ACTION_UP) {
            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            float yVelocity = mVelocityTracker.getYVelocity();
            //  当手速大于50时执行滑动,小于50则回到原来的位置
            if (Math.abs(yVelocity) > 50) {
                int currScrollY = (int) bottomTranslationYLayout.getScrollY();
                bottomTranslationYLayout.startScroll(0, currScrollY, 0, halfHeight);
            } else {

                int currScrollY = (int) bottomTranslationYLayout.getScrollY();
                bottomTranslationYLayout.startScroll(0, currScrollY, 0, -currScrollY);
            }
            mVelocityTracker.clear();
        }
        return true;
    }
// 如果当执行滑动时,根据scroller的值计算透明度。
    @Override
    public void onRestoreCurrY(int currY) {
        float alpha = (float) (1.0 - currY * 1.0f / halfHeight * 1.0f);
        mTopLayout.setAlpha(alpha);
    }
    @Override
    public void executeFinished(int currY) {
        if (currY > 0) {
            mTopLayout.setVisibility(GONE);
        }
    }
}

源码: github.com/eerry/werwe…