Android JetPack Compose では、Navigation コンポーネントを使用してナビゲーションを実装できます
1. Navigation コンポーネントの構成
新しいプロジェクトを作成し、Empty Compose Activity を選択します。
次に、プロジェクト モジュールの build.gradle に次の内容を設定します。
dependencies {
def nav_version = "2.5.2"
implementation("androidx.navigation:navigation-compose:$nav_version")
......
}
2. アプリケーションの導入とエンティティ クラス
JetPack Compose コンポーネントのナビゲーション アプリケーションを説明するために、単純なアプリケーションを定義します。つまり、ロボットのスクロール リストを表示し、スクロール リスト内の 1 つの項目をクリックして、特定のロボットインターフェイス。
図 1: ロボット リスト
リストの行にあるロボット アイコンをクリックして、次のインターフェイスに入ります。
図2: 個々のロボット情報の表示
この目的のために、次のように定義されるロボット エンティティを表すクラスを作成します。
data class Robot(val imageId:Int,val name:String,val description:String):Parcelable{
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString()!!,
parcel.readString()!!
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(imageId)
parcel.writeString(name)
parcel.writeString(description)
}
override fun describeContents(): Int =0
companion object CREATOR : Parcelable.Creator<Robot> {
override fun createFromParcel(parcel: Parcel): Robot {
return Robot(parcel)
}
override fun newArray(size: Int): Array<Robot?> {
return arrayOfNulls(size)
}
}
}
3. さまざまなインターフェイスを定義します。
このアプリケーションで 3 つのインターフェイスを定義します。
(1) RobotItemView のスクロール リストの各単一項目を定義します。
/**
* 定义列表单项的视图
* @param robot Robot
*/
@Composable
fun RobotItemView(robot:Robot){
Column{
Row(modifier= Modifier
.fillMaxWidth()
.border(1.dp,Color.Black)
.clip(RoundedCornerShape(10.dp))
.background(colorResource(id = R.color.teal_200))
.padding(5.dp)){
Image(modifier = Modifier
.width(80.dp)
.height(80.dp)
.clip(shape = CircleShape)
.background(Color.Black)
.clickable {
//增加导航处理
},
painter = painterResource(id = robot.imageId),
contentDescription = "机器人")
Column{
Text("${robot.name}",fontSize=16.sp,color=Color.Blue)
Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)
}
}
}
}
(2) ロボットのスクロールリストを表示するインターフェース RobotListScreen を定義する
/**
* Robot list screen
* 定义显示机器人滚动列表的界面
*/
@Preview
@Composable
fun RobotListScreen(){
val robots = mutableListOf<Robot>()
for(i in 1..20)
robots.add(Robot(android.R.mipmap.sym_def_app_icon,"第${i}机器人","进入机器人世界"))
var reverseLayout = false
Box(modifier= Modifier
.background(Color.Black)
.fillMaxSize()){
LazyColumn(state= rememberLazyListState(),
verticalArrangement = if(!reverseLayout) Arrangement.Top else Arrangement.Bottom){
items(robots){
robot->
RobotItemView(robot = robot)
}
}
}
}
(3) ロボット固有の情報を定義するインターフェース「RobotScreen」
/**
* 定义机器人具体信息显示界面
* @param robot Robot
*/
@Composable
fun RobotScreen(){
val robot = Robot(android.R.mipmap.sym_def_app_icon,"第1号机器人","第1号机器人进入机器人的世界")
Column(modifier = Modifier
.background(Color.Black)
.padding(20.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center){
Row(verticalAlignment = Alignment.CenterVertically){
Image(modifier= Modifier
.width(160.dp)
.height(160.dp),
painter= painterResource(id = robot.imageId),
contentDescription = "${robot.description}")
Text("${robot.name}",fontSize=36.sp,color=Color.White)
}
Text("${robot.description}",fontSize=24.sp,color=Color.Yellow)
}
}
これらの異なるインターフェイスをより適切に識別して処理するために、シールされたクラス Screens が定義され、それぞれのインターフェイスのエンティティ オブジェクトが作成されます。
/**
* 定义可显示的界面
* @property route String 导航线路名称
* @property title String 界面标题
* @constructor
*/
sealed class Screens(val route:String,val title:String){
object HomePage:Screens("home","机器人列表")
object RobotPage:Screens("robot","机器人详细信息")
}
4. 異なるインターフェース間のナビゲーション切り替えを実装します
。ナビゲーションを実装するには、各インターフェース間のナビゲーション方向を示すナビゲーション マップを定義する必要があります。このためには、次の操作を行います。
/**
* Navigation graph screen
* 定义导航图
*/
@Composable
fun NavigationGraphScreen(){
//获取导航控制器
val navController = rememberNavController()
NavHost(navController, startDestination = Screens.HomePage.route){
composable(Screens.HomePage.route){
RobotListScreen()
}
composable(Screens.RobotPage.route){
RobotScreen()
}
}
}
これまでナビゲーションは実装されていませんでしたが、これは、ナビゲーション コントロールにナビゲーション アクションを処理するためのナビゲーション コントローラー オブジェクトが不足しており、RobotItemView の Image クリック アクション定義が空であるため、ナビゲーションを実装することができないためです。変更が加えられます
:
(1) ナビゲーション マップを再定義します。
/**
* Navigation graph screen
* 定义导航图
*/
@Preview
@Composable
fun NavigationGraphScreen(){
//获取导航控制器
val navController = rememberNavController()
NavHost(navController, startDestination = Screens.HomePage.route){
composable(Screens.HomePage.route){
RobotListScreen(navController)
}
composable(Screens.RobotPage.route){
RobotScreen()
}
}
}
上記のコードでは、NavHost はナビゲーション マップおよび NavController のコンテナーであり、ナビゲーションの開始ルート、つまり Screens.HomePage.route を指定します。
(2) RobotListScreen を変更し、インターフェイスをナビゲートできるようにナビゲーション コントローラーを追加します。
/**
* Robot list screen
* 定义显示机器人滚动列表的界面
*/
@Composable
fun RobotListScreen(navController:NavController){
//增加导航控制器对象
val robots = mutableListOf<Robot>()
for(i in 1..20)
robots.add(Robot(android.R.mipmap.sym_def_app_icon,"第${i}机器人","进入机器人世界"))
var reverseLayout = false
Box(modifier= Modifier
.background(Color.Black)
.fillMaxSize()){
LazyColumn(state= rememberLazyListState(),
verticalArrangement = if(!reverseLayout) Arrangement.Top else Arrangement.Bottom){
items(robots){
robot->
RobotItemView(navController ,robot = robot) //将导航控制器对象传递给单项视图
}
}
}
}
(3) RobotItemView を変更し、ナビゲーション グラフ内のナビゲーション コントローラー オブジェクトをパラメーターとして渡し、ナビゲーション処理を追加します。
/**
* 定义列表单项的视图
* @param robot Robot
*/
@Composable
fun RobotItemView(navController:NavController,robot:Robot){
Column{
Row(modifier= Modifier
.fillMaxWidth()
.border(1.dp, Color.Black)
.clip(RoundedCornerShape(10.dp))
.background(colorResource(id = R.color.teal_200))
.padding(5.dp)){
Image(modifier = Modifier
.width(80.dp)
.height(80.dp)
.clip(shape = CircleShape)
.background(Color.Black)
.clickable {
//增加导航处理
//根据导航路线robot到Screens.RobotPage对应的RobotScreen定义的界面
navController.navigate("robot")
},
painter = painterResource(id = robot.imageId),
contentDescription = "机器人")
Column{
Text("${robot.name}",fontSize=16.sp,color=Color.Blue)
Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)
}
}
}
}
現在の位置で、ロボットのスクロールリストから 1 つの項目のアイコンが選択され、次のページにジャンプします。ただし、各ジャンプは同一データのインターフェースであるため、実態に即したものではありません。実際に行う必要があるのは、スクロール リストから単一の項目アイコンを選択し、クリックしてこの「ロボット」の詳細情報インターフェイスに入ることで、対応するデータを渡す必要があります。
5. ナビゲーションでのデータの受け渡し
1. 基本的な種類のデータの受け渡し
スクロールリストからロボット詳細情報インターフェースへジャンプする際に文字列を渡すことを想定し、ロボットリスト単品表示インターフェースを修正し、クリック時のデータ送信処理を追加します。アクション: (1
) RobotItemScreen関数を修正し、データ送信処理を追加
@Composable
fun RobotItemView(navController:NavController,robot:Robot){
Column{
Row(modifier= Modifier
.fillMaxWidth()
.border(1.dp, Color.Black)
.clip(RoundedCornerShape(10.dp))
.background(colorResource(id = R.color.teal_200))
.padding(5.dp)){
Image(modifier = Modifier
.width(80.dp)
.height(80.dp)
.clip(shape = CircleShape)
.background(Color.Black)
.clickable {
//增加导航处理,发送方在导航路线中发送字符串数据
navController.navigate("robot/${robot.toString()}")
},
painter = painterResource(id = robot.imageId),
contentDescription = "机器人")
Column{
Text("${robot.name}",fontSize=16.sp,color=Color.Blue)
Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)
}
}
}
}
(2) ナビゲーション マップを変更します。
ナビゲーション マップを変更し、ナビゲーション マップ内のデータの受信側のパラメーター名とパラメーター タイプを指定し、ナビゲーション ルートをパラメーターを含む形式に変更します。
/**
* Navigation graph screen
* 定义导航图
*/
@Preview
@Composable
fun NavigationGraphScreen(){
//获取导航控制器
val navController = rememberNavController()
NavHost(navController, startDestination = Screens.HomePage.route){
//数据的发送方
composable(Screens.HomePage.route){
RobotListScreen(navController)
}
//数据的接收方
composable(route=Screens.RobotPage.route+"/{robot}",//修改导航路线,增加要传递的参数名称
arguments=listOf(navArgument("robot"){
type= NavType.StringType}))//指定接收的参数和参数类型
{
val robotStr=it.arguments?.getString("robot")?:"没有任何信息,接收参数失败"
RobotScreen(robotStr)
}
}
}
(3) 受信側ロボット詳細情報インタフェースでは、受信パラメータの処理とパラメータの種類を指定します。
/**
* 定义机器人具体信息显示界面
* @param robot Robot
*/
@Composable
fun RobotScreen(robot:String){
Column(modifier = Modifier
.background(Color.Black)
.padding(20.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center){
Row(verticalAlignment = Alignment.CenterVertically){
Image(modifier= Modifier
.width(160.dp)
.height(160.dp),
painter= painterResource(id = android.R.mipmap.sym_def_app_icon),
contentDescription = "${robot}")
}
Text("${robot}",fontSize=24.sp,color=Color.Yellow)
}
}
このような処理の後、ロボットの詳細インターフェイスに移動すると、次のようになります。
図 3: 渡された文字列データの受信
2. カスタムタイプのデータを渡す
実際の場面ではカスタムタイプのオブジェクトデータを渡す必要があることが多いですが、例えば上記のRobotオブジェクトがParcelableインターフェースを実装している場合はどうすればよいでしょうか?このようなオブジェクトを次のインターフェースに直接渡す場合は、
(1) ナビゲーショングラフの変更 ナビゲーション
グラフ内の受信機のパラメータの型をロボット型に変更します。
@Preview
@Composable
fun NavigationGraphScreen(){
//获取导航控制器
val navController = rememberNavController()
NavHost(navController, startDestination = Screens.HomePage.route){
//数据的发送方
composable(Screens.HomePage.route){
RobotListScreen(navController)
}
//数据的接收方
composable(route=Screens.RobotPage.route+"/{robot}",//修改导航路线,增加要传递的参数名称
arguments=listOf(navArgument("robot"){
//指定接收的参数和参数类型
type= NavType.inferFromValueType(Robot(R.mipmap.ic_launcher,"",""))}))
{
val robot:Robot=it.arguments?.getParcelable("robot")?:Robot(android.R.mipmap.sym_def_app_icon,"测试","机器人信息获取失败")
RobotScreen(robot)
}
}
}
(2) データ受信側のインターフェースを変更する
データ送信側のインターフェースは上記の内容を維持していますので、変更の必要はありません、受信側のインターフェース処理の変更と、データ送信側のインターフェース処理の変更と、データ送信側のインターフェースが受け取るパラメータの型を変更するだけで済みます。受信側の RobotScreen 関数を文字列からロボット タイプ、GUI に変換し、次のコードに示すように、インターフェイスはそれに応じて処理できます。
@Composable
fun RobotScreen(robot:Robot){
//修改参数类型为Robot
Column(modifier = Modifier
.background(Color.Black)
.padding(20.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center){
Row(verticalAlignment = Alignment.CenterVertically){
Image(modifier= Modifier
.width(160.dp)
.height(160.dp),
painter= painterResource(id = robot.imageId),
contentDescription = "${robot.description}")
Text("${robot.name}",fontSize=36.sp,color=Color.White)
}
Text("${robot.description}",fontSize=24.sp,color=Color.Yellow)
}
}
java.lang.UnsupportedOperationException : Parcelable はデフォルト値をサポートしていません。Parcelable
型データはデフォルト値をサポートしていないため、スローされます。直接渡した場合、サポートされていない操作の例外がスローされます。したがって、カスタム タイプのオブジェクトを渡すには他の方法が必要です。3. Gson を使用してカスタム データを転送する 1 つの解決策は、送信側でカスタム タイプ オブジェクトのデータを JSON 文字列に変換し、受信側で受信した文字列をカスタム タイプ オブジェクトに変換することです
。
これは、Gson フレームワークの助けを借りて実現されます。
(1) Gson の依存関係を追加する
Gson フレームワークの依存関係をモジュールの build.gradle に次の形式で追加する必要があります。
dependencies {
implementation 'com.google.code.gson:gson:2.10'
...
}
(2) スクロールリストの単品ビューの定義
/**
* 定义列表单项的视图
* @param robot Robot
*/
@Composable
fun RobotItemView(navController:NavController,robot:Robot){
Column{
Row(modifier= Modifier
.fillMaxWidth()
.border(1.dp, Color.Black)
.clip(RoundedCornerShape(10.dp))
.background(colorResource(id = R.color.teal_200))
.padding(5.dp)){
Image(modifier = Modifier
.width(80.dp)
.height(80.dp)
.clip(shape = CircleShape)
.background(Color.Black)
.clickable {
val robotStr = Gson().toJson(robot)
//增加导航处理,发送方在导航路线中发送字符串数据
navController.navigate("robot/${robotStr}")
},
painter = painterResource(id = robot.imageId),
contentDescription = "机器人")
Column{
Text("${robot.name}",fontSize=16.sp,color=Color.Blue)
Text("${robot.description}",fontSize=20.sp,color=Color.DarkGray)
}
}
}
}
(3) スクロールリストインターフェースの定義を変更する
/**
* Robot list screen
* 定义显示机器人滚动列表的界面
*/
@Composable
fun RobotListScreen(navController:NavController){
val robots = mutableListOf<Robot>()
for(i in 1..20)
robots.add(Robot(android.R.mipmap.sym_def_app_icon,"第${i}机器人","第${i}机器人进入机器人世界"))
var reverseLayout = false
Box(modifier= Modifier
.background(Color.Black)
.fillMaxSize()){
LazyColumn(state= rememberLazyListState(),
verticalArrangement = if(!reverseLayout) Arrangement.Top else Arrangement.Bottom){
items(robots){
robot->
RobotItemView(navController,robot = robot)
}
}
}
}
コードは変更ありません。
(4) データを受信するインターフェースの定義
@Composable
fun RobotScreen(robot:Robot){
Column(modifier = Modifier
.background(Color.Black)
.padding(20.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center){
Row(verticalAlignment = Alignment.CenterVertically){
Image(modifier= Modifier
.width(160.dp)
.height(160.dp),
painter= painterResource(id = robot.imageId),
contentDescription = "${robot.description}")
Text("${robot.name}",fontSize=36.sp,color=Color.White)
}
Text("${robot.description}",fontSize=24.sp,color=Color.Yellow)
}
}
(5) ナビゲーションマップの修正
@Preview
@Composable
fun NavigationGraphScreen(){
//获取导航控制器
val navController = rememberNavController()
NavHost(navController, startDestination = Screens.HomePage.route){
//数据的发送方
composable(Screens.HomePage.route){
RobotListScreen(navController)
}
//数据的接收方
composable(route=Screens.RobotPage.route+"/{robot}",//修改导航路线,增加要传递的参数名称
arguments=listOf(navArgument("robot"){
type= NavType.StringType}))//指定接收的参数和参数类型为字符串
{
val robotJsonStr=it.arguments?.getString("robot")?:"接收错误的参数"
RobotScreen(Gson().fromJson(robotJsonStr,Robot::class.java))//将字符串转换成Robot对象
}
}
}
メイン アクティビティでナビゲーション グラフ インターフェイスを呼び出します。コードは次のとおりです。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Ch04_ComposeTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
NavigationGraphScreen()
}
}
}
}
}
最終的な実行結果は次のとおりです。
この時点で、任意のスクロール リスト項目アイコンをクリックして、指定されたインターフェイスに入ります。
ナビゲーション
に Compose を使用する https://developer.android.google.cn/reference/androidx/navigation/NavHost?hl=zh-cn