接着上一期 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
轉載請註明出處~謝謝~