最近项目需要,需要模仿其他app 的一个动画,这个动画主要是使用VelocityTracker和Scroller类。根据手势的位移不停的计算alpha。这个效果非常适合做文章阅读的开屏页的显示。
要模仿的app的gif
模仿结果
第一步,滑动效果处理
处理的手法也比较简单,首先是对下方的文字的移动做滑动效果,这里使用的适合做弹性滑动的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);
}
}
}