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使用CountDownTimer模擬短信驗(yàn)證倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了Android使用CountDownTimer模擬短信驗(yàn)證倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android使用SlidingPaneLayout 實(shí)現(xiàn)仿微信的滑動(dòng)返回
這篇文章主要介紹了Android使用SlidingPaneLayout 實(shí)現(xiàn)仿微信的滑動(dòng)返回,需要的朋友可以參考下2018-04-04
Android 自定義View之倒計(jì)時(shí)實(shí)例代碼
這篇文章主要介紹了Android 自定義View之倒計(jì)時(shí)實(shí)例代碼的相關(guān)資料,大多數(shù)app在注冊的時(shí)候,都有一個(gè)獲取驗(yàn)證碼的按鈕,點(diǎn)擊后,訪問接口,最終用戶會(huì)收到短信驗(yàn)證碼。為了不多次寫這個(gè)獲取驗(yàn)證碼的接口,下面將它自定義成一個(gè)view,方便使用,需要的朋友可以參考下2017-04-04
Android仿英語流利說取詞放大控件的實(shí)現(xiàn)方法(附demo源碼下載)
這篇文章主要介紹了Android仿英語流利說取詞放大控件的實(shí)現(xiàn)方法,較為詳細(xì)的分析了取詞放大控件的實(shí)現(xiàn)步驟與相關(guān)技巧,需要的朋友可以參考下2016-02-02
Android?十六進(jìn)制狀態(tài)管理實(shí)例詳解
這篇文章主要為大家介紹了Android?十六進(jìn)制狀態(tài)管理實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Android編程實(shí)現(xiàn)禁止StatusBar下拉的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)禁止StatusBar下拉的方法,涉及Android StatusBarManager相關(guān)屬性控制操作技巧,需要的朋友可以參考下2017-08-08
Android入門之使用SharedPreference存取信息詳解
這篇文章主要為大家詳細(xì)介紹了Android如何使用SharedPreference實(shí)現(xiàn)存取信息,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Android有一定的幫助,需要的可以參考一下2022-12-12
Android時(shí)間選擇器、日期選擇器實(shí)現(xiàn)代碼
這篇文章主要為大家分別介紹了Android時(shí)間選擇器、日期選擇器實(shí)現(xiàn)代碼,感興趣的小伙伴們可以參考一下2016-04-04

