LoginSignup
9
1

More than 1 year has passed since last update.

【Jetpack Compose】簡単なGuidesスタイルのOnboarding画面を作成する

Last updated at Posted at 2022-12-15

この記事は、リンクバルアドベントカレンダー2022の14日目の記事です。

はじめに

よくスマホを使えば、多分新しいアプリケーションをインストールして初めて起動しようとするたびに、紹介や使用方法が表示されることがよくあります。 みなさんはそれらを何と呼びますか? チュートリアル、ウォークスルー、ガイド等?
これらは、Onboardingという一般名で知られています。 この記事では、Jetpack Composeを使用して簡単に作成する方法を紹介します。

Onboarding画面とは

Onboarding画面は、ユーザーがアプリケーションを起動したときに最初に目にするものです。First User Experience (FUX)として知られることもあります。通常、アプリケーションの使用方法、機能、アプリケーションがユーザーにどのように役立つかなど、アプリケーションに関するいくつかのものを示すために使用されます。これは、複雑な機能や紛らわしいUIを持つアプリケーションで一般的です。

この記事のOnboarding画面

「Onboarding」の画像をGoogleで検索すると、ほとんどの結果が上の画像のようになります。 しかし実際には、OnboardingのUI/UXデザインには他にも多くの形式があります。それらの一つはガイド/ウォークスルー(Guides/Walkthrough)です。

また、現在、Jetpack Composeを使用してガイド/ウォークスルー オンOnboardingを作成する方法に関する記事を見つけるのは困難です :frowning2:。 現在の記事のほとんどは、HorizontalPagerを使用したスライドショー形式です。
この記事では、ガイド/ウォークスルーOnboarding画面を作成する簡単な方法を紹介します。

結果を見ましょう!! :point_down:

実装

準備するもの

まず、次の準備/知識が必要です。

  1. プロジェクト。
  2. Preferences (SharedPreferences/DataStore)。
  3. Jetpack Composeアーキテクチャ
  4. アプリの編集したスクリーンショット。

プロジェクト: もちろん、アプリの紹介画面を実装するので、最も重要なことはそのアプリのコードですね。サンプル用のため、この記事でJetpack Composeの組み込みテンプレートのJetchatを使用します。
この指示に従ってください :point_right:

Preferences: Googleは、開発者がDataStoreを使用することを推奨しています。 ただし、この記事が長くなりすぎないように、代わりにSharedPreferencesを使用します。
後でDataStoreに変換することもお勧めします。

アプリの編集したスクリーンショット: この記事のマイナスポイントは、ガイド/ウォークスルーOnboarding画面のようなCompound Shapeを作成する方法がまだ見つかなかったことです。 現在の回避策はスクリーンショットを使用することですが、これでは相互運用性がなくなります。

はじめましょう

ステップ1: 現在のチュートリアルステップを保存する

まず、今のチュートリアルステップを格納するkey-valueのペアを保存する必要があります。
SharedPreferences を使用すると、次のようになります。

data/MyPreferences.kt
object MyPreferences {
    const val KEY_CHAT_STEP = "key_chat_step"
    // ここにさらに画面を追加できる

    lateinit var prefs: SharedPreferences

    fun get(key: String): Int {
        return prefs.getInt(key, 0)
    }

    fun set(key: String, value: Int) {
        with(prefs.edit()) {
            putInt(key, value)
            apply()
        }
    }
}
NavActivity.kt
class NavActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
+       MyPreferences.prefs = getPreferences(Context.MODE_PRIVATE)
        // ...

ステップ2: ViewModelを作成してFragment/Screenに追加する

Jetchatを使用する場合は、package conversationに移動してViewModelを作成しましょう。

conversation/ConversationViewModel.kt
class ConversationViewModel : ViewModel() {
    // 初期ステップを0で初期化する
    private val _tutorialStep = MutableStateFlow(0)
    val tutorialStep: StateFlow<Int> = _tutorialStep

    init {
        getTutorialStep()
    }

    // 次のステップに移動すると、新しい値を更新する
    fun setTutorialStep(currentStep: Int = 0) {
        _tutorialStep.value = currentStep
        MyPreferences.set(MyPreferences.KEY_CHAT_STEP, currentStep)
    }

    // 実機に格納されているステップ値を取得する
    private fun getTutorialStep() {
        _tutorialStep.value = MyPreferences.get(MyPreferences.KEY_CHAT_STEP)
    }
}

プロジェクトのコードスタイルによっては、違うかもしれません。 Jetchatの場合はConversationFragment.ktにコードを追加します :point_down:

conversation/ConversationFragment.kt
class ConversationFragment : Fragment() {

+   private val viewModel: ConversationViewModel by viewModels()
    private val activityViewModel: MainViewModel by activityViewModels()

    override fun onCreateView(
        // ...

ステップ3: チュートリアル画面を作る

チュートリアル画面の共通コンポーネントを作成します。

tutorial/TutorialScreen.kt
@Composable
fun TutorialScreen(
    backgroundResId: Int,   // Screen background Resource Id
    contentXOffset: Float,  // 説明文のxOffset
    contentYOffset: Float,  // 説明文のyOffset
    onGoNext: () -> Unit,   // [Next]ボタンをクリックすると実行されるアクション
    goNextText: String = "Next",
    content: @Composable () -> Unit
) {
    Box(modifier = Modifier
        .fillMaxSize()
        .systemBarsPadding()
    ) {
        // スクリーンショットを背景として
        Image(
            painter = painterResource(backgroundResId),
            contentScale = ContentScale.FillBounds,
            contentDescription = null,
        )
        // 説明文
        Surface(
            color = Color.Transparent,
            contentColor = Color.White,
            modifier = Modifier.offset(x = contentXOffset.dp, y = contentYOffset.dp)
        ) {
            content()
        }
        // 画面左上のアクションボタン
        Button(
            onClick = onGoNext,
            modifier = Modifier
                .align(Alignment.TopEnd)
                .padding(16.dp)
        ) {
            Text(goNextText)
        }
    }
}

ステップ4: Conversationのチュートリアル画面を作成しする

conversation packageに戻り、ConversationTutorial.ktという名前の新しいファイルを作成します。
最大のステップ値を3に設定します。つまり、チュートリアルが3つのステップがあります。

conversation/ConversationTutorial.kt
const val CONVERSATION_MAX_STEP = 3
conversation/ConversationTutorial.kt
@Composable
fun ConversationTutorial(step: Int, onClick: () -> Unit) {
    when (step) {
        0 -> ConversationTutorial1(onClick = onClick)
        1 -> ConversationTutorial2(onClick = onClick)
        2 -> ConversationTutorial3(onClick = onClick)
    }
}

それぞれのチュートリアル画面を作成します。下記は例です :point_down:

conversation/ConversationTutorial.kt
@Composable
private fun ConversationTutorial1(onClick: () -> Unit) {
    TutorialScreen(
        backgroundResId = R.drawable.chat_step_1,
        contentXOffset = 20f,
        contentYOffset = 600f,
        onGoNext = onClick
    ) {
        Column(
            modifier = Modifier.wrapContentWidth(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {

            Text("Write your message here")
            Icon(Icons.Default.ArrowDownward, null)
        }
    }
}

上記のコードの結果は次のとおりです。

ステップ5:ConversationFragmentにチュートリアル画面を追加する

conversation/ConversationFragment.kt
class ConversationFragment : Fragment() {
    // ...
    override fun onCreateView(
        // ...
        JetchatTheme {
+           if (tutorialStep < CONVERSATION_MAX_STEP) {
+              ConversationTutorial(step = tutorialStep, onClick = toNextStep)
+           } else {
                ConversationContent(
                    // ..

ステップ6: アニメーションを追加してインターフェイスをより魅力的にする

この記事では、Infinite Animationを使用してNext/Finishボタンをドキドキさせて注意を引きます。
その他のJetpack Composeアニメーションについては、Jetpack Composeアニメーションで見てください。

tutorial/TutorialScreen.kt
@Composable
fun TutorialScreen(
    backgroundResId: Int,
    contentXOffset: Float,
    contentYOffset: Float,
    onGoNext: () -> Unit,
    goNextText: String = "Next",
    content: @Composable () -> Unit
) {
+   val infiniteTransition = rememberInfiniteTransition()
+   val scale by infiniteTransition.animateFloat(
+       initialValue = 1f,
+       targetValue = 1.1f,
+       animationSpec = infiniteRepeatable(
+           animation = tween(500, easing = LinearEasing),
+           repeatMode = RepeatMode.Reverse
+       )
+   )
    Box(
    // ..   
        Button(
            onClick = onGoNext,
            modifier = Modifier
                .align(Alignment.TopEnd)
                .padding(16.dp)
+               .graphicsLayer(
+                   scaleX = scale,
+                   scaleY = scale
+               )
        ) {
            Text(goNextText)
        }
    }

結果:
ezgif.com-gif-maker (1).gif

おわりに

多くのアプリケーションがOnboarding画面を無視してきましたが、機能が複雑だったりインターフェースがわかりにくいアプリケーションの場合、Onboarding画面が必要になると思います。 Onboarding画面はユーザーに利益をもたらすだけでなく、ユーザーを感動させることでビジネスにも利益をもたらします。

shadow背景の上にtransparent背景があるものを作成するための回避策は見つかりませんでした(Combound Shapeの作成)。 回避策の提案があれば、遠慮なくコメントしてください :bow:
ここまで読んでいただきありがとうございました :bow:

参考

  1. How to Properly Design Onboarding Screens
  2. https://developer.android.com/jetpack/compose/setup#sample
9
1
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
9
1