接着上一期 Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件 的博客开始,一样,在开始前咱们先来看一下目标效果:java
下面上一下本章须要实现的效果图:算法
你们看到这个效果确定不会以为陌生,QQ已经把粘滞效果作的满大街都是,相信很多读者或多或少对于贝塞尔曲线有所了解,不了解的朋友们也没有关系,在这里我会带领读者领略一下贝塞尔的魅力!canvas
咱们知道,任何一条线段是由起始点和终止点的连线组成,两点组成一条直线,这就是最简单的一阶公式(就是线段):网络
一阶贝塞尔曲线表达公式(图略):ide
B(t) = P0 + ( P1 - P0 ) t = ( 1 - t ) P0 + t P1 , t∈[0,1]函数
很显然,一阶的贝塞尔只是用于一条线段,其中t的变化率表明着线性插值大小.因此咱们的效果用于一阶贝塞尔曲线公式确定不行,下面咱们来着重介绍一下二阶(次)贝塞尔曲线变化率和公式:布局
(图片来自于网络)post
公式:性能
B(t) = ( 1 - t )² P0 + 2 t ( 1 - t ) P1 + t² P2 , t∈[0,1]优化
其实公式对于咱们的开发者来讲并无太大的意义,由于主要的算法咱们的API都已经包含,不过咱们须要了解的是,咱们的辅助点的查找.首先,咱们须要了解曲线是如何画出来的?从图中咱们能够看出咱们的辅助点是p1点,由p0和p1组成的线段加上p1和p2组成的线段一共是有两条线段,咱们须要一个变化率t,t从p0走到p1和从p1走到p2的时间是同样的,这样咱们链接两点,就产生了第三条直线(图中绿色的线),这条直线其实就是咱们的贝塞尔曲线的切线,只要有了这条直线,咱们就能够肯定咱们的贝塞尔曲线轨迹(这一点相当重要).
固然,有一阶二阶,确定也会有三阶、四阶等等.由于辅助点的增长,曲线也会发生各类变化,在这里,博主就不介绍了,想了解更深刻的读者,能够在不少关于贝塞尔的博客中去了解.
介绍完了贝塞尔曲线,接下来咱们就要开始着手打造QQ的粘滞效果了.在开始编写代码前咱们先分析一下,咱们要实现这个效果所须要的准备工做:
/** * 圆的画笔 */ private Paint circlePaint; /** * 画笔的路径 */ private Path circlePath; /** * 可拖动的最远距离 */ private int maxHeight; /** * 刷新图标 */ private Bitmap bt; private float topCircleRadius;//默认上面圆形半径 private float topCircleX;//默认上面圆形x private float topCircleY;//默认上面圆形y private float bottomCircleRadius;//默认上面圆形半径 private float bottomCircleX;//默认下面圆形x private float bottomCircleY;//默认下面圆形y private float defaultRadius;//默认上面圆形半径 float offset=1.0f; float lastY; OnAnimResetListener listener; ObjectAnimator anim;
public YPXBezierView(Context context) { this(context, null); } public YPXBezierView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public YPXBezierView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } protected void init() { maxHeight=dp(60); topCircleX=ScreenUtils.getScreenWidth(getContext())/2; topCircleY=dp(100); topCircleRadius=dp(15); bottomCircleX=topCircleX; bottomCircleY=topCircleY; bottomCircleRadius=topCircleRadius; defaultRadius=topCircleRadius; circlePath = new Path(); circlePaint = new Paint(); circlePaint.setAntiAlias(true); circlePaint.setStyle(Paint.Style.FILL_AND_STROKE); circlePaint.setStrokeWidth(1); circlePaint.setColor(Color.parseColor("#999999")); }
@Override protected void onDraw(Canvas canvas) { drawPath(); float left=topCircleX-topCircleRadius; float top=topCircleY-topCircleRadius; canvas.drawPath(circlePath, circlePaint); canvas.drawCircle(bottomCircleX, bottomCircleY, bottomCircleRadius, circlePaint); canvas.drawCircle(topCircleX, topCircleY, topCircleRadius, circlePaint); int btWidth=(int) topCircleRadius* 2-dp(6); if ((btWidth) > 0) { bt = BitmapFactory.decodeResource(getResources(), R.mipmap.refresh); bt = Bitmap.createScaledBitmap(bt,btWidth, btWidth, true); canvas.drawBitmap(bt, left+dp(3), top+dp(2) , null); bt.recycle(); } super.onDraw(canvas); }
private void drawPath() { float p1X = topCircleX - topCircleRadius ; float p1Y = topCircleY ; float p2X = topCircleX + topCircleRadius; float p2Y = topCircleY ; float p3X = bottomCircleX - bottomCircleRadius ; float p3Y = bottomCircleY ; float p4X = bottomCircleX + bottomCircleRadius ; float p4Y = bottomCircleY ; float anchorX = (p1X+ p4X) / 2-topCircleRadius*offset; float anchorY = (p1Y + p4Y) / 2; float anchorX2 = (p2X +p3X) / 2+topCircleRadius*offset; float anchorY2 = (p2Y + p3Y) / 2; /* 画粘连体 */ circlePath.reset(); circlePath.moveTo(p1X, p1Y); circlePath.quadTo(anchorX, anchorY, p3X, p3Y); circlePath.lineTo(p4X, p4Y); circlePath.quadTo(anchorX2, anchorY2, p2X, p2Y); circlePath.lineTo(p1X, p1Y); }
private void drawPath() { float p1X = topCircleX - topCircleRadius ; float p1Y = topCircleY ; float p2X = topCircleX + topCircleRadius; float p2Y = topCircleY ; float p3X = bottomCircleX - bottomCircleRadius ; float p3Y = bottomCircleY ; float p4X = bottomCircleX + bottomCircleRadius ; float p4Y = bottomCircleY ; float anchorX = (p1X+ p4X) / 2-topCircleRadius*offset; float anchorY = (p1Y + p4Y) / 2; float anchorX2 = (p2X +p3X) / 2+topCircleRadius*offset; float anchorY2 = (p2Y + p3Y) / 2; /* 画粘连体 */ circlePath.reset(); circlePath.moveTo(p1X, p1Y); circlePath.quadTo(anchorX, anchorY, p3X, p3Y); circlePath.lineTo(p4X, p4Y); circlePath.quadTo(anchorX2, anchorY2, p2X, p2Y); circlePath.lineTo(p1X, p1Y); }
public void animToReset(boolean lock){ if(!lock) { Log.e("onAnimationEnd", "动画开始"); anim= ObjectAnimator.ofFloat(offset, "ypx", 0.0F, 1.0F).setDuration(200); //使用反弹算法插值器,貌似没有什么太大的效果 - -! anim.setInterpolator(new BounceInterpolator()); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float cVal = (Float) animation.getAnimatedValue(); offset = cVal; bottomCircleX=bottomCircleX+(topCircleX-bottomCircleX)*offset; bottomCircleY=bottomCircleY+(topCircleY-bottomCircleY)*offset; bottomCircleRadius=bottomCircleRadius+(topCircleRadius-bottomCircleRadius)*offset; topCircleRadius=topCircleRadius+(defaultRadius-topCircleRadius)*offset; postInvalidate(); } }); anim.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { Log.e("onAnimationEnd", "动画结束"); if (listener != null) { listener.onReset(); } } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); anim.start(); } }
感谢你们的支持,谢谢!
做者:yangpeixing
QQ:313930500
下载地址:http://download.csdn.net/detail/qq_16674697/9741375
转载请注明出处~谢谢~