Andriod 自定義控件之音頻條
今天我們實(shí)現(xiàn)一個(gè)直接繼承于View的全新控件。大家都知道音樂播放器吧,在點(diǎn)擊一首歌進(jìn)行播放時(shí),通常會(huì)有一塊區(qū)域用于顯示音頻條,我們今天就來學(xué)習(xí)下,播放器音頻條的實(shí)現(xiàn)。
首先我們還是先定義一個(gè)類,直接繼承于View,并重寫它的構(gòu)造方法,并初始化一個(gè)畫筆,這和上一節(jié)是同樣的道理。直接貼出代碼:
public class AudioBar extends View{ private Paint mTextPaint; public AudioBar(Context context) { this(context,null); } public AudioBar(Context context, AttributeSet attrs) { this(context, attrs,0); } public AudioBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mTextPaint = new TextPaint(); mTextPaint.setColor(Color.RED); } }
然后同樣的道理,想要定義我們自己的View控件,我們需要重寫View的onDraw()方法。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }
有聽過或是播放音樂的伙伴大都知道音頻條是什么樣子的,無非就是來回跳動(dòng)的不同豎形圖,在這里我們稍微轉(zhuǎn)換下思想就知道,在我們android中可以以豎形矩形來實(shí)現(xiàn),各個(gè)矩形之間以固定的間距分割開來就能模仿實(shí)現(xiàn)我們的目標(biāo)控件-音頻條。先貼出代碼,稍候看代碼解釋:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); width = getMeasuredWidth() ; height = getMeasuredHeight(); int mRectCount = 0; for(int count = 5 ; count < width ;count += mRectWidth){ mRectCount ++ ; } for(int i = 0 ; i < mRectCount ; i ++){ double mRandom = Math.random(); mRectHeight = (float) (height * mRandom); canvas.drawRect(offset + mRectWidth * i, mRectHeight, mRectWidth * (i+1), height, mTextPaint); } }
好,來看下這段代碼,首先是我們先獲取手機(jī)頻幕的尺寸大小,然后我會(huì)根據(jù)手機(jī)頻幕尺寸和預(yù)先定義出的矩形寬度(這里使用mRectWidth變量)來計(jì)算出當(dāng)前手機(jī)頻幕可以容納多少個(gè)矩形(使用mRectCount 來計(jì)數(shù))。然后通過循環(huán)創(chuàng)建矩形的方式,讓系統(tǒng)給我們畫出我們所定義的視圖。當(dāng)然這里我還隨機(jī)產(chǎn)生了一個(gè)隨機(jī)數(shù),用于控制矩形的高度。
ok,把它加入到我們的布局文件中,并在Activity中顯示出來看看是什么效果吧:
activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.sanhuimusic.mycustomview.MainActivity"> <com.sanhuimusic.mycustomview.view.AudioBar android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
然后MainActivity類
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
現(xiàn)在運(yùn)行程序看看效果吧。
是不是很酷呢?但是有伙伴該有疑問了,音頻條不都是動(dòng)態(tài)的嗎?現(xiàn)在我們實(shí)現(xiàn)的只是靜態(tài)的矩形條呀,別急,我們現(xiàn)在讓它動(dòng)起來,但是該怎么實(shí)現(xiàn)呢?
有經(jīng)驗(yàn)的伙伴都知道,我們所使用或定義的UI視圖都是在onDraw()繪制完成之后在Activity中顯示出來的,那么我們要實(shí)現(xiàn)動(dòng)態(tài)的視圖是不是可以不停的調(diào)用該方法呢?又有什么方法可以不停的調(diào)用它使它不停的繪制呢?答案顯而易見,使用invalidate();方法,它可以不停的重新繪制View。因?yàn)槭褂胕nvalidate();間隔太短,速度太快,所以根據(jù)我們的需求,我們可以使用延遲的方法重繪View,在這里我們使用postInvalidateDelayed(500);讓它500毫秒重畫一次,這樣就可以體現(xiàn)了動(dòng)態(tài)的音頻條。大家可以試下,動(dòng)態(tài)圖不太會(huì)搞,我就不貼圖了,你可以跑下程序了。
ok,現(xiàn)在已基本符合我們的要求了,是不是送了一口氣呢,還沒有,你有沒有試試在layout文件中為我們自定義的控件添加padding屬性呢,試試吧。哈哈,是不是也木有任何改變呢?
那是因?yàn)槲覀冊趏nDraw()方法中沒有考慮到這一情況的發(fā)生。在自定義控件中,直接繼承View時(shí),必須要考慮到padding屬性對控件的影響,所以接下來,讓我們的控件貼近原生控件吧。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int leftPadding = getPaddingLeft(); int topPadding = getPaddingTop(); int rightPadding = getPaddingRight(); int bottomPadding = getPaddingBottom(); width = getMeasuredWidth() - leftPadding - rightPadding; height = getMeasuredHeight()- topPadding - bottomPadding; int mRectCount = 0; for(int count = 5 ; count < width ;count += mRectWidth){ mRectCount ++ ; } for(int i = 0 ; i < mRectCount ; i ++){ double mRandom = Math.random(); mRectHeight = (float) (height * mRandom); canvas.drawRect(offset + mRectWidth * i, mRectHeight, mRectWidth * (i+1), height, mTextPaint); } postInvalidateDelayed(500); }
也相當(dāng)?shù)暮美斫?,根?jù)當(dāng)前情景對padding屬性進(jìn)行控制一下就ok了,小伙伴們現(xiàn)在趕緊在運(yùn)行試試吧。
到這里整個(gè)自定義控件已差不多完成,但是細(xì)心的伙伴可能會(huì)發(fā)現(xiàn):我們制作的音頻條不可能占據(jù)整個(gè)頻幕呀,嘿嘿,這個(gè)比較簡單,我們通常的做法是修改一下布局文件不就行嘍,好,修改如下:
activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.sanhuimusic.mycustomview.MainActivity"> <com.sanhuimusic.mycustomview.view.AudioBar android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
ok,大功告成,在運(yùn)行下,試試。
我倒,怎么完全沒有木有變化啊,檢查檢查,還是木有問題,到底是哪個(gè)出問題了呢,我想你該蒙了。
這時(shí)候你該通過搜索或是書籍查詢了,(10秒鐘以后,哈哈)通過了解你大概明白了問題所在,View的工作流程是在onDraw繪制之前,是需要先測量布局的,這里引入了兩個(gè)名詞,測量,和布局。后面我想針對View的工作流程專門做一節(jié)學(xué)習(xí),所以,我們現(xiàn)在只需要先了解下View測量的工作是在哪進(jìn)行的。
好,經(jīng)過查詢資料,我們了解到,View的測量工作是在onMeasure()方法中進(jìn)行的。接下來讓我們看看它到底是怎么測量的,而我們在當(dāng)前場景下使用wrap_content為什么沒有效果?帶著問題,我們先重寫View的onMeasure()方法,如下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
然后跟到super.onMeasure(widthMeasureSpec, heightMeasureSpec);源碼中,我們所看到的源碼很簡單,如下,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
在方法體中只是調(diào)用了setMeasuredDimension();方法來決定View尺寸的,再看它里面的參數(shù)是通過getDefaultSize()方法獲取大小,再次跟進(jìn)getDefaultSize()方法中。
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
ok,很簡單的源碼,主要通過MeasureSpec類(后面會(huì)詳細(xì)講解)獲取測量的模式和測量的大小,然后通過測量的模式來決定測量的大小,但是有一點(diǎn)是不是很奇怪呢,當(dāng)測量模式為AT_MOST(最大值模式,對應(yīng)的是layout寬高屬性是wrap_content)時(shí)它的測量大小和模式為EXACTLY(精確值模式,對應(yīng)的是layout寬高屬性是match_parent)的測量大小一樣呢,因?yàn)槲覀兓腥淮笪?,系統(tǒng)默認(rèn)的測量大小不管是layout寬高屬性是wrap_content還是match_parent它的取值都是match_parent是的默認(rèn)值。
由此可以明白,我們在修改了layout寬高屬性值時(shí),并沒有達(dá)到我們預(yù)期的希望。那該怎么解決呢?其實(shí)也很簡單,因?yàn)?,View測量大小的取值取決于setMeasuredDimension()這個(gè)方法,因此只要我們重寫了setMeasuredDimension()方法,就可以完成我們的需求。因此,我們可以進(jìn)行如下操作:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(getMeasuredWidth()/2 , getMeasuredHeight()/2); } else if(widthSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(getMeasuredWidth()/2 ,heightSpecSize); } else if(heightSpecMode == MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpecSize ,getMeasuredHeight()/2); } }
代碼解釋:首先我們分別先得到控件測量的模式和大小,然后根據(jù)情況分別識(shí)別當(dāng)前View屬性屬于哪種情景,再根據(jù)具體的情景進(jìn)行重寫了setMeasuredDimension()方法。這里我是讓它各顯示屏幕的一半。好,來看看現(xiàn)在是否符合了我們的需求。
好了,完全符合需求,可以開心下了。
總結(jié)下:當(dāng)我們直接繼承View實(shí)現(xiàn)自定義控件時(shí),主要困難點(diǎn)就在于坐標(biāo)系的計(jì)算,計(jì)算出正確的坐標(biāo),自定義的控件也就完成大半了,另外還有需要針對padding屬性和layout_width 和 layout_height屬性值為wrap_content的情況進(jìn)行必要的考慮。好了,今天就說到這里吧。
以上所述是小編給大家介紹的Andriod 自定義控件之音頻條,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Android自定義View實(shí)現(xiàn)直播點(diǎn)贊特效
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)直播點(diǎn)贊特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07Android手機(jī)上同時(shí)安裝正式包與測試包的方法
這篇文章主要給大家介紹了關(guān)于Android手機(jī)上同時(shí)安裝正式包與測試包的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02android MediaRecorder實(shí)現(xiàn)錄屏?xí)r帶錄音功能
這篇文章主要介紹了android MediaRecorder錄屏?xí)r帶錄音功能實(shí)現(xiàn)代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Android仿今日頭條滑動(dòng)頁面導(dǎo)航效果
這篇文章主要為大家詳細(xì)介紹了Android仿今日頭條滑動(dòng)頁面導(dǎo)航效果的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01Android隱藏標(biāo)題欄及解決啟動(dòng)閃過標(biāo)題的實(shí)例詳解
這篇文章主要介紹了Android隱藏標(biāo)題欄及解決啟動(dòng)閃過標(biāo)題的實(shí)例詳解的相關(guān)資料,這里提供兩種方法幫助大家解決這種問題,需要的朋友可以參考下2017-09-09淺析Android App的相對布局RelativeLayout
這篇文章主要介紹了Android App的相對布局RelativeLayout,文中舉了一個(gè)登錄界面的XML布局例子,非常直觀,需要的朋友可以參考下2016-04-04