LoginSignup
5
2

More than 5 years have passed since last update.

爆速でfloat menuを作ってみた

Last updated at Posted at 2018-08-06

爆速でこんな感じなものを作った
Untitled.gif
Serviceから動けるアイコンを作ってみた

とりあえずしておきたいこと

androidで表示しているレイヤー

android 8.0以下ならTYPE_PHONEを使っているけど、android 8.0以上ならTYPE_PHONETYPE_PRIORITY_PHONEが非推奨になったのでTYPE_APPLICATION_OVERLAYを使う

Permissionをちゃんと取る必要がある

他のアプリの上で表示するにはACTION_MANAGE_OVERLAY_PERMISSIONでユーザーの許可を取る必要がある。
今ユーザーが許可しているかどうかはSettings.canDrawOverlays(context)で確認するとが出来る。

android 8.0以上でbackground serviceを始まる時要注意

android 8.0でstartForegroundService(Intent)サービス開始する時onStartCommandでやる時にstartForeground(Int, Notification)をやらないと、serviceが強制終了させるので、要注意。
公式のドキュメントはこちら
https://developer.android.com/about/versions/oreo/background?hl=ja#services

主なコード

Fragment

MainFragment.kt
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        super.onCreateView(inflater, container, savedInstanceState)
        //Permissionチェック
        if (!hasDrawOverlaysPermission()) {
            requestPermission()
        } 
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        var count = 0
        when (requestCode) {
            OVERLAY_PERMISSION_REQUEST_CODE -> {
                // 「他アプリの上に表示する」設定の取得が画面復帰直後には取得できない為、1秒間(100ms x 10回)設定の変更を監視する
                handler.post(object: Runnable {
                    override fun run() {
                        if (count > 10 || hasDrawOverlaysPermission()) {
                            startService()
                        } else {
                            count++
                            handler.postDelayed(this, 100)
                        }
                    }
                })
            }
        }
    }

    private fun hasDrawOverlaysPermission(): Boolean {
        return Settings.canDrawOverlays(context)
    }

    private fun requestPermission() {
        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse("package:${context?.packageName}"))
        startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE)
    }

    private fun startService() {

        val intent = Intent(context, FloatService::class.java)
        // Serviceの開始
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context?.startForegroundService(intent)
        } else {
            context?.startService(intent)
        }
    }

Service

FloatMenuService.kt

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        createNotification(intent)
        createFloatMenuView()
        return super.onStartCommand(intent, flags, startId)
    }

    private fun createNotification(intent: Intent) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channelId = "好きなchannelId"
            val notificationTitle = "タイトル"

            var notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            var pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

            notificationManager.createNotificationChannel(NotificationChannel(channelId, title, NotificationManager.IMPORTANCE_DEFAULT))
            var notification = Notification.Builder(applicationContext, channelId)
                    .setContentTitle(notificationTitle)
                    .setSmallIcon(R.drawable.ic_stat_name)//image assetで生成出来る、Adaptive iconを使うとSystem UIがクラッシュする
                    .setContentText("notificationの本文")
                    .setAutoCancel(true)
                    .setContentIntent(pendingIntent)
                    .setWhen(System.currentTimeMillis())
                    .build()

            startForeground(ONGOING_NOTIFICATION_ID, notification)//ONGOING_NOTIFICATION_IDは0が使えない
        }
    }

    private fun createFloatMenuView() {
        windowManager = applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        windowManagerParams = WindowManager.LayoutParams()
        layoutInflater = LayoutInflater.from(this)

        windowManagerParams.let {
            //レイヤー設定
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                it.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                it.type = WindowManager.LayoutParams.TYPE_PHONE
            }
            it.format = PixelFormat.RGBA_8888
            it.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            it.gravity = Gravity.LEFT or Gravity.TOP
            it.x = 0
            it.y = 0
            it.width = WindowManager.LayoutParams.WRAP_CONTENT
            it.height = WindowManager.LayoutParams.WRAP_CONTENT
        }

        floatMenuLayout = layoutInflater.inflate(R.layout.float_menu_layout, null)
        windowManager?.addView(floatMenuLayout, windowManagerParams)
        floatMenuView = floatMenuLayout.findViewById(R.id.float_menu_button) as ImageButton

        floatMenuLayout.measure(View.MeasureSpec.makeMeasureSpec(0,
                View.MeasureSpec.UNSPECIFIED), View.MeasureSpec
                .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
        //floatMenuの座標移動
        floatMenuView.setOnTouchListener { _, event ->
            windowManagerParams.x = event.rawX.toInt() - floatMenuView.measuredWidth / 2
            windowManagerParams.y = event.rawY.toInt() - floatMenuView.measuredHeight / 2
            mWindowManager?.updateViewLayout(floatMenuLayout, windowManagerParams)
            false
        }

        floatMenuView.setOnClickListener {
            //タップした時の処理
        }
    }

float menuのXML

float_menu_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageButton
        android:id="@+id/float_menu_button"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical"
        android:gravity="center_horizontal"
        android:src="@mipmap/ic_launcher" />

</android.support.constraint.ConstraintLayout>
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2