Android 实现下拉界面一种方式

需要是按的效果
从屏幕顶端下拉出来一个界面跟随手势滑动

效果如下
image

先看一下实现之后的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 ,就能实现需求所述功能

posted @ 2023-06-06 11:47  阿丟啊  阅读(450)  评论(1编辑  收藏  举报