经常用饿了么点餐,看到这个下拉刷新,模仿一波。
自定义EleView.这个view就是根据RecyclerView的onTouch移动的距离,显示处理篮子打开和冒出水果的动画。这里有两个偏移,坐标(篮子位移)与角度(篮子盖打开)偏移,所以需要一个onMove方法统一传入偏移的百分百percent,再计算和重绘。
public void onMove(float percent) {
if (percent >= 0.6) {
currentDegree = (float) (150 * (percent - 0.6) * 2.5);
}
if (percent <= 1) {
offsetY = percent * maxOffsetY;
invalidate();
}
}
在onDraw方法里面可以先绘制静态的图,在根据偏移统一调整坐标和角度。这里篮子有个迷の带感的震动效果,实现也是用属性动画:
cycleAnimator = ValueAnimator.ofFloat(-1, 1);
cycleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentOffset = (float) animation.getAnimatedValue();
invalidate();
}
});
......
通过currentOffset变化,影响绘制的canvas.drawBitmap传入的Rect dst的坐标,造成影像的缩放。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mWidth / 2, offsetY);
for (int i = 0; i < 5; i++) {
dstFruit.left = (int) (mPosition0[i][0] - 0.5 * fW);
dstFruit.top = (int) (mPosition0[i][1] - 0.5 * fH);
dstFruit.right = (int) (mPosition0[i][0] + 0.5 * fW);
dstFruit.bottom = (int) (mPosition0[i][1] + 0.5 * fH);
if (foods.get(i).showing) {
canvas.drawBitmap(foods.get(i).bitmap, null, dstFruit, paint);
}
}
canvas.save();
int offsetC = (int) (currentOffset * 5);
canvas.rotate(currentDegree, (float) (dstE.right - iWidth * scale / 4), (float) (dstE.top + scale * iWidth));
canvas.drawBitmap(iR, null, new Rect(dstI2.left, dstI2.top - offsetC, dstI2.right, dstI2.bottom + offsetC), paint);
canvas.restore();
canvas.save();
canvas.rotate(-currentDegree, (float) (dstE.left + iWidth * scale / 4), (float) (dstE.top + scale * iWidth));
canvas.drawBitmap(iL, null, new Rect(dstI.left, dstI.top - offsetC, dstI.right, dstI.bottom + offsetC), paint);
canvas.restore();
canvas.drawBitmap(ele, null, new Rect(dstE.left, dstE.top - offsetC, dstE.right, dstE.bottom + offsetC), paint);
}
水果移动需要得到水果移动的路径,在根据路径算出坐标。路径很好得到,大概估计一下就行。获取到路径在获取每个点的坐标,这里用到了PathMeasure的getPosTan(float distance, float pos[], float tan[]),简单的说就是把这个路径拉直成一条直线,然后根据当前位移获取当前的坐标,封装到数组中去。由于现在有5个水果需要移动,新建二位数组mPosition0 = new float[5][2];
for (int i = 0; i < 5; i++) {
final int finalI = i;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.get(finalI).getLength());
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
// 获取当前点坐标封装到mCurrentPosition
mPathMeasure.get(finalI).getPosTan(value, mPosition0[finalI], null);
postInvalidate();
}
});
}
新建一个MyHeaderView 继承LinearLayout。这里为了方便以后的拓展,抽取一个接口:之后只要实现了这个接口,就可以传入到ERecyclerView充当刷新的HeadView。
public interface Header {
int STATE_NORMAL = 0;
int STATE_READY = 1;
int STATE_REFRESHING = 2;
int STATE_DONE = 3;
void setVisibleHeight(int height);
int getVisibleHeight();
void setState(int state);
void onMove(float delta);
void refreshComplete();
boolean releaseAction();
}
自定义RecyclerView主要是处理当onTouchEvent。其实还有其他的地方但不是本文的重点。
.....
private Header headerView;
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLastY == -1) {
mLastY = e.getRawY();
}
if (isOnTop() && !isloading) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float delta = e.getRawY() - mLastY;
mLastY = e.getRawY();
headerView.onMove(delta / 1.5f);
break;
default:
if (headerView.releaseAction() && freshListener != null && !isloading) {
noMore = false;
isloading = true;
freshListener.onRefresh();
}
mLastY = -1;
break;
}
}
return super.onTouchEvent(e);
}
差不多就这些,没错就是这么短小精悍!还有其他的具体代码细节请看项目源码,如果觉得不错顺手点个star也是极好的O(∩_∩)O哈哈~:
https://github.com/Ulez/ERecyclerView