Android控件實(shí)現(xiàn)水滴效果
看到ios版上QQ刷新效果像水滴,然后自己也想著去實(shí)現(xiàn)這樣的效果,這篇文章暫時(shí)沒(méi)有介紹下拉刷新的效果,只是單獨(dú)用一個(gè)控件來(lái)實(shí)現(xiàn)這樣的水滴效果。
效果圖如下:
一、總體思路
1、畫兩個(gè)圓形,其中一個(gè)就是上面的大圓,還有一個(gè)是下面的小圓,大圓和小圓不斷變小,大圓的位置保持不變,小圓的位置不斷向下移動(dòng),即圓心不斷下移。
2、畫兩邊的曲線,這時(shí)候用到貝塞爾曲線去畫。
3、用屬性動(dòng)畫實(shí)現(xiàn)動(dòng)態(tài)的效果。
二、代碼實(shí)現(xiàn)
1、找出畫曲線的幾個(gè)關(guān)鍵點(diǎn)。
其實(shí)我是在第一張圖的基礎(chǔ)上,再在上面分別畫兩個(gè)圓,就可以得到第二張圖了。關(guān)鍵是畫出第一張圖。
(1)在這里,p1,p2,p3,p4,這4個(gè)點(diǎn)分別對(duì)應(yīng)兩個(gè)圓的兩邊的點(diǎn),即p1到p2就是圓的直徑。p3和p4同理,那么就很容易確定這四個(gè)點(diǎn)的坐標(biāo)了。
(2)然后c1和c2是分別控制p1到p3、p2到p4的曲線,是貝塞爾曲線的控制點(diǎn)。它們的橫坐標(biāo)對(duì)應(yīng)的是p3,p4的橫坐標(biāo)(相等),縱坐標(biāo)取兩個(gè)圓心距離的一半。這樣畫出這個(gè)靜態(tài)的圖片就不難了。
(3)畫上下兩個(gè)圓進(jìn)去,就會(huì)變成第二張圖的效果。
2、在構(gòu)造方法中調(diào)用init()初始化一些基本的變量
private void init(Context context, AttributeSet attrs) { drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); paint = new Paint(); paint.setColor(fillColor); // 轉(zhuǎn)換為像素單位 bigRadius = dip2px(context, bigRadius); smallRadius = dip2px(context, smallRadius); distance = dip2px(context, distance); }
3、在onDraw()方法中畫水滴效果
要注意的是path需要重新new, 貝塞爾曲線的繪制,用到是path.quadTo這方法。具體可以看代碼。
@Override protected void onDraw(Canvas canvas) { // 必須重新new,不然路徑會(huì)重復(fù),我之前就是這樣 path = new Path(); // 把畫布移到中心 canvas.translate(width / 2, height / 2); // 從canvas層面去除繪制時(shí)鋸齒 canvas.setDrawFilter(drawFilter); // 當(dāng)前的兩個(gè)圓心的距離 currentDis = distance * fraction; // 計(jì)算當(dāng)前大圓的半徑 float bigRadius = this.bigRadius - currentDis / bigPercent; float smallRadius = 0; if (currentDis > 5) {// 距離大于5才改變小圓的半徑 smallRadius = this.smallRadius - currentDis / smallPercent; } // 大圓兩邊的兩個(gè)點(diǎn)坐標(biāo) leftX = -bigRadius;// 大圓當(dāng)前的半徑 leftY = rightY = 0; rightX = bigRadius;// 大圓當(dāng)前的半徑 // 小圓兩邊的兩個(gè)點(diǎn)坐標(biāo) leftX2 = -smallRadius;// 小圓當(dāng)前的半徑 leftY2 = rightY2 = currentDis; rightX2 = -leftX2; // 小圓當(dāng)前的半徑 // 兩個(gè)控制點(diǎn) controlX1 = -smallRadius;// x坐標(biāo)取小圓當(dāng)前的半徑大小 controlY1 = currentDis / 2;// y坐標(biāo)取兩個(gè)圓距離的一半 controlX2 = smallRadius;// x坐標(biāo)取小圓當(dāng)前的半徑大小 controlY2 = currentDis / 2;// y坐標(biāo)取兩個(gè)圓距離的一半 path.moveTo(leftX, leftY); path.lineTo(rightX, rightY); // 用二階貝塞爾曲線畫右邊的曲線,參數(shù)的第一個(gè)點(diǎn)是右邊的一個(gè)控制點(diǎn) path.quadTo(controlX2, controlY2, rightX2, rightY2); path.lineTo(leftX2, leftY2); // 用二階貝塞爾曲線畫左邊邊的曲線,參數(shù)的第一個(gè)點(diǎn)是左邊的一個(gè)控制點(diǎn) path.quadTo(controlX1, controlY1, leftX, leftY); // 畫大圓 canvas.drawCircle(0, 0, bigRadius, paint); // 畫小圓 canvas.drawCircle(0, currentDis, smallRadius, paint); // 畫path canvas.drawPath(path, paint); }
4、用屬性動(dòng)畫,實(shí)現(xiàn)動(dòng)態(tài)的效果。
/*** 執(zhí)行屬性動(dòng)畫,實(shí)現(xiàn)水滴的效果 */ public void perforAnim() { ValueAnimator valAnimator = ObjectAnimator.ofFloat(0, 1); valAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { fraction = (float) animation.getAnimatedValue(); postInvalidate(); } }); valAnimator.setDuration(duration); valAnimator.start(); }
5、重寫onMeasure()方法,處理wrap_content情況。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /* * 處理為wrap_content情況,那么它的specMode是AT_MOST模式,在這種模式下它的寬/高 * 等于spectSize,這種情況下view的spectSize是parentSize,而parentSize是 * 父容器目前可以使用大小,就是父容器當(dāng)前剩余的空間大小, 就相當(dāng)于使用match_parent一樣 的效果,因此我們可以設(shè)置一個(gè)默認(rèn)的值 */ int widthSpectMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpectSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpectMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpectSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpectMode == MeasureSpec.AT_MOST && heightSpectMode == MeasureSpec.AT_MOST) { setMeasuredDimension(width, height); } else if (widthSpectMode == MeasureSpec.AT_MOST) { setMeasuredDimension(width, heightSpectSize); } else if (heightSpectMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpectSize, height); } }
6、其它的一些方法實(shí)現(xiàn)。
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { width = right - left; height = bottom - top; } } /** * 根據(jù)手機(jī)的分辨率從 dp 的單位 轉(zhuǎn)成為 px(像素) */ public int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); }
7、字段的定義
private final int fillColor = 0xff999999;// 填充顏色 private Paint paint; private int width = 100, height = 300;// 默認(rèn)寬高 /* 兩個(gè)圓心的最大距離 / private int distance = 60; /* 當(dāng)前兩個(gè)圓心的距離 / private float currentDis = 0; private float bigRadius = 20;// 大圓半徑 private float smallRadius = 10;// 小圓半徑 private float controlX1, controlX2, controlY1, controlY2;// 兩個(gè)控制點(diǎn)的坐標(biāo) private float leftX, leftY, rightX, rightY;// 大圓兩邊的兩個(gè)點(diǎn)的坐標(biāo) private float leftX2, leftY2, rightX2, rightY2; // 小圓兩邊的兩個(gè)坐標(biāo) DrawFilter drawFilter; Path path; /* 由屬性動(dòng)畫控制,范圍為0-1 */ float fraction = 0;// 比例值 /* 大圓半徑變化的比例 / private final int bigPercent = 8; /* 小圓半徑變化的比例 / private final int smallPercent = 20; // 動(dòng)畫的執(zhí)行時(shí)間 private long duration = 3000;
三、總結(jié)
一種動(dòng)畫效果,應(yīng)該先分析它的靜態(tài)的實(shí)現(xiàn),然后添加動(dòng)態(tài)的效果,這樣就比較容易實(shí)現(xiàn)它的動(dòng)畫效果了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ViewPager頂部導(dǎo)航欄聯(lián)動(dòng)效果(標(biāo)題欄條目多)
這篇文章主要介紹了ViewPager頂部導(dǎo)航欄聯(lián)動(dòng)效果,代碼簡(jiǎn)單易懂,感興趣的朋友參考下吧2016-08-08史上最全Android build.gradle配置詳解(小結(jié))
這篇文章主要介紹了史上最全Android build.gradle配置詳解(小結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04Android開(kāi)發(fā)之Fragment懶加載的幾種方式及性能對(duì)比
這篇文章主要介紹了Android開(kāi)發(fā)之Fragment懶加載的幾種方式及性能對(duì)比的相關(guān)資料,具體詳細(xì)介紹需要的小伙伴可以參考下面文章內(nèi)容2022-05-05Android 封裝Okhttp+Retrofit+RxJava,外加攔截器實(shí)例
下面小編就為大家分享一篇Android封裝Okhttp+Retrofit+RxJava,外加攔截器實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01使用android studio開(kāi)發(fā)工具編譯GBK轉(zhuǎn)換三方庫(kù)iconv的方法
這篇文章主要介紹了使用android studio開(kāi)發(fā)工具編譯GBK轉(zhuǎn)換三方庫(kù)iconv的教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Eclipse開(kāi)發(fā)環(huán)境導(dǎo)入android sdk的sample中的源碼
初學(xué)Android編程,Android SDK中提供的Sample代碼自然是最好的學(xué)習(xí)材料,需要的朋友可以參考下2012-12-12解決Error:All flavors must now belong to a named flavor dimens
這篇文章主要介紹了解決Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com,需要的朋友可以參考下2017-11-11Android編程自定義菜單實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Android編程自定義菜單實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Android自定義菜單的布局、動(dòng)畫及功能相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2017-02-02