Android View 流程簡讀

上一篇大致介紹瞭如何更新主題風格之類的,沒有談及到一些本質的,這一篇大致介紹一下所有的UI歸根結底的View類顯示效果流程.


這裏面以View背景動畫產生流程爲主線的.(即,我們經常會遇到各種點擊效果,如material的水波紋,或者以前的按鈕點擊前後的背景效果變化)


看了android源碼View.java類

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource 

View實現三個接口,其中Drawable.Callback是View圖像繪製更新接口:

public static interface Callback {
        /**
         * Called when the drawable needs to be redrawn.  A view at this point
         * should invalidate itself (or at least the part of itself where the
         * drawable appears).
         *
         * @param who The drawable that is requesting the update.
         */
        public void invalidateDrawable(Drawable who);

        /**
         * A Drawable can call this to schedule the next frame of its
         * animation.  An implementation can generally simply call
         * {@link android.os.Handler#postAtTime(Runnable, Object, long)} with
         * the parameters <var>(what, who, when)</var> to perform the
         * scheduling.
         *
         * @param who The drawable being scheduled.
         * @param what The action to execute.
         * @param when The time (in milliseconds) to run.  The timebase is
         *             {@link android.os.SystemClock#uptimeMillis}
         */
        public void scheduleDrawable(Drawable who, Runnable what, long when);

        /**
         * A Drawable can call this to unschedule an action previously
         * scheduled with {@link #scheduleDrawable}.  An implementation can
         * generally simply call
         * {@link android.os.Handler#removeCallbacks(Runnable, Object)} with
         * the parameters <var>(what, who)</var> to unschedule the drawable.
         *
         * @param who The drawable being unscheduled.
         * @param what The action being unscheduled.
         */
        public void unscheduleDrawable(Drawable who, Runnable what);
    }

至於KeyEvent.Callback是觸摸按鍵點擊事件回調的接口,用於監聽按鍵消息事件,這個很容易理解,很多UI都會讓使用者去點擊的.不在貼代碼了.

根據上面兩個接口,可以這樣理解,一個接口用於視圖顯示刷新和繪製(其實這個是Drawable的事),一個是讓View響應按鍵event,View的視圖變化和刷新通過按鍵事件去驅動,即當key event產生後,回調Drawable回調接口,對視圖進行更新.

因爲源代碼又臭又長,所以我們可以自己寫一個類似的開發demo,來簡約說明大致工作流程:

<1> : 新建一個android工程:PumpKinCallBack工程:工程結構如下:

<2> : 具體代碼如下:

package org.pumpkin.pumpkincallback;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import org.pumpkin.pumpkincallback.interfaces.KeyEvent;
import org.pumpkin.pumpkincallback.view.Button;
import org.pumpkin.pumpkincallback.view.TextView;
import org.pumpkin.pumpkincallback.view.View;

public class PumpKinMainActivity extends AppCompatActivity implements KeyEvent.CallBack {

    private static View mview;
    private static TextView textview;
    private static Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pump_kin_main);

        /*mview=new View();
        mview.raiseKeyDown();*/

        //res id : 1000001
        textview=new TextView();
        textview.setOnKeyDown(this);
        textview.raiseKeyDown();

        //res id : 1000002
        button=new Button();
        button.setOnKeyDown(this);
        button.raiseKeyDown();

    }

    @Override
    public void onKeyDown(int id) {
        Log.i("pumpkin","button keydown !");
        int resid=id;

        switch (resid){
            case 1000001:
                textview.onDraw();
                break;
            case 1000002:
                button.onDraw();
                break;
        }

    }
}

代碼很簡單,不具體研究了.

package org.pumpkin.pumpkincallback.interfaces;

/**
 * Project name : PumpKinCallBack
 * Created by zhibao.liu on 2016/5/3.
 * Time : 11:19
 * Email [email protected]
 * Action : durian
 */
public class KeyEvent {
    public interface CallBack{
        void onKeyDown(int id);
    }
}


package org.pumpkin.pumpkincallback.view;

import android.util.Log;

/**
 * Project name : PumpKinCallBack
 * Created by zhibao.liu on 2016/5/3.
 * Time : 11:29
 * Email [email protected]
 * Action : durian
 */
public class Button extends TextView {

    public void onDraw(){
        Log.i("pumpkin","button refresh status and ui !");
    }

}


package org.pumpkin.pumpkincallback.view;

import android.util.Log;

import org.pumpkin.pumpkincallback.interfaces.KeyEvent;

/**
 * Project name : PumpKinCallBack
 * Created by zhibao.liu on 2016/5/3.
 * Time : 11:22
 * Email [email protected]
 * Action : durian
 */
public class TextView extends View {

    public TextView(){

        Log.i("pumpkin","textview register ... ");
        //setOnKeyDown(this);

    }

    public void onDraw(){
        Log.i("pumpkin","textview refresh status and ui !");
    }

}


package org.pumpkin.pumpkincallback.view;

import android.util.Log;

import org.pumpkin.pumpkincallback.interfaces.KeyEvent;

/**
 * Project name : PumpKinCallBack
 * Created by zhibao.liu on 2016/5/3.
 * Time : 11:20
 * Email [email protected]
 * Action : durian
 */
public class View implements KeyEvent.CallBack {

    private KeyEvent.CallBack callBack;
    public View(){
        Log.i("pumpkin","view register ... ");
        //setOnKeyDown(this);
    }

    public void raiseKeyDown(){
        callBack.onKeyDown(0);
    }

    public void setOnKeyDown(KeyEvent.CallBack callback){
        Log.i("pumpkin","callback : "+callback);
        callBack=callback;
    }

    @Override
    public void onKeyDown(int id) {

        Log.i("pumpkin","view callback start!");
        //Thread.sleep(2500);
        onDraw();
        Log.i("pumpkin","view callback end!");

    }

    public void onDraw(){
        Log.i("pumpkin","view refresh status and ui !");
    }

}


其中raiseKeyDown是模擬人觸摸產生KeyEvent事件.這樣運行上面的測試程序,就可以發現,Button繼承TextView,然後TextView又是繼承View.View類實現一個CallBack回調.當調用raiseKeyDown模擬KeyEvent消息時,觸發onKeyDown事件,再通過這個事件去更新視圖界面.這個Demo是View最簡單的一個說明模型.

同理也可以實現Drawable.CallBack接口.這裏不再同類模仿了,在後面的程序只給出如何使用這些接口.


然後根據上面的Demo可知,View的視圖更新通過KeyEvent驅動,那麼這裏關於View的構造函數就不過去介紹,基本上和前一篇完全類似.

直接從這裏開始:

public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean result = false;

        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER: {
                if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                    return true;
                }
                // Long clickable items don't necessarily have to be clickable
                if (((mViewFlags & CLICKABLE) == CLICKABLE ||
                        (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
                        (event.getRepeatCount() == 0)) {
                    setPressed(true);
                    checkForLongClick(0);
                    return true;
                }
                break;
            }
        }
        return result;
    }

然後繼續看setPressed方法:

public void setPressed(boolean pressed) {
        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

        if (pressed) {
            mPrivateFlags |= PFLAG_PRESSED;
        } else {
            mPrivateFlags &= ~PFLAG_PRESSED;
        }

        if (needsRefresh) {
            refreshDrawableState();
        }
        dispatchSetPressed(pressed);
    }


然後繼續:默認pressed是有效的流程:

public void refreshDrawableState() {
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        drawableStateChanged();

        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
    }


發現:drawableStateChanged();方法,這個可是Drawable.Callback的其中的一個回調方法:

protected void drawableStateChanged() {
        Drawable d = mBackground;
        if (d != null && d.isStateful()) {
            d.setState(getDrawableState());
        }
    }

上面mBackground即是將要作爲View的背景的,可能是動畫,可能不是.

那麼下面看看mBackground是如何來的:從View構造體開始看,因爲構造體是往往要解析style xml文件信息,同時也就設置了背景:

if (background != null) {
            setBackground(background);
        }


public void setBackground(Drawable background) {
        //noinspection deprecation
        setBackgroundDrawable(background);
    }

主要看書上面的方法都是public的,也就是說設置View背景不僅可以通過xml設置也可以通過程序設置.

至於setBackgroundDrawable這個方法有點長,抽出部分來看看:

if (mBackground != null) {
            mBackground.setCallback(null);
            unscheduleDrawable(mBackground);
        }

上面setCallback是上面的回調的反註冊.下面un***Drawable取消正在更新的背景效果.

background.setCallback(this);
            if (background.isStateful()) {
                background.setState(getDrawableState());
            }

重新註冊回調,並且設置狀態,而這些狀態是什麼,這個狀態是xml中配置的那些如:

android.R.attr.state_pressed;

而整個View需要更新變化,就需要更具這些狀態判斷後進行.


接着後面即把值賦給mBackground:

background.setVisible(getVisibility() == VISIBLE, false);
            mBackground = background;

這樣就可以從上面看出,整個View視圖的更新是KeyEvent事件進行推動,(當然也可以是其他的事件).

後面再根據上面的提供一個demo來進一步說明View背景在點擊效果下是如何更新的.

這樣就更加使人能夠理解通順.如果直接完全看源碼的話,可能只能夠理解,如果能夠根據理解做出demo的話,理解纔會更加深刻.

後面的繼續





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章