1. 程式人生 > >安卓權威編程指南-筆記 (第29章定制視圖與觸摸事件)

安卓權威編程指南-筆記 (第29章定制視圖與觸摸事件)

getx prot ret tex when fff ati 簡單 lean

1.定制視圖

  Android自帶眾多優秀的標準視圖與組件,但有時為追求獨特的應用視覺效果,我們仍需創建定制視圖。

  定制視圖分為兩大類別:

  •   簡單視圖: 簡單視圖內部也可以很復雜,之所以歸為簡單類別,是因為簡單視圖不包括子視圖,而且簡單視圖幾乎總是會執行定制繪制。
  •   聚合視圖:聚合視圖由其他視圖對象組成,聚合視圖通常管理著子視圖,但不負責執行定制繪制,圖形繪制任務都委托給了各個子視圖。

  

  創建定制視圖的所需的三大步驟:

  •   選擇超類。對於簡單定制視圖而言,View是個空白畫布,因此它作為超類最常見,對於聚合定制視圖,我們應選擇合適的超類布局,比如FrameLayout.
  •   繼承選定的超類,並至少覆蓋一個超類構造方法。
  •   覆蓋其他關鍵方法,以定制視圖行為。

1.1 創建一個簡單的視圖類

public class BoxDrawingView extends View {
    // Used when creating the view in code
    public BoxDrawingView(Context context) {
        this(context, null);
    }

    // Used when inflating the view from XML


    public BoxDrawingView(Context context, AttributeSet attrs) {
        
super(context, attrs); } }

  從布局文件中實例化的視圖可收到一個AttributeSet實例,該實例包含了XML布局文件中指定的XML屬性。即使不打算使用構造方法,按習慣做法也應添加他們。

  有了定制視圖類,可以在布局文件裏面引用它。

<com.bignerdranch.android.draganddraw.BoxDrawingView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height
="match_parent" />

  在引用時必須使用自定義View的全路徑名,這樣布局inflater才能夠找到它,布局文件inflater解析布局XML文件,並按視圖定義創建View實例,如果元素名不是全路徑名,布局inflater

會轉而在android.view和android.widget包中尋找目標,如果目標視圖放置在其他包中,布局inflater將無法找到目標並最終導致應用崩潰。

  因此,對於android.view和android.widger包以外的定制視圖類,必須指定它們的全路徑名。

1.2 處理觸摸事件

監聽觸摸事件的一種方式是使用以下view方法,設置一個觸摸事件監聽器:

public void setOnTouchListener(View.OnTouchListener l)

不過我們的定制視圖是View的子類,因此可走捷徑直接覆蓋以下View方法:

public boolean onTouchEvent(MotionEvent event)

該方法接收一個MotionEvent類實例,MotionEvent類可用來描述包括位置和動作的觸摸事件。動作用於描述事件所處的階段。

動作常量 動作描述
ACTION_DOWN 手指觸摸到屏幕
ACTION_MOVE 手指在屏幕上移動
ACTION_UP    手指離開屏幕
ACTION_CANCEL 父視圖攔截了觸摸事件

在onTouchEvent()實現方法中,可使用以下MotionEvent方法查看動作值:

public final int getAction()

我們的目的就是在一根手指放下的時候記錄下放下的位置,移動時隨之變化,放開時固定該矩形框。並且之前畫的矩形框數據需要記錄下來。

建立一個實體類,用於表示一個矩形框的定義數據。用來保存原始坐標點(手指的初始位置)和當前坐標點(手指的當前位置):

public class Box {
    private PointF mOrigin;
    private PointF mCurrent;

    public Box(PointF origin) {
        mOrigin = origin;
        mCurrent = origin;
    }
   //get、set略 }

然後重寫onTouchEvent()方法並進行相應操作:

private Box mCurrentBox;
private List<Box> mBoxen = new ArrayList<>();

@Override
public boolean onTouchEvent(MotionEvent event) {
    // 每次有觸摸事件都記錄下現在的坐標
    PointF current = new PointF(event.getX(), event.getY());
    String action = "";

    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            action = "ACTION_DOWN";
            // 每次按下的時候在列表中中新增一個 Box
            mCurrentBox = new Box(current);
            mBoxen.add(mCurrentBox);
            break;
        case MotionEvent.ACTION_MOVE:
            action = "ACTION_MOVE";
            if (mCurrentBox != null) {
            // 移動的時候都要重繪
                mCurrentBox.setCurrent(current);
                invalidate();
            }
            break;
        case MotionEvent.ACTION_UP:
            // 擡起的時候不再指向最新的 Box
            action = "ACTION_UP";
            mCurrentBox = null;
            break;
        case MotionEvent.ACTION_CANCEL:
            action = "ACTION_CANCEL";
            mCurrentBox = null;
            break;
    }

    Log.i(TAG, action + " at x=" + current.x +
           ", y=" + current.y);z

    return true;
}

在取消觸摸事件或用戶手指離開屏幕時,應清空mCurrentBox以結束屏幕繪制,以完成的Box會安全的存儲在數組中。

invalidate()方法會強制BoxDrawingView重新繪制自己,這樣在拖拽時就能實時看到矩形框。

2 onDraw()方法內的圖形繪制

  應用啟動時,所有視圖都處於無效狀態(視圖還沒有繪制到屏幕上),為解決這個問題,Android調用了頂級View視圖的draw()方法,這會引起自上而下的鏈式調用反映。

首先,視圖完成自我繪制,然後是子視圖的自我繪制,再然後是子視圖的子視圖的自我繪制,如此調用下去直至繼承結構的末端。當繼承結構中的所有視圖都完成自我繪制後,最頂級

View 視圖也就生效了。

  為加入這種繪制,可覆蓋以下 View 方法:
    protected void onDraw(Canvas canvas);

  在onTouchEvent()方法中響應ACTION_MOVE動作時,我們調用invalidate()方法再次讓BoxDrawingView處於失效狀態,這迫使他重新完成自我繪制,並再次調用onDraw()方法。

  

  Canvas和Paint是Android系統的兩大繪制類。

  •   Canvas類擁有我們需要的所有繪制操作,其方法可決定在哪裏以及繪什麽,比如線條、圓形、字詞、矩形等。
  •   Paint類決定如何繪制,其方法可指定繪制圖形的特征,例如是否填充圖形、使用什麽字體繪制、線條是什麽顏色等

  

 public BoxDrawingView(Context context, AttributeSet attrs){ //AttributeSet實例包含了XML布局文件中指定的XML屬性。
        super(context,attrs);

        mBoxPaint = new Paint();
        mBoxPaint.setColor(0x22ff0000);

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setColor(0xfff8efe0);
    }

 @Override
    protected void onDraw(Canvas canvas){
        //先畫出背景
        canvas.drawPaint(mBackgroundPaint);

        //畫出每個繪制過的矩形
        for(Box box : mBoxen){
            float left = Math.min(box.getOrigin().x, box.getCurrent().x);
            float right = Math.max(box.getOrigin().x, box.getCurrent().x);
            float top = Math.min(box.getOrigin().y, box.getCurrent().y);
            float bottom = Math.max(box.getOrigin().y,box.getCurrent().y);

            canvas.drawRect(left, top ,right ,bottom, mBoxPaint);
        }

    }

以上代碼的第一部分簡單直接:使用米白背景paint,填充canvas以襯托矩形框。然後,針對矩形框數組中的每一個矩形框,據其兩點坐標,確定矩形框上下左右的位置。繪制時,左端和頂端的值作為最小值,右端和底端的值作為最大值。完成位置坐標值計算後,調用 Canvas.drawRect(...) 方法,在屏幕上繪制紅色矩形框。

  

  

安卓權威編程指南-筆記 (第29章定制視圖與觸摸事件)