自定義動畫效果——音頻抖動效果
繪制一個矩形:
想要繪制一個矩形,繼承View,并重寫onDraw方法即可。複雜一點還可以重寫onMeasure方法和onLayout方法進行大小測量和位置測量。但本文不打算寫那麼複雜的view,故隻需要重寫一個onDraw方法即可:
private RectF rectF = new RectF();//繪制矩形 private float lineWidth = 50; private Paint paint = new Paint(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //設定顔色 paint.setColor(context.getColor(R.color.colorPrimary)); //填充内部 paint.setStyle(Paint.Style.FILL); //設定抗鋸齒 paint.setAntiAlias(true); //繪制矩形的四邊 int widthCentre = getWidth() / 2; int heightCentre = getHeight() / 2; rectF.left = widthCentre - lineWidth / 2; rectF.right = widthCentre + lineWidth / 2; rectF.top = heightCentre - lineWidth * 2; rectF.bottom = heightCentre + lineWidth * 2; //繪制圓角矩形,rx:x方向上的圓角半徑。ry:y方向上的圓角半徑。 canvas.drawRoundRect(rectF, 6, 6, paint); }
1.我們需要初始化一個RectF來繪制矩形,這個類通過一個邊的來繪制矩形。并初始化一個畫筆,和矩形的寬度。
2.在onDraw方法中,設定畫筆paint,包括顔色,填充方式,是否抗拒性。還有更多的設定,讀者可以自行查閱API
3.擷取該View的實際寬高的一半,然後設定矩形的四邊,熟悉Android的view的繪制都知道,view的寬為right - left,高度為bottom - top。是以讓right比left多一個lineWidth即可讓矩形的寬為lineWidth,bottom比top多4lineWidth即可讓高讀為4lineWidth,并利用實際寬高的一半,把矩形繪制在view的中央。
4.在畫布上使用drawRoundRect方法繪制一個圓角的矩形
5.然後在xml檔案中引用這個view就可以使用了:
這樣就可以在view中繪制一個矩形,如圖所示:
繪制多個矩形
現在我們可以繪制多個矩形在畫布上。直接采用for循環是不行的,這樣會讓矩形重疊在一起,導緻隻顯示一個矩形,是以應該控制讓矩形錯開顯示,我們可以讓矩形之間間隔一個lineWidth。如圖所示:
我們以第一個矩形的左邊為參照,标為0,則其右邊為1,第二個矩形的左邊為2,右邊為3,以此類推,它們的距離都是lineWidth。是以我們可以得出:
- 第一個矩形的左邊是0,第二個矩形的左邊是2,第三個左邊是4,以此類推是一個2(n-1)的等差數列
- 第一個矩形的右邊是1,第二個矩形的右邊是3,第三個右邊是5,以此類推是一個2n-1的等差數列
是以我們可以這樣寫:
for (int i = 1; i <= 4; i++) { rectF.left = widthCentre - lineWidth / 2 + 2 * (i - 1) * lineWidth; rectF.right = widthCentre + lineWidth / 2 + (2 * i - 1) * lineWidth; rectF.top = heightCentre - lineWidth * 2; rectF.bottom = heightCentre + lineWidth * 2; //繪制圓角矩形,rx:x方向上的圓角半徑。ry:y方向上的圓角半徑。 canvas.drawRoundRect(rectF, 6, 6, paint); }
當然注意要在循環裡繪制圓角矩形,因為繪制多個矩形,當然要有一個繪制一個,不然放到循壞外隻能繪制最後一個。效果如圖:
繪制矩形抖動
我們要繪制音頻抖動的效果,矩形的高度肯定不能一樣,而是要根據聲音的大小來顯示,這裡我們沒有聲音,簡單模拟一下給高度乘上for循環裡的i效果如圖:
至此我們已經知道了如何繪制多個矩形,并控制不同的高度,那我們要如何動态的控制高度呢?比如我們點選開始錄音的時候,就會動态的傳入聲音的大小,這個分貝值控制着矩形的抖動。要實作這個動态的效果,我們需要不斷的設定分貝,并不斷的重新整理。是以我們可以開啟一個線程,不斷設定音量的分貝,并不斷的重新整理。為了讓矩形抖動有錯落感,就需要讓每個矩形抖動的值不一樣,是以我們設定一個list存儲音量值,并依次改變裡面的值即可。
private static final int MIN_WAVE_HEIGHT = 2;//矩形線最小高 private static final int MAX_WAVE_HEIGHT = 12;//矩形線最大高 private static final int[] DEFAULT_WAVE_HEIGHT = {2, 2, 2, 2}; private static final int UPDATE_INTERVAL_TIME = 100;//100ms更新一次 private LinkedList mWaveList = new LinkedList<>(); private float maxDb; private void resetView(List list, int[] array) { list.clear(); for (int anArray : array) { list.add(anArray); } } private synchronized void refreshElement() { Random random = new Random(); maxDb = random.nextInt(5) + 2; int waveH = MIN_WAVE_HEIGHT + Math.round(maxDb * (MAX_WAVE_HEIGHT - MIN_WAVE_HEIGHT)); mWaveList.add(0, waveH); mWaveList.removeLast(); } public boolean isStart = false; private class LineJitterTask implements Runnable { @Override public void run() { while (isStart) { refreshElement(); try { Thread.sleep(updateSpeed); } catch (Exception e) { e.printStackTrace(); } postInvalidate(); } } } public synchronized void startRecord() { isStart = true; executorService.execute(task); } public synchronized void stopRecord() { isStart = false; mWaveList.clear(); resetView(mWaveList, DEFAULT_WAVE_HEIGHT); postInvalidate(); }
1.為了控制矩形抖動的範圍,我們需要設定一個最大值和最小值。
2.并利用數組設定矩形的預設值,因為有四個矩形,是以數組大小為4
3.定義一個分貝值,控制矩形的高度
4.重置View的時候把預設的數組傳進去,就可以達到View的重置,比如View的初始化,和停止錄音的時候
5.重新整理元素方法,用于不停的重新整理矩陣的高度,讓矩陣抖起來。這裡用随機數模拟聲音大小,傳給數組,每次都添加到第一個,然後每次都移除最後一個,這樣能讓矩陣按順序抖動。
6.線上程中調用這個重新整理矩陣的方法,當開始錄音的時候,在while中重新整理矩陣,并睡眠100ms,這樣就實作了沒100ms重新整理一次view,開始錄音的時候設定isStart為true。
7.在停止錄音的時候設定isStart為false,并初始化矩形為原始高度。由于線上程中重新整理View,應該使用postInvalidate()方法。
至此這個邏輯已經實作了,稍微潤色一下即可實作錄音時的音頻抖動
效果如圖:
完整代碼:
/** * 語音錄制的動畫效果 */public class LineWaveVoiceView extends View { private static final String DEFAULT_TEXT = " 請錄音 "; private static final int LINE_WIDTH = 9;//預設矩形波紋的寬度,9像素, 原則上從layout的attr獲得 private Paint paint = new Paint(); private Runnable task; private ExecutorService executorService = Executors.newCachedThreadPool(); private RectF rectRight = new RectF();//右邊波紋矩形的資料,10個矩形複用一個rectF private RectF rectLeft = new RectF();//左邊波紋矩形的資料 private String text = DEFAULT_TEXT; private int updateSpeed; private int lineColor; private int textColor; private float lineWidth; private float textSize; public LineWaveVoiceView(Context context) { super(context); } public LineWaveVoiceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LineWaveVoiceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(attrs, context); resetView(mWaveList, DEFAULT_WAVE_HEIGHT); task = new LineJitterTask(); } private void initView(AttributeSet attrs, Context context) { //擷取布局屬性裡的值 TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.LineWaveVoiceView); lineColor = mTypedArray.getColor(R.styleable.LineWaveVoiceView_voiceLineColor, context.getColor(R.color.defaultLineColor)); lineWidth = mTypedArray.getDimension(R.styleable.LineWaveVoiceView_voiceLineWidth, LINE_WIDTH); textSize = mTypedArray.getDimension(R.styleable.LineWaveVoiceView_voiceTextSize, 42); textColor = mTypedArray.getColor(R.styleable.LineWaveVoiceView_voiceTextColor, context.getColor(R.color.defaultTextColor)); updateSpeed = mTypedArray.getColor(R.styleable.LineWaveVoiceView_updateSpeed, UPDATE_INTERVAL_TIME); mTypedArray.recycle(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //擷取實際寬高的一半 int widthCentre = getWidth() / 2; int heightCentre = getHeight() / 2; paint.setStrokeWidth(0); paint.setColor(textColor); paint.setTextSize(textSize); float textWidth = paint.measureText(text); canvas.drawText(text, widthCentre - textWidth / 2, heightCentre - (paint.ascent() + paint.descent()) / 2, paint); //設定顔色 paint.setColor(lineColor); //填充内部 paint.setStyle(Paint.Style.FILL); //設定抗鋸齒 paint.setAntiAlias(true); for (int i = 0; i < 10; i++) { rectRight.left = widthCentre + textWidth / 2 + (1 + 2 * i) * lineWidth; rectRight.top = heightCentre - lineWidth * mWaveList.get(i) / 2; rectRight.right = widthCentre + textWidth / 2 + (2 + 2 * i) * lineWidth; rectRight.bottom = heightCentre + lineWidth * mWaveList.get(i) / 2; //左邊矩形 rectLeft.left = widthCentre - textWidth / 2 - (2 + 2 * i) * lineWidth; rectLeft.top = heightCentre - mWaveList.get(i) * lineWidth / 2; rectLeft.right = widthCentre - textWidth / 2 - (1 + 2 * i) * lineWidth; rectLeft.bottom = heightCentre + mWaveList.get(i) * lineWidth / 2; canvas.drawRoundRect(rectRight, 6, 6, paint); canvas.drawRoundRect(rectLeft, 6, 6, paint); } } private static final int MIN_WAVE_HEIGHT = 2;//矩形線最小高 private static final int MAX_WAVE_HEIGHT = 12;//矩形線最大高 private static final int[] DEFAULT_WAVE_HEIGHT = {2,2, 2, 2,2, 2, 2, 2,2,2}; private static final int UPDATE_INTERVAL_TIME = 100;//100ms更新一次 private LinkedList mWaveList = new LinkedList<>(); private float maxDb; private void resetView(List list, int[] array) { list.clear(); for (int anArray : array) { list.add(anArray); } } private synchronized void refreshElement() { Random random = new Random(); maxDb = random.nextInt(5) + 2; int waveH = MIN_WAVE_HEIGHT + Math.round(maxDb * (MAX_WAVE_HEIGHT - MIN_WAVE_HEIGHT)); mWaveList.add(0, waveH); mWaveList.removeLast(); } public boolean isStart = false; private class LineJitterTask implements Runnable { @Override public void run() { while (isStart) { refreshElement(); try { Thread.sleep(updateSpeed); } catch (Exception e) { e.printStackTrace(); } postInvalidate(); } } } public synchronized void startRecord() { isStart = true; executorService.execute(task); } public synchronized void stopRecord() { isStart = false; mWaveList.clear(); resetView(mWaveList, DEFAULT_WAVE_HEIGHT); postInvalidate(); } public synchronized void setText(String text) { this.text = text; postInvalidate(); } public void setUpdateSpeed(int updateSpeed) { this.updateSpeed = updateSpeed; }
想學習更多Android知識,或者擷取上述所有免費資料請關注我并私信【資料】。 有面試資源系統整理分享,Java語言進階和Kotlin語言與Android相關技術核心,APP開發架構知識, 360°Android App全方位性能優化。Android前沿技術,進階UI、Gradle、RxJava、小程式、Hybrid、 移動架構師專題項目實戰環節、React Native、等技術教程!架構師課程、NDK子產品開發、 Flutter等全方面的 Android進階實踐技術講解。還有線上答疑