问题
My aim: have a Page slide onto the screen when the user swipes in from the left side of any screen.
My current solution:
- I start my Service (GlobalTouchService)
- which creates a narrow transparent view (floatingView) with an OnTouchListener
- this view is then added to side of the screen using WindowManager.addView
- on MotionEvent.ACTION_DOWN I add the inflated layout to this View (I call it innerView)
- 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