上一篇大致介紹瞭如何更新主題風格之類的,沒有談及到一些本質的,這一篇大致介紹一下所有的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的話,理解纔會更加深刻.
後面的繼續