浙江网站搭建网络服务器配置与管理
背景:
在使用android手机时候,大家壁纸可能一般都是使用的静态壁纸,静态壁纸一般就是设置一张静态,这种静态壁纸因为是固定的一张图片,所以对壁纸触摸交互这块需求比较少,但是如果设置的是一个动态壁纸,那么这个触摸交互需求就会大大增加。具体可以看下面的一个整体旋转的动态壁纸触摸场景:
明显可以看到这里的有一个正方体一直在旋转着,在鼠标触摸桌面时候,不仅仅桌面有响应事件,也发现Wallpaper也会绘制一个触摸点为圆心的圆,那么下面就来剖析一下Wallpaper壁纸窗口为啥可以实现触摸事件的接收原理,下面是桌面和壁纸窗口图层的位置,桌面肯定是位于壁纸上面的。
学习了马哥input课程就知道,正常逻辑的话上面有图层覆盖而且可以接受事件的话,那么底部的窗口肯定无法接受事件,但是上面看到动态壁纸情况下,明显可以看到桌面响应了触摸事件,壁纸也有响应,这个事件透传我们下一篇来分析input汇总原理,本节先来剖析上层WallpaperService的事件接受。
剖析动态壁纸事件接收代码
动态壁纸要接收触摸事件,还是需要一些额外设置的,具体的设置方法如下:
@Overridepublic void onCreate(SurfaceHolder surfaceHolder) {super.onCreate(surfaceHolder);// By default we don't get touch events, so enable them.setTouchEventsEnabled(true);}
一般动态壁纸要继承WallpaperService时候会重写onCreate,在onCreate中需要调用 setTouchEventsEnabled(true)让壁纸服务可以接受对应的触摸事件,如果设置为true,默认是不可以收到触摸事件。
上面设置完成后就可以让系统把触摸事件传递到WallpaperSerivce了,具体哪个方法接收呢?
这里其实WallpaperSericce的内部类Engine也有个onTouchEvent方法,它可以实现对触摸事件的接受
一般会重写这个方法进行相关的事件处理,比如上面正方体动态壁纸的处理如下:
/** Store the position of the touch event so we can use it for drawing later*/@Overridepublic void onTouchEvent(MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_MOVE) {mTouchX = event.getX();mTouchY = event.getY();} else {mTouchX = -1;mTouchY = -1;}super.onTouchEvent(event);}
这里的处理只是记录了一下坐标,记录坐标目的是为了在正方体绘制时候用这个坐标来绘制一个圆:
/** Draw one frame of the animation. This method gets called repeatedly* by posting a delayed Runnable. You can do any drawing you want in* here. This example draws a wireframe cube.*/void drawFrame() {final SurfaceHolder holder = getSurfaceHolder();Canvas c = null;try {c = holder.lockCanvas();if (c != null) {// draw somethingdrawCube(c);drawTouchPoint(c);//绘制触摸点的圆}} finally {if (c != null) holder.unlockCanvasAndPost(c);}// Reschedule the next redrawmHandler.removeCallbacks(mDrawCube);if (mVisible) {mHandler.postDelayed(mDrawCube, 1000 / 25);}}
上面就是靠drawTouchPoint方法来绘制触摸点的圆:
/** Draw a circle around the current touch point, if any.*/void drawTouchPoint(Canvas c) {if (mTouchX >=0 && mTouchY >= 0) {c.drawCircle(mTouchX, mTouchY, 80, mPaint);}}
整体使用也是非常简单,那么下面来看看这块WallpaperSerice可以接受触摸事件的一个初步原理。
WallpaperService触摸事件接收原理
先从 setTouchEventsEnabled(true)作为切入点开始剖析
/*** Control whether this wallpaper will receive raw touch events* from the window manager as the user interacts with the window* that is currently displaying the wallpaper. By default they* are turned off. If enabled, the events will be received in* {@link #onTouchEvent(MotionEvent)}.*/public void setTouchEventsEnabled(boolean enabled) {mWindowFlags = enabled? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE): (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);if (mCreated) {updateSurface(false, false, false);}}
可以看到这里主要是对mWindowFlags进行了设置,如果enable就是不进行设置FLAG_NOT_TOUCHABLE,可以通过dump确认。
dumpsys window windows
默认的静态壁纸,不可以触摸
自定义动态壁纸,可以触摸,就没有flag为NOT_TOUCHABLE
那么setTouchEventsEnabled(true)后,事件又是怎么到传递到WallpaperService的呢?
可以看看updateSurface方法,在窗口添加时候创建好对应的InputChannel
所以上面就可以看得出来无论是否WallpaperService是否enable Touch,都会创建好对应的InputChannel即在InputDispatcher中都会有对应的InputWindow,只不过这个时候窗口InputWindow因为设置了NOT_TOUCHABLE导致不会进行派发到这个窗口。
可以通过dumpsys input来看看静态壁纸情况:
dumpsys input
再看看事件的怎么到的WallpaperSerice的onTouchEvent
先看看WallpaperInputEventReceiver接收
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
final class WallpaperInputEventReceiver extends InputEventReceiver {public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}@Overridepublic void onInputEvent(InputEvent event) {boolean handled = false;try {if (event instanceof MotionEvent&& (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event);dispatchPointer(dup);//这里会调用dispatchPointer派发handled = true;}} finally {finishInputEvent(event, handled);}}}
再看看dispatchPointer
private void dispatchPointer(MotionEvent event) {if (event.isTouchEvent()) {synchronized (mLock) {if (event.getAction() == MotionEvent.ACTION_MOVE) {mPendingMove = event;} else {mPendingMove = null;}}Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);//主要是发送了msgmCaller.sendMessage(msg);} else {event.recycle();}}
再看看MSG_TOUCH_EVENT的消息处理:
case MSG_TOUCH_EVENT: {boolean skip = false;MotionEvent ev = (MotionEvent)message.obj;if (ev.getAction() == MotionEvent.ACTION_MOVE) {synchronized (mEngine.mLock) {if (mEngine.mPendingMove == ev) {mEngine.mPendingMove = null;} else {// this is not the motion event we are looking for....skip = true;}}}if (!skip) {if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);mEngine.onTouchEvent(ev);//调用到mEngine的onTouchEvent}ev.recycle();
上面就展示了,最后事件到了WallpaperSerice内部类Engine的onTouchEvent方法。
具体事件是如何可以穿透壁纸上面窗口图层Launcher的,下一篇文章再给大家分析。
壁纸触摸事件总结:
1、壁纸窗口本身在创建时候就已经有创建好对应的InputChannel,而且也会和正常窗口一样会有InputWindow在InputDispatcher,而且位置一般处于最底层
2、要让壁纸可以接收事件,需要调用 setTouchEventsEnabled(true),主要是把Window不进行设置NOT_TOUCHABLE的flag。
3、事件在InputDispatcher派发时候,可以透过Launcher传递到Wallpaper的窗口,但如果有NOT_TOUCHABLE的flag那么就不会传递,具体如何可以透传下一篇分析。
更多framework实战开发干货,请关注下面“千里马学框架”