編輯:關於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 自定義控件之音頻條,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!
Android 圖片的顏色處理實例代碼
仿造美圖秀秀移動鼠標調整seekbar,調整圖片的顏色項目布局如下:<LinearLayout xmlns:android=http://schemas.andro
android 5.X Toolbar+DrawerLayout實現抽屜菜單
前言?android5.X新增的一個控件Toolbar,這個控件比ActionBar更加自由,可控,由於以前的ActionBar的靈活性比較差,所以google逐漸使用T
Android實戰打飛機游戲之無限循環的背景圖(2)
首先分析下游戲界面內的元素:無限滾動的背景圖, 可以操作的主角,主角的子彈, 主角的血量,兩種怪物(敵機),一個boss, boss的爆炸效果.先看效果圖1、首先實現無限
Android官方入門文檔[7]樣式化操作欄
Android官方入門文檔[7]樣式化操作欄 Styling the Action Bar 樣式化操作欄 This lesson teache