Android 实现下拉界面一种方式
需要是按的效果
从屏幕顶端下拉出来一个界面跟随手势滑动
效果如下
先看一下实现之后的window 层级
$ dumpsys window windows
Window #0 Window{2710e08 u0 SystemUI_smallPanel}: mDisplayId=0 rootTaskId=1 mSession=Session{4739268 1310:1000} mClient=android.os.BinderProxy@69125fa mOwnerUid=1000 showForAllUsers=true package=com.android.systemui appop=NONE mAttrs={(0,0)(1440x35) gr=TOP LEFT CENTER sim={adjust=pan} ty=2100 fmt=TRANSLUCENT fl=NOT_FOCUSABLE LAYOUT_IN_SCREEN LAYOUT_NO_LIMITS FULLSCREEN HARDWARE_ACCELERATED pfl=FIT_INSETS_CONTROLLED bhv=SHOW_TRANSIENT_BARS_BY_SWIPE fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR fitSides=}
可以看到有一个高度为35的view,放置在屏幕的顶部,这个就是当前用来响应下拉的一个入口,相对应的参数配置如下
@RequiresApi(api = Build.VERSION_CODES.R) protected WindowManager.LayoutParams createSmallPanelWindowParams(Context context, Display display, int w, int h, int gravity) { WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, GlobalConstants.STATUS_PANEL_WINDOW_TYPE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); params.packageName = context.getPackageName(); params.gravity = gravity; params.width = w; params.height = h; params.setFitInsetsSides(0); params.setTitle("SystemUI_smallPanel"); return params; }
然后对该view 监听触摸事件 onTouch(View v, MotionEvent event)
,把拿到的 event
抛出给到真正需要跟随下拉实现的view 来做对应事件处理
接下来看真正下拉显示的view,也是一开始就已经添加在了window 里面
Window #1 Window{390618f u0 SystemUI_Container}: mDisplayId=0 rootTaskId=1 mSession=Session{4739268 1310:1000} mClient=android.os.BinderProxy@6b08669 mOwnerUid=1000 showForAllUsers=true package=com.android.systemui appop=NONE mAttrs={(0,0)(1440x1920) gr=START CENTER_HORIZONTAL sim={adjust=pan} ty=2100 fmt=TRANSLUCENT fl=DIM_BEHIND NOT_FOCUSABLE LAYOUT_IN_SCREEN LAYOUT_NO_LIMITS FULLSCREEN HARDWARE_ACCELERATED pfl=FIT_INSETS_CONTROLLED bhv=SHOW_TRANSIENT_BARS_BY_SWIPE fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR fitSides=}
然后再这个view 中去处理 上一个传过来的 MotionEvent
public void postOpenTouch(MotionEvent event) { // 对event 各个状态 适配当前view 的滑动就可完成此效果 }
方案小结
能达到系统需要的效果,弊端在于多添加了一个view到window上,有一个MotionEvent
传递的过程,需要把这个步骤处理好,一面出现下拉无响应的情况
其他实现思路
若是能拿到系统的后门InputManager
,直接监听系统input 事件来实现,就能避免以上方案的弊端
大概实现落实如下:
private InputManager mInputManager; private InputMonitor mInputMonitor; public void init() { mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); mInputMonitor = mInputManager.monitorGestureInput( "gesture_monitor", Display.DEFAULT_DISPLAY ); MyMonitor m = new MyMonitor(context, Display.DEFAULT_DISPLAY); mPointerEventDispatcher = new PointerEventDispatcher(mInputMonitor.getInputChannel()); mPointerEventDispatcher.registerInputEventListener(m); }
定义的 MyMonitor
public class MyMonitor implements WindowManagerPolicyConstants.PointerEventListener{ @Override public void onPointerEvent(MotionEvent motionEvent) { // do sth } }
public class PointerEventDispatcher extends InputEventReceiver { private static final String TAG = "PointerEventDispatcher"; private final ArrayList<PointerEventListener> mListeners = new ArrayList<>(); private PointerEventListener[] mListenersArray = new PointerEventListener[0]; public PointerEventDispatcher(InputChannel inputChannel) { super(inputChannel, Looper.getMainLooper()); Log.d(TAG, "PointerEventDispatcher:"); } @Override public void onInputEvent(InputEvent event) { try { if (event instanceof MotionEvent && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { final MotionEvent motionEvent = (MotionEvent) event; PointerEventListener[] listeners; synchronized (mListeners) { if (mListenersArray == null) { mListenersArray = new PointerEventListener[mListeners.size()]; mListeners.toArray(mListenersArray); } listeners = mListenersArray; } for (int i = 0; i < listeners.length; ++i) { listeners[i].onPointerEvent(motionEvent); } } } finally { finishInputEvent(event, false); } } /** * Add the specified listener to the list. * * @param listener The listener to add. */ public void registerInputEventListener(PointerEventListener listener) { Log.d(TAG, "registerInputEventListener: "); synchronized (mListeners) { if (mListeners.contains(listener)) { Log.w(TAG, "registerInputEventListener: trying to register" + listener + " twice."); return; } Log.d(TAG, "register " + listener); mListeners.add(listener); mListenersArray = null; } } /** * Remove the specified listener from the list. * * @param listener The listener to remove. */ public void unregisterInputEventListener(PointerEventListener listener) { synchronized (mListeners) { if (!mListeners.contains(listener)) { Log.w(TAG, "unregisterInputEventListener: " + listener + " not registered."); return; } Log.d(TAG, "unRegister " + listener); // 直接清空 mListeners.clear(); mListenersArray = null; } } /** * Dispose the associated input channel and clean up the listeners. */ @Override public void dispose() { super.dispose(); synchronized (mListeners) { mListeners.clear(); mListenersArray = null; } } }
拿到的 MotionEvent
,就能实现需求所述功能
本文来自博客园,作者:阿丟啊,转载请注明原文链接:https://www.cnblogs.com/qiyuexiaxun/p/17460145.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂