這裏寫鏈接內容# 自定義View基礎與原理
什麼是自定義View
其實就是繼承系統的View,然後加入繪製元素(文字/圖形)和邏輯,最終達到自己想要想過的控件。
爲什麼使用自定義View
- 特定的顯示風格
- 處理特有的用戶交互
- 優化佈局
- 封裝等
如何自定義控件
編寫自己的自定義View
- 編寫最簡單的自定義View,什麼都不顯示,但是有View的特性
/**
* java代碼創建視圖的時候被調用,如果是從xml填充的視圖,就不會調用這個
*
* @param context
*/
public MyView(Context context) {
super(context);
}
/**
* 這個是在xml創建但是沒有指定style的時候被調用
*
* @param context
* @param attrs
*/
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 這個是在xml創建且指定style的時候被調用
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
那種情況下調用那個構造方法,上面註釋已經寫的很清楚了。不妨我們想一下,讓第一個構造方法去調用第二個構造方法,第二個去調用第三個,這樣會不會省去一些事呢。如下面例子的代碼所示。
- 可以顯示自己的元素(文字/幾何圖形/圖片)
public class MyView extends View {
private Paint paint;
private Bitmap bitmap;
/**
* java代碼創建視圖的時候被調用,如果是從xml填充的視圖,就不會調用這個
*
* @param context
*/
public MyView(Context context) {
this(context, null);
}
/**
* 這個是在xml創建但是沒有指定style的時候被調用
*
* @param context
* @param attrs
*/
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 這個是在xml創建且指定style的時候被調用
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// Paint樣式和顏色信息,關於文字/幾何圖形和bitmap的
paint = new Paint();
bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setTextSize(30);
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
// 繪製文字
canvas.drawText("this is onDraw", 0, 30, paint);
// 繪製直線
canvas.drawLine(0, 60, 100, 60, paint);
// 繪製矩形
canvas.drawRect(0, 90, 100, 190, paint);
// Rect中參數int類型
Rect r = new Rect(100, 90, 200, 190);
canvas.drawRect(r, paint);
// Rectf中參數float類型
RectF rect = new RectF(200, 90, 300, 190);
canvas.drawRect(rect, paint);
// 圓角矩形rx, ry x和y方向上的弧度
RectF rect2 = new RectF(300, 90, 400, 190);
canvas.drawRoundRect(rect2, 30, 30, paint);
// 繪製圓形
canvas.drawCircle(50, 270, 50, paint);
// 繪製圖片
canvas.drawBitmap(bitmap, 0, 350, paint);
}
}
佈局中使用
<!-- 完整的包名和類名 -->
<com.example.myview.MyView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
運行結果如圖:
加入邏輯線程
- 怎麼讓元素動起來呢,需要什麼
不斷的改變元素繪製的座標位置,我們在視覺上就會感覺到元素在運動 - 元素動起來的邏輯放在哪裏
創建一個線程,執行run方法裏面改變其座標,在進行重新繪製,android提供了在線程中重新繪製的方法postInvalidate() - 怎樣看起來動起來的元素流暢
一秒內繪製20次即可
public class LogicView extends View {
private Paint paint;
private float rx;
/**
* 邏輯線程
*/
private MyThread mThread;
private RectF rectF;
/**
* 區間角度
*/
private float sweepAngle;
public LogicView(Context context) {
this(context, null);
}
public LogicView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LogicView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 對類成員進行初始化
*/
private void init() {
paint = new Paint();
rectF = new RectF(0, 60, 100, 160);
mThread = new MyThread();
mThread.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setTextSize(30);
canvas.drawText("LogicView", rx, 30, paint);
// 起始角度/區間角度
canvas.drawArc(rectF, 0, sweepAngle, true, paint);
}
class MyThread extends Thread {
Random random = new Random();
@Override
public void run() {
super.run();
// 不斷的進行繪製
while (true) {
long start = System.currentTimeMillis();
// 超出屏幕長度
if ((rx += 3) > getWidth()) {
rx = 0 - paint.measureText("LogicView");
}
// 超出360度,清零
if (sweepAngle++ > 360) {
sweepAngle = 0;
}
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
paint.setARGB(255, r, g, b);
// 線程中更新繪製的方法
// postInvalidate();
invalidateView();
long end = System.currentTimeMillis();
// 一秒繪製20次即可
if (end - start < 50) {
try {
Thread.sleep(50 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 重繪
*/
private void invalidateView() {
if (Looper.getMainLooper() == Looper.myLooper()) {
invalidate();
} else {
postInvalidate();
}
}
}
提取和封裝自定義View
- 封裝後的基類
public abstract class BaseView extends View {
/**
* 邏輯線程
*/
private MyThread mThread;
/**
* 線程的控制開關
*/
private boolean isRunning = true;
public BaseView(Context context) {
this(context, null);
}
public BaseView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// 這裏使用final關鍵字,不讓子類重寫
@Override
protected final void onDraw(Canvas canvas) {
if (mThread == null) {
mThread = new MyThread();
mThread.start();
} else {
drawSub(canvas);
}
}
/**
* 繪製元素
*
* @param canvas
*/
protected abstract void drawSub(Canvas canvas);
/**
* 邏輯處理
*/
protected abstract void logic();
//離開屏幕的時候結束掉線程
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
isRunning = false;
}
class MyThread extends Thread {
@Override
public void run() {
super.run();
// 不斷的進行繪製
while (isRunning) {
long start = System.currentTimeMillis();
logic();
invalidateView();
long end = System.currentTimeMillis();
// 一秒繪製20次即可
if (end - start < 50) {
try {
Thread.sleep(50 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 重繪
*/
public void invalidateView() {
if (Looper.getMainLooper() == Looper.myLooper()) {
invalidate();
} else {
postInvalidate();
}
}
}
該過程中我們提出的繪製元素drawSub(Canvas canvas)方法和邏輯處理logic()方法。並將onDraw使用final關鍵字,不讓子類去重寫。當繪製離開屏幕的時候,結束掉邏輯處理線程。
使用基類
public class LogicView extends BaseView {
private Paint paint;
private float rx;
private RectF rectF;
/**
* 區間角度
*/
private float sweepAngle;
private Random random;
public LogicView(Context context) {
this(context, null);
}
public LogicView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LogicView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 對類成員進行初始化
*/
protected void init() {
paint = new Paint();
rectF = new RectF(0, 60, 100, 160);
random = new Random();
}
@Override
protected void drawSub(Canvas canvas) {
paint.setTextSize(30);
canvas.drawText("LogicView", rx, 30, paint);
// 起始角度/區間角度
canvas.drawArc(rectF, 0, sweepAngle, true, paint);
}
@Override
protected void logic() {
// 超出屏幕長度
if ((rx += 3) > getWidth()) {
rx = 0 - paint.measureText("LogicView");
}
// 超出360度,清零
if (sweepAngle++ > 360) {
sweepAngle = 0;
}
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
paint.setARGB(255, r, g, b);
}
}
運行結果如圖:
定義XML中定義的樣式來影響顯示效果
- 自定義樣式attr.xml
新建自定義樣式attr.xml在res/values目錄下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumText">
<!-- 繪製的行數 -->
<attr name="lineNum" format="integer"></attr>
<!-- 文字是否滾動顯示 -->
<attr name="isScrool" format="boolean"></attr>
</declare-styleable>
</resources>
- 在佈局中使用
在使用自定義屬性的時候先聲明xml的命名空間
- eclipse:
xmlns:myview=”http://schemas.android.com/apk/res/完整的包名” - android studio:
xmlns:myview=”http://schemas.android.com/apk/res-auto”
佈局中的使用:
- eclipse:
<com.example.myview.v4.NumText
android:layout_width="match_parent"
android:layout_height="match_parent"
myview:isScrool="true"
myview:lineNum="3" >
</com.example.myview.v4.NumText>
- 例子代碼
public class NumText extends BaseView {
/**
* 畫筆
*/
private Paint paint;
/**
* 繪製文字x座標
*/
private float rx;
/**
* 繪製的行數
*/
private int lintNum;
private Random random;
/**
* 是否滾動
*/
private boolean isScrool;
public NumText(Context context) {
this(context, null);
}
public NumText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
random = new Random();
//默認不滾動
isScrool = false;
//在view構造方法中獲取自定義屬性
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.NumText);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.NumText_lineNum:
lintNum = (int) a.getInt(attr, 1);
break;
case R.styleable.NumText_isScrool:
isScrool = (boolean) a.getBoolean(attr, false);
break;
}
}
a.recycle();
}
@Override
protected void drawSub(Canvas canvas) {
// 繪製lineNum行文字
for (int i = 0; i < lintNum; i++) {
int textSize = 30 + i;
paint.setTextSize(textSize);
canvas.drawText("自定義View基礎與原理", rx, textSize + textSize * i, paint);
}
}
@Override
protected void logic() {
if(isScrool){
// 超出屏幕長度
if ((rx += 5) > getWidth()) {
rx = 0 - paint.measureText("自定義View基礎與原理");
}
// 改變畫筆的顏色
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
paint.setARGB(255, r, g, b);
}
}
}
運行結果: