Androidのホームアプリ(ランチャーアプリ)の作り方 初心者用

先日、Androidのホームアプリ(ランチャーアプリ)「超らくらくフォン for 爺」を作成しました。その際、こちらの記事を参考にさせて頂きました。ほぼそのまま、使わせてもらっています。本当にありがとうございます!

超シンプルなホームアプリを作る ~ホームアプリ(ランチャーアプリ)の作り方~

ですが、私は、なかなか動作させることができませんでした。理由は、

・理解が浅すぎてコードをどこに書けばいいのかよくわからない

・そもそも何をしてるのかよく分かってない

・リサイクルビューを理解してない

というあたりです。そこで、コード全部を載せます。

何をしているか

アプリの内容ですが、「アプリが起動したら端末にインストールされているアプリの一覧を取得して表示し、アプリをタップするとそのアプリを起動」します。以下、詳細です。

1.アプリを「ホームアプリ」として認識させように作成する。これによってホームアプリの候補として表示されます。そのために、AndroidManifest.xmlの<activity>内の<intent-filter>内に下記を追加します。※Androidマニフェストは、Androidアプリについての定義をしてあるファイルです。

<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />

参考用に、マニフェスト全部はこちら (****は各自の環境に置き換え)。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="****">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.*********">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

                <!-- android.intent.category.HOME をつけると起動時に最初に起動する画面として認識される しかし新しいAndroidでは手動でホームアプリを設定する必要があるようだ -->
                <category android:name="android.intent.category.HOME" />
                <!-- android.intent.category.DEFAULT をつけると暗黙的インテントから起動できる画面として認識される  しかし新しいAndroidでは手動でホームアプリを設定する必要があるようだ -->
                <category android:name="android.intent.category.DEFAULT" />

            </intent-filter>
        </activity>
    </application>

</manifest>

2.起動したら、「android.intent.category.LAUNCHER」に該当するアプリの一覧を取得してデフォルトのレイアウトに一覧表示します(間違ってたらすみません)。

その際、リストとして表示するために、リサイクルビューというオブジェクトを利用します。リスト表示するためのレイアウトとして、「li_application.xml」を使います。

li_application.xml はこちら。※activity_main.xml やli_application.xml は、画面に何をどのように表示するか定義したファイルです

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:foreground="?android:attr/selectableItemBackground"
    >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="ContentDescription"
        tools:srcCompat="@drawable/ic_launcher_background"
        />

    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Title"
        app:layout_constraintBottom_toTopOf="@+id/packageName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toEndOf="@+id/icon"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed"
        tools:text="Launcher"
        />

    <TextView
        android:id="@+id/packageName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toEndOf="@+id/icon"
        app:layout_constraintTop_toBottomOf="@+id/label"
        tools:text="net.mm2d.launcher"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

今回はデフォルトのアクティビティの activity_main に表示するため、activitiy_main.xml にリサイクルビューを定義しています。

activitiy_main.xml はこちら。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1.0" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainAcitivity.kt はこちら。

package ********

import android.content.*
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.widget.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.content.Intent
import android.view.*
import android.os.*
import android.widget.TextView

class MainActivity : AppCompatActivity() {

    lateinit var context : Context

    //ここからスタートする(MainActivityが表示されるとここが走る)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)//activity_mainを表示

        //コンテキストという謎のものを作っておく
        context = applicationContext

        //リサイクルビューを作成(アプリの一覧を表示するためのオブジェクト)
        var recyclerView = findViewById<RecyclerView>(R.id.recyclerView);

        //リサイクルビューのアダプターをセット
        recyclerView.adapter = AppAdapter(layoutInflater, create(this)) { view, info ->
            //リサイクルビューをタップしたとき呼ばれる
            var appInfo : AppInfo? = info.getlabel()
            var s =  appInfo?.componentName.toString()
            //パッケージ名とクラス名を取得
            var packageName = s.substring(s.indexOf("{") + 1,s.indexOf("/"))
            var className = s.substring(s.indexOf("/") + 1,s.indexOf("}"))
            //アプリを起動
            intent.setClassName(packageName,className)
            startActivity(intent)

        }
        //リサイクルビューのレイアウトを指定(これで表示ができるようになった)
        var layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = LinearLayoutManager.VERTICAL
        recyclerView.layoutManager = layoutManager


    }

    // 適当なgetDefaultIcon()関数を追加する。下記は一例。
    private fun getDefaultIcon(context: Context): Drawable {
        return context.resources.getDrawable(R.mipmap.ic_launcher, null)
    }

    fun create(context: Context): List<AppInfo> {
        val pm = context.packageManager
        val intent = Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_LAUNCHER) }
        return pm.queryIntentActivities(intent, PackageManager.MATCH_ALL)
            .asSequence()
            .mapNotNull { it.activityInfo }
            //.filter { it.packageName != context.packageName }
            .map {
                AppInfo(
                    it.loadIcon(pm) ?: getDefaultIcon(context),
                    //Unresolved reference: getDefaultIcon
                    it.loadLabel(pm).toString(),
                    ComponentName(it.packageName, it.name)
                )
            }
            .sortedBy { it.label }
            .toList()
    }

    //activityが終了しないようにする(ホームアプリは終了してほしくないので)
    override fun finish() {
    }
}

data class AppInfo(
    val icon: Drawable,
    val label: String,
    val componentName: ComponentName,
) {
    fun launch(context: Context) {
        try {
            val intent = Intent(Intent.ACTION_MAIN).also {
                it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                it.addCategory(Intent.CATEGORY_LAUNCHER)
                it.component = componentName
            }
            context.startActivity(intent)
        } catch (e: ActivityNotFoundException) {
            Log.e("E", e.toString());
        }
    }
    fun getlabel(): AppInfo {
        return this;
    }
}

class AppAdapter(
    private val inflater: LayoutInflater,
    private val list: List<AppInfo>,
    private val onClick: (view: View, info: AppInfo) -> Unit,
) : RecyclerView.Adapter<AppAdapter.AppViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder =
        AppViewHolder(inflater.inflate(R.layout.li_application, parent, false))

    override fun getItemCount(): Int = list.size

    override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
        val info = list[position]
        holder.itemView.setOnClickListener { onClick(it, info) }
        holder.icon.setImageDrawable(info.icon)
        holder.label.text = info.label
        holder.packageName.text = info.componentName.packageName
    }

    class AppViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val icon: ImageView = itemView.findViewById(R.id.icon)
        val label: TextView = itemView.findViewById(R.id.label)
        val packageName: TextView = itemView.findViewById(R.id.packageName)
    }
}

以上のように、MainAcitivity.kt 、activitiy_main.xml、AndroidManifest.xml をコピペし、li_appliciton.xml をapp>src>main>res>layout 内に作成すれば、動くと思います。コメントで雰囲気は理解できると思います。

Androidの「ホームアプリ」、簡単なので作ってみてはどうでしょうか。簡単な割りに実用的で面白いです!

タイトルとURLをコピーしました