Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Andriod 自定義控件之音頻條

Andriod 自定義控件之音頻條

編輯:關於Android編程

今天我們實現一個直接繼承於View的全新控件。大家都知道音樂播放器吧,在點擊一首歌進行播放時,通常會有一塊區域用於顯示音頻條,我們今天就來學習下,播放器音頻條的實現。

首先我們還是先定義一個類,直接繼承於View,並重寫它的構造方法,並初始化一個畫筆,這和上一節是同樣的道理。直接貼出代碼:

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);
}

有聽過或是播放音樂的伙伴大都知道音頻條是什麼樣子的,無非就是來回跳動的不同豎形圖,在這裡我們稍微轉換下思想就知道,在我們android中可以以豎形矩形來實現,各個矩形之間以固定的間距分割開來就能模仿實現我們的目標控件-音頻條。先貼出代碼,稍候看代碼解釋:

@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);
}
}

好,來看下這段代碼,首先是我們先獲取手機頻幕的尺寸大小,然後我會根據手機頻幕尺寸和預先定義出的矩形寬度(這裡使用mRectWidth變量)來計算出當前手機頻幕可以容納多少個矩形(使用mRectCount 來計數)。然後通過循環創建矩形的方式,讓系統給我們畫出我們所定義的視圖。當然這裡我還隨機產生了一個隨機數,用於控制矩形的高度。

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);
}
}

現在運行程序看看效果吧。

是不是很酷呢?但是有伙伴該有疑問了,音頻條不都是動態的嗎?現在我們實現的只是靜態的矩形條呀,別急,我們現在讓它動起來,但是該怎麼實現呢?

有經驗的伙伴都知道,我們所使用或定義的UI視圖都是在onDraw()繪制完成之後在Activity中顯示出來的,那麼我們要實現動態的視圖是不是可以不停的調用該方法呢?又有什麼方法可以不停的調用它使它不停的繪制呢?答案顯而易見,使用invalidate();方法,它可以不停的重新繪制View。因為使用invalidate();間隔太短,速度太快,所以根據我們的需求,我們可以使用延遲的方法重繪View,在這裡我們使用postInvalidateDelayed(500);讓它500毫秒重畫一次,這樣就可以體現了動態的音頻條。大家可以試下,動態圖不太會搞,我就不貼圖了,你可以跑下程序了。

ok,現在已基本符合我們的要求了,是不是送了一口氣呢,還沒有,你有沒有試試在layout文件中為我們自定義的控件添加padding屬性呢,試試吧。哈哈,是不是也木有任何改變呢?

那是因為我們在onDraw()方法中沒有考慮到這一情況的發生。在自定義控件中,直接繼承View時,必須要考慮到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);
}

也相當的好理解,根據當前情景對padding屬性進行控制一下就ok了,小伙伴們現在趕緊在運行試試吧。

到這裡整個自定義控件已差不多完成,但是細心的伙伴可能會發現:我們制作的音頻條不可能占據整個頻幕呀,嘿嘿,這個比較簡單,我們通常的做法是修改一下布局文件不就行喽,好,修改如下:

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,大功告成,在運行下,試試。

我倒,怎麼完全沒有木有變化啊,檢查檢查,還是木有問題,到底是哪個出問題了呢,我想你該蒙了。

這時候你該通過搜索或是書籍查詢了,(10秒鐘以後,哈哈)通過了解你大概明白了問題所在,View的工作流程是在onDraw繪制之前,是需要先測量布局的,這裡引入了兩個名詞,測量,和布局。後面我想針對View的工作流程專門做一節學習,所以,我們現在只需要先了解下View測量的工作是在哪進行的。

好,經過查詢資料,我們了解到,View的測量工作是在onMeasure()方法中進行的。接下來讓我們看看它到底是怎麼測量的,而我們在當前場景下使用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));
}

在方法體中只是調用了setMeasuredDimension();方法來決定View尺寸的,再看它裡面的參數是通過getDefaultSize()方法獲取大小,再次跟進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類(後面會詳細講解)獲取測量的模式和測量的大小,然後通過測量的模式來決定測量的大小,但是有一點是不是很奇怪呢,當測量模式為AT_MOST(最大值模式,對應的是layout寬高屬性是wrap_content)時它的測量大小和模式為EXACTLY(精確值模式,對應的是layout寬高屬性是match_parent)的測量大小一樣呢,因為我們恍然大悟,系統默認的測量大小不管是layout寬高屬性是wrap_content還是match_parent它的取值都是match_parent是的默認值。

由此可以明白,我們在修改了layout寬高屬性值時,並沒有達到我們預期的希望。那該怎麼解決呢?其實也很簡單,因為,View測量大小的取值取決於setMeasuredDimension()這個方法,因此只要我們重寫了setMeasuredDimension()方法,就可以完成我們的需求。因此,我們可以進行如下操作:

@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);
}
}

代碼解釋:首先我們分別先得到控件測量的模式和大小,然後根據情況分別識別當前View屬性屬於哪種情景,再根據具體的情景進行重寫了setMeasuredDimension()方法。這裡我是讓它各顯示屏幕的一半。好,來看看現在是否符合了我們的需求。

好了,完全符合需求,可以開心下了。

總結下:當我們直接繼承View實現自定義控件時,主要困難點就在於坐標系的計算,計算出正確的坐標,自定義的控件也就完成大半了,另外還有需要針對padding屬性和layout_width 和 layout_height屬性值為wrap_content的情況進行必要的考慮。好了,今天就說到這裡吧。

以上所述是小編給大家介紹的Andriod 自定義控件之音頻條,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved