转载请注意:http://blog.youkuaiyun.com/wjzj000/article/details/73441173
我和一帮应届生同学维护了一个公众号:IT面试填坑小分队。旨在帮助应届生从学生过度到开发者,并且每周树立学习目标,一同进步!
写在前面
本来想昨天就把这个效果分析完毕。然后事与愿违,随便玩了玩时间就没了。没办法只有今天补上….最难逃儿女情长书接上回楚霸王,让我们续上仿天天美剧拖动卡片的效果(上):
http://blog.youkuaiyun.com/wjzj000/article/details/73432852
开始
我们上部分分析完了setAdapter的一系列的过程,这部分我们要通过
FlingCardListener
来近距离的看一看我们拖动Card的实现效果。
在开始FlingCardListener
之前,让我们回过来上看一个类LinearRegression
。可能各位看官已经把这个类给忘了,所以我们在分析的一开始就看一看它。
LinearRegression
public LinearRegression(float[] x, float[] y) {
if (x.length != y.length) {
throw new IllegalArgumentException("array lengths are not equal");
}
N = x.length;
//求平均值
double sumx = 0.0, sumy = 0.0, sumx2 = 0.0;
for (int i = 0; i < N; i++) sumx += x[i];
for (int i = 0; i < N; i++) sumx2 += x[i]*x[i];
for (int i = 0; i < N; i++) sumy += y[i];
double xbar = sumx / N;
double ybar = sumy / N;
//求方差
double xxbar = 0.0, yybar = 0.0, xybar = 0.0;
for (int i = 0; i < N; i++) {
xxbar += (x[i] - xbar) * (x[i] - xbar);
yybar += (y[i] - ybar) * (y[i] - ybar);
xybar += (x[i] - xbar) * (y[i] - ybar);
}
beta = xybar / xxbar;
alpha = ybar - beta * xbar;
//一些计算公式
double rss = 0.0;
double ssr = 0.0;
for (int i = 0; i < N; i++) {
double fit = beta*x[i] + alpha;
rss += (fit - y[i]) * (fit - y[i]);
ssr += (fit - ybar) * (fit - ybar);
}
int degreesOfFreedom = N-2;
R2 = ssr / yybar;
svar = rss / degreesOfFreedom;
svar1 = svar / xxbar;
svar0 = svar/N + xbar*xbar*svar1;
}
FlingCardListener
这里比较重要的方法是
onTouch()
方法,构造方法传参非常多,各个变量的使用将逐一在onTouch()
方法这种展开。因此接下来让我们看一看onTouch()
方法。
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//确定唯一的坐标点,避免多指操作带来的问题
mActivePointerId = event.getPointerId(0);
float x = 0;
float y = 0;
boolean success = false;
try {
x = event.getX(mActivePointerId);
y = event.getY(mActivePointerId);
success = true;
} catch (IllegalArgumentException e) {
Log.w(TAG, "Exception in onTouch(view, event) : " + mActivePointerId, e);
}
if (success) {
//记住我们按下时的坐标
aDownTouchX = x;
aDownTouchY = y;
//设置View的初始坐标是传入frame的X、Y值
if (aPosX == 0) {
aPosX = frame.getX();
}
if (aPosY == 0) {
aPosY = frame.getY();
}
//记录按压位置状态
if (y < objectH / 2) {
touchPosition = TOUCH_ABOVE;
} else {
touchPosition = TOUCH_BELOW;
}
}
view.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER_ID;
//将在下文展开
resetCardViewOnStack();
view.getParent().requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_MOVE:
final int pointerIndexMove = event.findPointerIndex(mActivePointerId);
final float xMove = event.getX(pointerIndexMove);
final float yMove = event.getY(pointerIndexMove);
final float dx = xMove - aDownTouchX;
final float dy = yMove - aDownTouchY;
aPosX += dx;
aPosY += dy;
/**
* objectX= frame.getX()
* 此处为计算移动距离
*/
float distobjectX = aPosX - objectX;
/**
* 如果我们仔细看Card的拖动效果可以发现,
* 有一个小小的旋转效果如果我们仔细看Card的拖动效果可以发现,有一个小小的旋转效果
*
* 此处是计算旋转角度自定义的公式,当拖动X轴距离和parentWidth相等时,
* 那么最大旋转的效果就是:BASE_ROTATION_DEGREES * 2.f
*/
float rotation = BASE_ROTATION_DEGREES * 2.f * distobjectX / parentWidth;
if (touchPosition == TOUCH_BELOW) {
rotation = -rotation;
}
//设置Card的位置,旋转等
frame.setX(aPosX);
frame.setY(aPosY);
frame.setRotation(rotation);
//onScroll方法调用
mFlingListener.onScroll(getScrollProgressPercent());
break;
}
return true;
}
这里的思路是通过setListener的时候,
new FlingCardListener
把每一个Card的View对象和Adapter
中对应的数据,FlingCardListener
将通过onTouch()
方法中的坐标变换进行对Card
位置进行设置,并且完成回调。
resetCardViewOnStack()
private boolean resetCardViewOnStack() {
//如果超出左边界限
if (movedBeyondLeftBorder()) {
// 执行左划出效果,相关执行代码在这个方法中( onSelected()在下文展开 )
// getExitPoint()得到离开时的坐标Y的方法
onSelected(true, getExitPoint(-objectW), 100);
mFlingListener.onScroll(-1.0f);
} else if (movedBeyondRightBorder()) {
// 执行右划出效果
onSelected(false, getExitPoint(parentWidth), 100);
mFlingListener.onScroll(1.0f);
} else {
//如果不从左或右划出,那么便是Card回弹
float abslMoveDistance = Math.abs(aPosX - objectX);
aPosX = 0;
aPosY = 0;
aDownTouchX = 0;
aDownTouchY = 0;
frame.animate()
.setDuration(200)
.setInterpolator(new OvershootInterpolator(1.5f))
.x(objectX)
.y(objectY)
.rotation(0);
mFlingListener.onScroll(0.0f);
if (abslMoveDistance < 4.0) {
mFlingListener.onClick(dataObject);
}
}
return false;
}
onSelected()
Card划出的代码实现,需要接受exitY,离开的坐标Y
public void onSelected(final boolean isLeft,float exitY, long duration) {
isAnimationRunning = true;
float exitX;
if (isLeft) {
/**
* 计算离开点的X坐标(左移动为减,右移动为加)
* getRotationWidthOffset():
* objectW / MAX_COS - objectW(计算旋转的宽度偏移量)
* MAX_COS=cos45
*/
exitX = -objectW - getRotationWidthOffset();
} else {
exitX = parentWidth + getRotationWidthOffset();
}
//弹出Card的代码实现
this.frame.animate()
.setDuration(duration)
.setInterpolator(new AccelerateInterpolator())
.x(exitX)
.y(exitY)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (isLeft) {
//左边移出回调
mFlingListener.onCardExited();
mFlingListener.leftExit(dataObject);
} else {
//右边移出回调
mFlingListener.onCardExited();
mFlingListener.rightExit(dataObject);
}
isAnimationRunning = false;
}
})
.rotation(getExitRotation(isLeft));
}
getExitPoint()
得到离开时的坐标Y的方法
private float getExitPoint(int exitXPoint) {
float[] x = new float[2];
x[0] = objectX;
x[1] = aPosX;
float[] y = new float[2];
y[0] = objectY;
y[1] = aPosY;
LinearRegression regression = new LinearRegression(x, y);
//这个返回值是一个线性方程: y = ax+b
return (float) regression.slope() * exitXPoint + (float) regression.intercept();
}
梳理
走到这里,整体的流程其实还是比较明确的:我们折叠的多个Card的效果是通过类似ListView的实现就行完成的。每一个Card的View在通过设置Listener进行完成Card的左右拖动的效果。而实现弹动的效果是用属性动画完成的。
尾声
希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp