【问题标题】:What is a better way to implement a system wide side panel (similar to navigation drawer) in Android在Android中实现系统宽侧面板(类似于导航抽屉)的更好方法是什么
【发布时间】:2014-11-01 12:31:12
【问题描述】:

我的目标:当用户从任何屏幕的左侧滑入时,让页面滑到屏幕上。

我目前的解决方案:

  1. 我启动我的服务 (GlobalTouchService)
  2. 使用 OnTouchListener 创建窄透明视图 (floatingView)
  3. 然后使用 WindowManager.addView 将此视图添加到屏幕的一侧
  4. 在 MotionEvent.ACTION_DOWN 上,我将膨胀的布局添加到此视图(我称之为 innerView)
  5. 在MotionEvent.ACTION_MOVE上我通过event.getRawX()坐标调整窗口的宽度和innerView的左右边距

问题:它可以工作,但它的动作非常生涩(缓慢)。我确信有更好的方法可以让它像“官方”导航抽屉一样流畅。

源文件片段

AndroidManifest.xml (系统范围的触摸检测所需的权限)

        <service android:name=".GlobalTouchService" />
</application>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

panel_layout.xml (这是要拖入的窗口内容)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#9900cc"
    android:orientation="vertical"
    android:gravity="right"
    tools:context=".GlobalTouchService" >
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/dummy_content2" />
</LinearLayout>

GlobalTouchService.java (我在这里包含了整个服务)

public class GlobalTouchService extends Service implements OnTouchListener{

    private String TAG = this.getClass().getSimpleName();
    private WindowManager mWindowManager;
    private LinearLayout floatingView;
    private LinearLayout innerView;
    private LinearLayout.LayoutParams floatingParams;
    private LinearLayout.LayoutParams innerParams;
    private WindowManager.LayoutParams windowParams;
    private int dw,dh;

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {

        super.onCreate();

        // get screen dimensions

        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);  
        dw=mWindowManager.getDefaultDisplay().getWidth();
        dh=mWindowManager.getDefaultDisplay().getHeight();

        // prepare contents to show when ACTION_DOWN is detected

        innerView =new LinearLayout(this);
        innerParams =new LinearLayout.LayoutParams(dw,dh);
        innerParams.gravity=Gravity.RIGHT | Gravity.TOP;
        innerView.setLayoutParams(innerParams);
        View view=View.inflate(this, R.layout.panel_layout, new FrameLayout(this));
        innerView.addView(view);

        // create blank view to catch touches

        floatingView = new LinearLayout(this);
        floatingParams = new LinearLayout.LayoutParams(dw, dh);
        floatingView.setLayoutParams(floatingParams);    
        floatingView.setOnTouchListener(this);

        // add view to the windowmanager

        windowParams = new WindowManager.LayoutParams(
                dw/30,                                            // narrow stripe on the left
                dh,                                               // full height of the screen
                WindowManager.LayoutParams.TYPE_PHONE, 
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, 
                PixelFormat.TRANSLUCENT);
        windowParams.gravity = Gravity.LEFT | Gravity.TOP;
        mWindowManager.addView(floatingView, windowParams);
    }

    @Override
    public void onDestroy() {
        if(mWindowManager != null) {

            // remove window when service is finished

            if(floatingView != null) mWindowManager.removeView(floatingView);
        }
        super.onDestroy();
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        if(event.getAction() == MotionEvent.ACTION_DOWN && windowParams.width<dw/3 ) {
            // add the inflated contents
            floatingView.addView(innerView);
            return true;

        } else if(event.getAction() == MotionEvent.ACTION_UP && event.getRawX()<dw/3) {
            // if released early collapses back to the left edge
            windowParams.width=dw/30;
            floatingView.removeView(innerView);
            mWindowManager.updateViewLayout(floatingView, windowParams);
            return true;

        } if(event.getAction()==MotionEvent.ACTION_MOVE) {
            windowParams.width=Math.max((int)event.getRawX(),dw/30);
            innerParams.leftMargin=-(dw-(int)event.getRawX());
            innerParams.rightMargin=(dw-(int)event.getRawX());
            mWindowManager.updateViewLayout(floatingView, windowParams);
            return true;
        }
        return false;
    }

}

【问题讨论】:

  • 那你为什么不使用 NavigationDrawer?
  • 我需要通过 Service 执行此操作(并且在佩戴时让事情变得更加有趣)我正在开发一个应该可以从任何屏幕访问的应用程序从边缘滑动)
  • 穿着?它真的不符合穿戴 UI 理念......
  • 你的代码也不能像广告宣传的那样工作......
  • 我完全不明白你为什么要使用innerParams 和windowParams?为什么要更新它们的宽度/leftMargin/rightMargin?它与滚动根窗口有什么关系?

标签: android overlay panel motionevent drawer


【解决方案1】:

试试这个(奖励包是ValueAnimator,如果您不需要在ACTION_UP 上自动滚动,可以将其删除):

public class MyService extends Service implements View.OnTouchListener, ValueAnimator.AnimatorUpdateListener {
    private static final int MIN_VISIBLE_WIDTH = 20;

    private WindowManager windowManager;
    private FrameLayout rootView;
    private Point displaySize = new Point();
    private WindowManager.LayoutParams params;
    private ValueAnimator animator = new ValueAnimator();

    @Override
    public void onCreate() {
        animator.addUpdateListener(this);
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getSize(displaySize);

        rootView = new FrameLayout(this);
        TextView tv = new TextView(this);
        tv.setGravity(Gravity.END | Gravity.CENTER_VERTICAL);
        int[] colors = {0xbb00ff00, 0x3300ff00};
        tv.setBackground(new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, colors));
        tv.setPadding(0, 0, 16, 0);
        tv.setText("text view");
        rootView.addView(tv);

        params = new WindowManager.LayoutParams(
            displaySize.x, displaySize.y / 4,       // width, height
            -displaySize.x + MIN_VISIBLE_WIDTH, 20, // left, top
            WindowManager.LayoutParams.TYPE_PHONE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
            PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.TOP | Gravity.LEFT;
        windowManager.addView(rootView, params);

        rootView.setOnTouchListener(this);
    }

    @Override
    public void onDestroy() {
        windowManager.removeView(rootView);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    float actionDownX;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            animator.cancel();
            actionDownX = event.getX();
        } else
        if (action == MotionEvent.ACTION_MOVE) {
            int newLeft = (int) (event.getRawX() - actionDownX);
            params.x = Math.max(-displaySize.x + MIN_VISIBLE_WIDTH, Math.min(newLeft, 0));
            windowManager.updateViewLayout(rootView, params);
        } else
        if (action == MotionEvent.ACTION_UP) {
            int startX = params.x;
            int endX = startX < (-displaySize.x + MIN_VISIBLE_WIDTH) / 2? -displaySize.x + MIN_VISIBLE_WIDTH : 0;
            animator.setIntValues(startX, endX);
            animator.setDuration(500 * Math.abs(endX - startX) / ((displaySize.x - MIN_VISIBLE_WIDTH) / 2))
                .start();
        }
        return true;
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        params.x = (Integer) animation.getAnimatedValue();
        windowManager.updateViewLayout(rootView, params);
    }
}

【讨论】:

  • 哇,谢谢,太完美了!我将不得不再看一会儿才能完全理解这里的所有内容,但是我一直在漫游 SO,Github 上上下下寻找这样的解决方案。很抱歉,我还没有声望投票 :-)
  • 这里的关键是我在文档中错过的标志 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS。这让我陷入了所有不必要的布局堆叠。另一个当然是动画师的使用。
  • Animator 的使用非常基础,你可以添加一些更复杂的行为,例如当视图最初被隐藏时,当它的可见度为 25% 或更多时打开,当它完全可见时,当它在 75% 或更少时可见时关闭它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-18
  • 1970-01-01
  • 1970-01-01
  • 2017-12-10
  • 1970-01-01
  • 2021-10-21
相关资源
最近更新 更多