Android imitation wechat article suspension window effect

Android suspension Window implementation (including 8.0 permission adaptation)

The preface

Chatting with a friend the other day, friends Z fruit powder, recently updated WeChat, WeChat tell a good handy feature, I ask is what ah, look at my big Android ever, he said now article reading public if someone give you send WeChat messages you can treat this article as floating window is suspended, you can talk the day don’t have to look to continue reading, When I read the article, I saw this hover button, but I have never used it. It is very convenient to try it, so I want to implement this function myself. Here is the picture, we are used to saying X without pictures

The principle of

After looking at the GIF, let’s analyze how to have a View on every page. Some people might say, write it in base, so that every time you start a new Activity, you have to add a View to the page, which is bad performance. Besides, can we as good programmers do this kind of repetitive thing? This kind of plan decisively fight back; In that case we definitely have to add to the global, so where is the global? I believe that friends who have understood the source code of Activity must know that the global can be added in the Window layer, so that it can be done at once, and does not affect the performance, dry dry.

implementation

1, permissions,

First of all, we need to consider a problem is the permission problem, because to adapt Android 7.0 8.0, add suspension window is required to apply for permission, here reference Android suspension window permission for various models of various systems adaptation 全 文, adaptation can be directly used. Note that in order to adapt to Android 8.0, the Window type needs to be configured:

If (build.version.sdk_int >= build.version_codes.o) {//Android 8.0mlayoutparams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else {/ / other version mLayoutParams. Type = WindowManager. LayoutParams. TYPE_PHONE; }Copy the code

Add ViewGroup to Window

After determining the permissions, add them directly

@SuppressLint("CheckResult") private void showWindow(Context context) { mWindowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE); mView = LayoutInflater.from(context).inflate(R.layout.article_window, null); ImageView ivImage = mView.findViewById(R.id.aw_iv_image); String imageUrl = SPUtil.getStringDefault(ARTICLE_IMAGE_URL, ""); RequestOptions requestOptions = RequestOptions.circleCropTransform(); requestOptions.placeholder(R.mipmap.ic_launcher_round).error(R.mipmap.ic_launcher_round); Glide.with(context).load(imageUrl).apply(requestOptions).into(ivImage); initListener(context); mLayoutParams = new WindowManager.LayoutParams(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; } mLayoutParams.format = PixelFormat.RGBA_8888; / / window transparent mLayoutParams. Gravity = gravity. LEFT | gravity. The TOP; / / window position mLayoutParams. Flags = WindowManager. LayoutParams. FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mLayoutParams.width = 200; mLayoutParams.height = 200; mLayoutParams.x = mWindowManager.getDefaultDisplay().getWidth() - 200; mLayoutParams.y = 0; mWindowManager.addView(mView, mLayoutParams); }Copy the code

3, View drag and drop implementation

With the help of a WindowManager. LayoutParams, mLayoutParams. X and mLayoutParams. Y respectively mView horizontal ordinate in the upper left corner, so we just need to change the two values, when ACTION_UP, Calculate the position of the center point of the current mView relative to the window, and then dynamically slide the mView to the left or right of the window:

// Set the touch slider event mview.setonTouchListener (new view.onTouchListener () {int startX, startY; // Start point Boolean isMove; // Whether to move long startTime; int finalMoveX; @override public Boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = (int) event.getX(); startY = (int) event.getY(); startTime = System.currentTimeMillis(); isMove = false; return false; case MotionEvent.ACTION_MOVE: mLayoutParams.x = (int) (event.getRawX() - startX); mLayoutParams.y = (int) (event.getRawY() - startY); updateViewLayout(); // Update mView position return true; case MotionEvent.ACTION_UP: long curTime = System.currentTimeMillis(); isMove = curTime - startTime > 100; // Determine the position of mView in the Window, By a middle the if (mLayoutParams. X + mView. GetMeasuredWidth () / 2 > = mWindowManager getDefaultDisplay () getWidth () / 2) { finalMoveX = mWindowManager.getDefaultDisplay().getWidth() - mView.getMeasuredWidth(); } else { finalMoveX = 0; Animator = ValueAnimator.ofint (mlayOutparams.x, finalMoveX).setDuration(Math.abs(mLayoutParams.x - finalMoveX)); animator.addUpdateListener((ValueAnimator animation) -> { mLayoutParams.x = (int) animation.getAnimatedValue(); updateViewLayout(); }); animator.start(); return isMove; } return false; }});Copy the code

4, pay attention to

To separate Windows from activities, we use a Service to add and remove views. We need to notify the Service (an interface to the Service) after the permission request is successful, and then use broadcasts within the Service to notify the Activity. Finally a place need to be aware of is that we need to decide whether the application in the foreground or background to add or remove Windows, by using ActivityLifecycleCallbacks here to monitor the Activity at the front desk to determine the number of application is in the foreground or background

class ApplicationLifecycle : Application.ActivityLifecycleCallbacks { private var started: Int = 0 override fun onActivityPaused(activity: Activity?) { } override fun onActivityResumed(activity: Activity?) { } override fun onActivityStarted(activity: Activity?) {started++ if (started == 1) {log. e("TAG", "application in foreground!!" ) } } override fun onActivityDestroyed(activity: Activity?) { } override fun onActivitySaveInstanceState(activity: Activity? , outState: Bundle?) { } override fun onActivityStopped(activity: Activity?) {started-- if (started == 0) {log. e("TAG", "application in background!!" ) } } override fun onActivityCreated(activity: Activity? , savedInstanceState: Bundle?) {}}Copy the code

reference

It is highly recommended

FRDialog, a dialog created by Builder mode, there are three types of Builder in the case, namely CommonBuilder, MDBuilder and RecyclerViewBuilder, if you want to achieve other common, Inherit from frbase EdialogBuilder.

Project address: custom universal FRDialog