編輯:關於Android編程
最近公司項目有一個錄音的錄制和播放動畫需求,然後時間是那麼緊,那麼趕緊開撸。先看效果圖
嗯,然後大致就是這樣,按住錄音,然後有一個倒計時,最外層一個進度條,還有一個類似模擬聲波的動畫效果(其實中間的波浪會根據聲音的大小浪起來的~)
然後,我們適當的來分析一下這個錄音動畫的實現方式。這個肯定是通過自定義控件,咱們來把這個效果完完全全畫出來。
大致包括以下幾個點:
1. 最外層的進度條,最坑的就是一開始的一個漸變的效果
2. 然後進度條最前方是有一個點的(我肯定選擇用圖片來實現)
3. 中間的波浪(關鍵是要隨著聲音的大小浪起來)
4. 中間的倒計時
/** * 畫一個大圓(純色) */ mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(dip2px(mContext, widthing)); mPaint.setColor(mContext.getResources().getColor(R.color.RoundColor)); RectF oval1 = new RectF( dip2px(mContext, pandding) , dip2px(mContext, pandding) , getWidth()-dip2px(mContext, pandding) , getHeight()-dip2px(mContext, pandding)); canvas.drawArc(oval1, progress, 360, false, mPaint); //繪制圓弧
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setTextSize(dip2px(mContext,textHintSize)); paint.setColor(mContext.getResources().getColor(R.color.RoundHintTextColor)); // 下面這行是實現水平居中,drawText對應改為傳入targetRect.centerX() paint.setTextAlign(Paint.Align.CENTER); canvas.drawText("剩余錄制時間", getWidth()/2, getHeight()/2+50, paint);
/** * 畫時間 * */ Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG); paint2.setTextSize(dip2px(mContext,60)); paint2.setColor(mContext.getResources().getColor(R.color.TimeTextColor)); paint2.setTextAlign(Paint.Align.CENTER); canvas.drawText(countdownTime2+"", getWidth()/2, getHeight()/2-20, paint2);
if (lastTime == 0) { lastTime = System.currentTimeMillis(); translateX += 5; } else { if (System.currentTimeMillis() - lastTime > lineSpeed) { lastTime = System.currentTimeMillis(); translateX += 5; } else { return; } } if (volume < targetVolume && isSet) { volume += getHeight() / 30; } else { isSet = false; if (volume <= 10) { volume = 10; } else { if (volume < getHeight() / 30) { volume -= getHeight() / 60; } else { volume -= getHeight() / 30; } } } //我是分隔線------------------------------- mPaint.setColor(voiceLineColor); mPaint.setAntiAlias(true); mPaint.setStrokeWidth(2); canvas.save(); int moveY = getHeight()*3/4; for (int i = 0; i < paths.size(); i++) { paths.get(i).reset(); paths.get(i).moveTo(getWidth()*5/6, getHeight() *3/4); } for (float j = getWidth()*5/6 - 1; j >= getWidth()/6; j -= fineness) { float i = j-getWidth()/6; //這邊必須保證起始點和終點的時候amplitude = 0; amplitude = 5 * volume *i / getWidth() - 5 * volume * i / getWidth() * i/getWidth()*6/4; for (int n = 1; n <= paths.size(); n++) { float sin = amplitude * (float) Math.sin((i - Math.pow(1.5, n)) * Math.PI / 180 - translateX+n*amplitude/(Math.PI*6)); paths.get(n - 1).lineTo(j, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY)); } } for (int n = 0; n < paths.size(); n++) { if (n == paths.size() - 1) { mPaint.setAlpha(255); } else { mPaint.setAlpha(n * 130 / paths.size()); } if (mPaint.getAlpha() > 0) { canvas.drawPath(paths.get(n), mPaint); } } canvas.restore();
這邊代碼就不展開了,畫的有點煩,簡單說下,還需要自己體會哈。上面分隔線之前的說白了就是讓聲波動起來,也就是改變volume的值,然後後面有3個for循環。第一個for循環是為了確定聲波水平線的位置,第二個是畫聲波,第三個是顏色的漸變。
我們先會個圖分析一下,如下圖。A點就是起始坐標,一開始我們的小圓點是隱藏的,如果不算padding的話,x=witdh/2,y=0;
嗯,然後呢畫圖片我們用的是
canvas.drawBitmap(......);
那麼要知道,drawBitmap()這個方法畫的時候是我們圖片的左上角去畫到A點的,其實我們應該往左上角挪一點,才能讓圖片的中心真正意義上的和A點重合,對吧對吧,嗯,仔細思考一下。
然後繼續看上面那個圖,當我們A點隨著時間運動到B點之後,我們要算出B點的坐標。這邊用一下三角函數啦,我們設A到B,轉過的角度為α,設圓的半徑為r,那麼A到B其實橫向增加的距離應該就是
m = x+r*sin(α); n = y+r*cos(α);
那麼我們該圖片的所有代碼就是:
/** * 畫一個點(圖片) * */ if(r>0){ if(progress >360) return; double hu = Math.PI*Double.parseDouble(String.valueOf(progress))/180.0; Log.d(TAG,"hu: "+hu); double p = Math.sin(hu)*r; Log.d(TAG,"p: "+p); double q = Math.cos(hu)*r; Log.d(TAG,"q: "+q); float x = (float) ((getWidth()-progressDrawable.getIntrinsicWidth())/2f+p); Log.d(TAG,"x: "+x); float y = (float) ((dip2px(mContext,pandding)-progressDrawable.getIntrinsicHeight()/2f)+r-q); Log.d(TAG,"y: "+y); canvas.drawBitmap(((BitmapDrawable)progressDrawable).getBitmap(),x,y,mPaint); }
我的實現方式很簡單,從我們的UI圖看出,外面的大圓在1/4進度的時候是漸變的,然後剩下的3/4圓其實都是一種顏色,對吧,那我就畫2個圓來實現這個效果呗。當progress<90的時候,我們畫那個漸變的圓環,當>90的時候,我們同時畫出那個漸變的和純色的圓環(當progress的時候,這個時候其實那個漸變的圓環沒變化,只是純色的圓環一直在變)。如圖:A是那個漸變的圓環,B是純色不變的圓環
/** * 這邊畫進度 */ if(progress > 90){ mPaint.setColor(getResources().getColor(R.color.RoundFillColor)); mPaint.setStrokeWidth(dip2px(mContext, widthing)); RectF oval = new RectF( dip2px(mContext, pandding) , dip2px(mContext, pandding) , getWidth()-dip2px(mContext, pandding) , getHeight()-dip2px(mContext, pandding)); canvas.drawArc(oval, 0, progress-90, false, mPaint); //繪制圓弧 r = getHeight()/2f-dip2px(mContext,pandding); } /** * 畫一個大圓(漸變) */ mPaint.setStyle(Paint.Style.STROKE); canvas.rotate(-90, getWidth() / 2, getHeight() / 2); mPaint.setStrokeWidth(dip2px(mContext, widthing)); mPaint.setShader(new SweepGradient(getWidth() / 2, getHeight() / 2, doughnutColors, null)); RectF oval = new RectF( dip2px(mContext, pandding) , dip2px(mContext, pandding) , getWidth()-dip2px(mContext, pandding) , getHeight()-dip2px(mContext, pandding)); //看這裡,其實這裡當progress大於90以後就一直只畫0-90度的圓環 canvas.drawArc(oval, 0, progress<90?progress:90, false, mPaint); //繪制圓弧 canvas.rotate(90, getWidth() / 2, getHeight() / 2); mPaint.reset();
private int countdownTime2 = 9;//倒計時時間,默認時間10秒.這是會變的 ... progress += 360.00/(countdownTime*950.00/5.00);
Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { if(msg.what == 1){ countdownTime2--; if(countdownTime2 == 0){ listener.onCountDown(); canSetVolume = false; timeTask.cancel(); postInvalidate(); } }else if(msg.what == 2){ progress += 360.00/(countdownTime*950.00/5.00); // Log.d(TAG,"progress:"+progress); if(progress >360){ targetVolume = 1; postInvalidate(); progressTask.cancel(); }else postInvalidate(); } } };
比如你的自定義屬性
以及一切其余自定義View的東西,對自定義View不熟的同學可以先去學習下怎麼自定義View(其實很簡單,新手不要怕),然後再去實現一些看上去挺棒的效果。
嗯,大致就是這樣,對於公司這些動畫的需求我只想說其實你想要咋弄,都是沒問題的,最重要的就是時間!本身其實最後留給開發人員的時間就不多,然後如果還要加各種動畫,那不是天天加班的節奏麼~
本節引言: 好的,終於學習完Adapter類相關的一些控件,當然除了講解的那幾個,還有其他很多的 相關的控件,就不慢慢講解了~有需要的自行查閱文檔,查看相關的
【Android 2D繪圖解析】系列文章將全面介紹Android繪圖相關,這篇簡單介紹下如何利用Android API進行一些簡單圖形的繪制,繪圖的前提是需要繼承自Vie
前面幾篇博文介紹了Android如何自定義控件,其實就是講一下如何“從無到有”的自定義一個全新的控件,繼承View或者繼承ViewG
我們建好一個android 的項目後,默認的res下面 有layout、values、drawable等目錄 這些都是程序默認的資源文件目錄,如果要實現多語言版本的話