上一篇大致介绍了如何更新主题风格之类的,没有谈及到一些本质的,这一篇大致介绍一下所有的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 warden_sprite@foxmail.com
* 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 warden_sprite@foxmail.com
* 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 warden_sprite@foxmail.com
* 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 warden_sprite@foxmail.com
* 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的话,理解才会更加深刻.
后面的继续