編輯:關於Android編程
這是Android UI Fundamentals裡的內容
創建自定義UI組件首先要繼承一個視圖類.
首先創建一個簡單的自定義視圖, 展示一條十字線.
需要做的第一件事是創建一個繼承自View的CrossView類.
public CrossView(Context context, AttributeSet attrs) {
super(context, attrs);
}
該構造函數的第二個參數是用來傳遞XML參數的, 等會兒會講到. 接下來我們要重寫兩個基礎方法: onMeasure
和 onDraw
.
系統調用onMeasure
方法來決定視圖及其子視圖的尺寸. 它的兩個參數的類型都是int
, 但是這倆參數並非普通的數字, 而是兩個MeasureSpec
, MeasureSpec
是一個模式和一個整型尺寸值的結合, 被當成一個整數來實現. 其中模式值有如下幾種情況:
MeasureSpec
中尺寸的任意大小
EXACTLY
父視圖要求該視圖必須是MeasureSpec
指定的尺寸大小
當你創建一個自定義視圖並重寫onMeasure
方法時, 必須正確處理每種情況, 得到相應的尺寸, 然後必須在onMeasure
中調用setMeasureDimensions
方法, 參數就是你決定的尺寸, 如果不調用就會拋出異常.
下面是重寫的onMeasure
方法代碼.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(calculateMeasure(widthMeasureSpec), calculateMeasure(heightMeasureSpec));
}
注意其中calculateMeasure
方法是我們自己定義的, 下面我們來完成這個方法.
我們先定義一個默認的尺寸100, 單位是dp(我暫時不確定是不是dp).
private static final int DEFAULT_SIZE = 100;
乘上設備的像素密度, 得到實際顯示需要的像素值.
int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
然後我們需要從MeasureSpec中拿到模式和尺寸
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
接下來我們根據specMode
的情況來判斷result
的值到底應該是什麼.
MeasureSpec.UNSPECIFIED
int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
MeasureSpec.AT_MOST
result = Math.min(specSize, result);
MeasureSpec.EXACTLY
result
等於它就好
result = specSize;
綜合上面的討論, 最終我們的方法代碼如下:
private static final int DEFAULT_SIZE = 100;
private int calculateMeasure(int measureSpec) {
int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(specSize, result);
}
return result;
}
當視圖應當繪制其內容時會調用onDraw
方法. 在重寫它之前, 我們先創建一個Paint
對象, 它處理諸如顏色和文本大小之類的事情.
通過CrossView
的構造函數來創建Paint
對象
private Paint mPaint;
public CrossView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(0xffff0000);
}
上面的代碼新建了Paint
對象, 並設置抗鋸齒和顏色.
接下來重寫onDraw
方法, 模板如下, canvas.save()
和canvas.restore()
我就不解釋了, 不影響後面的理解.
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
// code goes here
canvas.restore();
}
我們基於視圖的尺寸縮放畫布, 這樣我們可以使用0到1之間的浮點數來作為畫線時的坐標
private static final float[] mPoints = {0.5f, 0f,
0.5f, 1f,
0f, 0.5f,
1f, 0.5f};
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(getWidth(), getHeight());
canvas.drawLines(mPoints, mPaint);
canvas.restore();
}
我們在activity的xml裡面加入我們的自定義控件
運行一下就可以看到文章開頭的截圖畫面了.
有了自定義視圖, 我們希望它能通過自定義XML屬性來配置, 要做到這一點, 需要先聲明屬性, 然後在XML布局中添加一個新的命名空間, 最後處理被傳遞給自定義視圖構造函數的AttributeSet對象.
在res/values/目錄下創建一個attrs.xml(可以是別的名字)的文件, 然後在其中添加如下內容:
declare-styleable
元素有一個name屬性, 用來在代碼中的引用自定義屬性, 每個自定義的屬性都使用一個attr
元素來聲明, attr
元素有name和format兩個屬性, name用於引用, format代表它的數據類型, 如果使用了默認的系統屬性, 就不需要定義format了, 如果嘗試給已有的屬性定義一個不同的format, 則工程無法build. 在外層聲明的attr
可以被其他declare-styleable
復用, 就和使用系統屬性一樣, 比如:
也可以給屬性創建自定義值, 例如
enum
和flag
都要求是整數. 不同之處在於flag
可以使用|
來拼接. 比如android:gravity
的值就是flag.
要使用在我們的XML中的新屬性, 首先必須為視圖聲明namespace. 其實我們經常見到namespace的聲明, 比如我們常在activity的xml文件中看到
xmlns:android="http://schemas.android.com/apk/res/android"
這個namespace聲明了所有以關鍵詞android
開頭的屬性都可以在android包中找到. 要使用自定義屬性, 需要聲明一個帶有新包名的新namespace, 下面為CrossView的屬性添加一個新的namespace, 並在自定義視圖中添加相關的xml配置:
上面聲明了所有以crossview
(名字可以用別的)開頭的屬性都可以在res中找到. 這是Gradle要求的寫法.
在CrossView
的構造函數中傳入了一個AttributeSet
對象, 我們可以通過它獲取XML布局中聲明的屬性.
更新CrossView
的構造函數並添加相應函數和成員變量:
private float mRotation;
public CrossView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.cross);
int color = arr.getColor(R.styleable.cross_android_color, Color.BLACK);
float rotation = arr.getFloat(R.styleable.cross_rotation, 0f);
arr.recycle();
setColor(color);
setRotation(rotation);
}
public void setColor(int color) {
mPaint.setColor(color);
}
public void setRotation(float degree) {
mRotation = degree;
}
同時更新onDraw
的代碼
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(getWidth(), getHeight());
canvas.rotate(mRotation, 0.5f, 0.5f);
canvas.drawLines(mPoints, mPaint);
canvas.restore();
}
我們的旋轉中心是畫布中心, 而不是左上角.
現在運行這個程序, 截圖如下:
從2015年下半年開始,Google為了簡化Android的開發工作,不再支持對Eclipse等開發工具的更新,主推由Google開發的AndroidStudio開發工具
前面本來說是做h264編碼的 研究了兩天發現ffmpeg裡的h264編碼似乎是要信賴第三方庫x264 還是怎麼簡單怎麼來吧所以就整了個mpeg編碼 ffmp
深入理解Adapter 一、ListView ListView是Android開發過程中較為常見的組件之一,它將數據以列表的形式展現出來。一般而言,一個ListView由
一、環境分離簡介每個App項目,至少都會有兩個環境:測試環境和生產環境。多的甚至有四個環境:開發環境、測試環境、預生產環境和生產環境。開發人員經常需要在環境之間切換,測試
上一篇文章主要講述了Android的TouchEvent的分發過程,其中