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