Slide 1

Slide 1 text

ERIC COCHRAN WHOA, VIEWS CAN DO THAT? WINDOWMANAGER IDEAS AND TRICKS! @ERIC_COCHRAN DROIDCON NYC AUGUST 28, 2015

Slide 2

Slide 2 text

BEYOND SCREENS • VIEWS THAT ARE NOT ATTACHED TO AN ACTIVITY’S WINDOW • REAL, FULL-FLEDGED VIEWS

Slide 3

Slide 3 text

PERMISSION Android M: hidden setting if (!Settings.canDrawOverlays(this)) {
 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
 Uri.parse("package:" + activity.getPackageName()));
 activity.startActivityForResult(intent, REQUEST_CODE_PERMISION_SYSTEM_ALERT_WINDOW);
 } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 if (requestCode == REQUEST_CODE_PERMISION_SYSTEM_ALERT_WINDOW) {
 if (Settings.canDrawOverlays(this)) {
 // yay!
 } else {
 // denied
 }
 }
 }

Slide 4

Slide 4 text

ADDING THE VIEWS 
 WindowManager windowManager =
 (WindowManager) context.getSystemService(WINDOW_SERVICE);
 View floatingView = new View(context);
 // background to see View
 floatingView.setBackgroundColor(Color.RED);
 WindowManager.LayoutParams floatingLayoutParams =
 new WindowManager.LayoutParams(floatingViewWidth, floatingViewHeight,
 WindowManager.LayoutParams.TYPE_PHONE,
 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
 PixelFormat.TRANSPARENT);
 floatingLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
 windowManager.addView(floatingView, floatingLayoutParams);

Slide 5

Slide 5 text

FLAG_NOT_FOCUSABLE Views will not receive focus. No EditTexts. WI FLAG_NOT_TOUCHABLE No touch listeners will work. FLAG_NOT_TOUCH_MODAL Will allow touch events to pass to outside Views FLAG_LAYOUT_NO_LIMITS Views are free to move offscreen. FLAG_LAYOUT_IN_SCREEN Keep Views bounded by the screen’s dimensions.

Slide 6

Slide 6 text

TO A SERVICE @Override public int onStartCommand(Intent intent, int flags, int startId) {
 // addView code
 Notification notification = // valid notification
 startForeground(id, notification);
 return START_STICKY;
 }

Slide 7

Slide 7 text

GET MOVING floatingView.setOnTouchListener(new View.OnTouchListener() {
 
 private int downX;
 private int downY;
 private float touchDownX;
 private float touchDownY;
 
 @Override public boolean onTouch(View v, MotionEvent event) {
 
 switch (event.getActionMasked()) {
 case MotionEvent.ACTION_DOWN:
 downX = floatingLayoutParams.x;
 downY = floatingLayoutParams.y;
 touchDownX = event.getRawX();
 touchDownY = event.getRawY();
 break;
 case MotionEvent.ACTION_MOVE:
 floatingLayoutParams.x = (int) (downX + event.getRawX() - touchDownX);
 floatingLayoutParams.y = (int) (downY + event.getRawY() - touchDownY);
 windowManager.updateViewLayout(floatingView, floatingLayoutParams);
 break;
 default:
 break;
 }
 return true;
 }
 });

Slide 8

Slide 8 text

android:clipChildren="false"

Slide 9

Slide 9 text

GOTCHAS — MORE ISSUES • ALPHA, SCALE, AND TRANSFORMATIONS DO NOT WORK PRE-LOLLIPOP

Slide 10

Slide 10 text

SPOILER • ADD A PARENT • VIEW PROPERTIES WILL WORK ON THE PARENTED VIEW

Slide 11

Slide 11 text

SCALING — STILL CLIPPED TWO TECHNIQUES TO SOLVE THE ISSUE

Slide 12

Slide 12 text

FIRST IDEA (NOT-SO-GREAT) • MAKE THE ROOT VIEW THAT IS ATTACHED TO THE WINDOW MANAGER ALWAYS LARGE ENOUGH TO ACCOMMODATE THE MAXIMUM SCALE THAT THE CORE VIEW WILL EVER ACHIEVE. • ADD A MARGIN TO THE CORE VIEW THAT FILLS THE SPACE. • WHEN THE CORE VIEW SCALES, THE MARGIN WILL BE THE ONLY PART CLIPPED.

Slide 13

Slide 13 text

• DISADVANTAGE OF A LARGER ROOT VIEW THAT WILL NOW BLOCK MORE UI FROM THE USER. • WORSE, THIS ADDED MARGIN IS INVISIBLE AND COULD LEAD TO THE USER NOT UNDERSTANDING WHY OUR FLOATING VIEW IS INTERCEPTING TOUCH EVENTS OUTSIDE OF THE VISIBLE UI. FIRST IDEA (NOT-SO-GREAT)

Slide 14

Slide 14 text

FIRST IDEA (NOT-SO-GREAT) GRAY: the root view’s invisible padding RED: visible parented view that should scale

Slide 15

Slide 15 text

FIRST IDEA (NOT-SO-GREAT) • ROOT VIEW ACCOUNTS FOR THE CORE VIEW (THE RED DOT) SCALING TO TWICE AS LARGE • BUT, THE ROOT VIEW IS NOW OBSTRUCTING OTHER TOUCH EVENTS WITHOUT THE USER REALIZING IT.

Slide 16

Slide 16 text

• DYNAMICALLY SCALE THE ROOT VIEW BETTER METHOD 
 floatingLayoutParams.width *= scaleFactorX;
 floatingLayoutParams.height *= scaleFactorY;
 windowManager.updateViewLayout(floatingView, floatingLayoutParams);

Slide 17

Slide 17 text

BETTER IDEA • WE HAVE JUST SCALED THE WIDTH AND THE HEIGHT WHILE LEAVING THE X AND Y FIELDS IN PLACE. PROBABLY NOT IDEAL. • TO SCALE RADIALLY: 
 floatingLayoutParams.x -= (int) ((scaleFactorX - 1f) * floatingLayoutParams.width / 2f);
 floatingLayoutParams.y -= (int) ((scaleFactorY - 1f) * floatingLayoutParams.height / 2f);

Slide 18

Slide 18 text

REMOVE WINDOW VIEWS • REMOVE VIEWS WHERE APPROPRIATE TO PREVENT LEAKS • (PROBABLY IN ONDESTROY() OF A SERVICE)

Slide 19

Slide 19 text

• DEFINE YOUR BOUNDS • WITH INSETS, CHECK AGAINST BOUNDS BEFORE MOVING IN TOUCH LISTENER • VIEW.GETLOCATIONONSCREEN • ADD A VELOCITY TRACKER TIPS

Slide 20

Slide 20 text

GO TRY IT • MAKE YOUR APP UNIQUELY ANDROID