What is a better way to implement a system wide side panel (similar to navigation drawer) in Android

允我心安 提交于 2019-12-08 12:20:58

问题


My aim: have a Page slide onto the screen when the user swipes in from the left side of any screen.

My current solution:

  1. I start my Service (GlobalTouchService)
  2. which creates a narrow transparent view (floatingView) with an OnTouchListener
  3. this view is then added to side of the screen using WindowManager.addView
  4. on MotionEvent.ACTION_DOWN I add the inflated layout to this View (I call it innerView)
  5. on MotionEvent.ACTION_MOVE I adjust the width of the window and the left and right margins of the innerView by the event.getRawX() coordinate

Problem: It works, but its movement is very jerky (slow). I am sure there is a much better way of doing this to make it as smooth as an "official" navigation drawer.

Source file snipets

AndroidManifest.xml (permission necessary for system wide touch detection)

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

panel_layout.xml (this is the contents of the window that's to be dragged in)

<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 (I include the whole service here)

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;
    }

}

回答1:


try this (a bonus pack is the ValueAnimator, you can remove it if you don't need automatic scrolling on 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);
    }
}


来源:https://stackoverflow.com/questions/26689330/what-is-a-better-way-to-implement-a-system-wide-side-panel-similar-to-navigatio

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!