好久没有写点小东西了,昨天去逛了一些dribbble,看了一个下拉效果貌似还不错
这让我联想到之前很久就出来一个类似的效果,github上面已经有人写出来了https://github.com/tuesda/CircleRefreshLayout.
言归正传,标题尽然是quadTo,此时你一定会吐槽不就是贝塞尔的二阶函数么,下拉的时候的有点像椭圆的形状不就是被他搞出来的么。没错其实原理还是很简单,但是如果数学不好的人去写的话,还是很吃力的譬如本菜鸟,那我们今天主要实现的是下拉的时候出现的类似椭圆的形状。
一眼看懂原理的人请无视下面一段话。说下菜鸟的思路:考虑用一个FrameLayout
去包裹一个RecylerView
(为什么不用线性的呢,万一人家要用覆盖式的,请自行脑补),在onInterceptTouchEvent
里面去判断是否是下拉并且RecylerView
是否能够被下拉(描述不太准确),在onTouchEvent
里面去根据下拉距离不断改变HeaderView
的高度以及mChildView
的translationY
,释放的时候用一个ValueAnimator
同理去操作,罗里吧嗦把原理说完了,下面具体我只想静静滴上代码。
先看下FrameLayout
的代码:
/** * Created by Tangxb on 2016/4/27. */
public class ComplexLayout extends FrameLayout {
private View mChildView;
private float mPullHeight;
private float mHeaderHeight;
private float mTouchStartY;
private float mTouchCurY;
private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(10);
private AnimationView mHeader;
public ComplexLayout(Context context) {
this(context, null);
}
public ComplexLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ComplexLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPullHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 600, context.getResources().getDisplayMetrics());
mHeaderHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mChildView = getChildAt(0);
addHeaderView();
}
private void addHeaderView() {
mHeader = new AnimationView(getContext());
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
params.gravity = Gravity.TOP;
mHeader.setLayoutParams(params);
addView(mHeader);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mTouchStartY = ev.getY();
mTouchCurY = mTouchStartY;
break;
case MotionEvent.ACTION_MOVE:
float curY = ev.getY();
float dy = curY - mTouchStartY;
if (dy > 0 && !canChildScrollUp()) {
return true;
}
}
return super.onInterceptTouchEvent(ev);
}
private boolean canChildScrollUp() {
if (mChildView == null) {
return false;
}
return ViewCompat.canScrollVertically(mChildView, -1);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mTouchCurY = event.getY();
float dy = mTouchCurY - mTouchStartY;
dy = Math.min(mPullHeight * 2, dy);
dy = Math.max(0, dy);
if (mChildView != null) {
float offsetY = decelerateInterpolator.getInterpolation(dy / 2 / mPullHeight) * dy / 2;
mChildView.setTranslationY(offsetY);
mHeader.getLayoutParams().height = (int) offsetY;
mHeader.requestLayout();
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mHeaderHeight = mHeader.getHeight();
ValueAnimator va = ValueAnimator.ofFloat(mHeaderHeight, 0);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float val = (float) animation.getAnimatedValue();
val = decelerateInterpolator.getInterpolation(val / mHeaderHeight) * val;
if (mChildView != null) {
mChildView.setTranslationY(val);
}
mHeader.getLayoutParams().height = (int) val;
mHeader.requestLayout();
}
});
va.setDuration(500);
va.start();
return true;
default:
return super.onTouchEvent(event);
}
}
}
在看下里面的AnimationView
的代码:
public class AnimationView extends View {
private int PULL_HEIGHT;
private int PULL_DELTA;
private float mWidthOffset;
private Path mPath;
private Paint mBackPaint;
private int mWidth;
private int mHeight;
public AnimationView(Context context) {
this(context, null);
}
public AnimationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
PULL_HEIGHT = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics());
PULL_DELTA = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 500, context.getResources().getDisplayMetrics());
mWidthOffset = 0.5f;
mPath = new Path();
mBackPaint = new Paint();
mBackPaint.setAntiAlias(true);
mBackPaint.setStyle(Paint.Style.FILL);
mBackPaint.setColor(0xff8b90af);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
if (height > PULL_DELTA + PULL_HEIGHT) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(PULL_DELTA + PULL_HEIGHT, MeasureSpec.getMode(heightMeasureSpec));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mWidth = getWidth();
mHeight = getHeight();
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, mWidth, PULL_HEIGHT, mBackPaint);
mPath.reset();
mPath.moveTo(0, PULL_HEIGHT);
mPath.quadTo(mWidthOffset * mWidth, PULL_HEIGHT + (mHeight - PULL_HEIGHT) * 2, mWidth, PULL_HEIGHT);
canvas.drawPath(mPath, mBackPaint);
}
}
其实难点的计算部分,里面用了DecelerateInterpolator
https://github.com/tuesda/CircleRefreshLayout
https://dribbble.com/shots/1209105-first-shot
https://dribbble.com/shots/1797373-Pull-Down-To-Refresh