先日、Androidのホームアプリ(ランチャーアプリ)「超らくらくフォン for 爺」を作成しました。その際、こちらの記事を参考にさせて頂きました。ほぼそのまま、使わせてもらっています。本当にありがとうございます!
超シンプルなホームアプリを作る ~ホームアプリ(ランチャーアプリ)の作り方~
ですが、https://qiita.com/ryo_mm2d/items/00326b0d8f088975fa0e私は、なかなか動作させることができませんでした。理由は、
・理解が浅すぎてコードをどこに書けばいいのかよくわからない
・そもそも何をしてるのかよく分かってない
・リサイクルビューを理解してない
というあたりです。そこで、コード全部を載せます。
何をしているか
アプリの内容ですが、「アプリが起動したら端末にインストールされているアプリの一覧を取得して表示し、アプリをタップするとそのアプリを起動」します。以下、詳細です。
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の「ホームアプリ」、簡単なので作ってみてはどうでしょうか。簡単な割りに実用的で面白いです!