20190228のAndroidに関する記事は10件です。

[Flutter/Android] Kotlin や C/C++ をデバッグする方法

Flutter でも Kotlin や C/C++ 書くことはあると思いますが、そのデバッグ方法が分かりにくかったのでメモ。

結論を言うと、Using OEM debuggers に書いてある。
なんかうまく検索でこの記事にたどり着けなくて無駄に時間を消費してしまった。

手順

1. アプリを起動する

VSCode や Android Studio から起動する

2. android ディレクトリのみを Android Studio で開く

image.png

3. デバッガをアタッチする

image.png
Auto のまま、対象のデバイス & アプリ を指定する
image.png

4. いえーい!

C/C++ のコードにブレークポイントを打つと、ちゃんと止まってくれる。
image.png

最後に

iOS/Swift は簡単にできますが、Android はこんな感じ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Flutter/Android] Kotlin と C/C++ をデバッグする方法

Flutter でも Kotlin や C/C++ 書くことはもちろんありますが、そのデバッグ方法が分かりにくかったのでメモ。

結論を言うと、Using OEM debuggers に書いてある。
なんかうまく検索でこの記事にたどり着けなくて時間を浪費してしまった。

手順

iOS/Swift/C は簡単にできますが、Android/Kotlin/C はこんな感じ。

1. アプリを起動する

VSCode や Android Studio から起動する

2. android ディレクトリのみを Android Studio で開く

image.png

3. デバッガをアタッチする

アプリが起動して画面が表示されたのを確認したら、アタッチする
image.png
Auto のまま、対象のデバイス & アプリ を指定する
image.png

4. いえーい!

C/C++ のコードにブレークポイントを打つと、ちゃんと止まってくれる。
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでAndroidのアプリ作成実践

はじめに

UnityでAndroidのアプリ作成を試してみたくて、まずはチュートリアルを試してみました。
https://unity3d.com/jp/learn/tutorials/projects/hajiuni-jp

開発環境

Unity 2018.3.0f2
Visual Studio 2017
Android Studio 3.3.1
Unity Remote5
GitHub for Unity 1.2.1

チュートリアルから修正

チュートリアルの説明に従い1~7まで作成したところで、一応PC上ではゲームとして遊べる状態になったので、Android用のビルド、実機上のデバッグまで実行してみました。
手順は以下ページが参考になりました。
https://qiita.com/taroyan/items/2ff7f6aadd640221bde5

チュートリアルはPC向けのため操作はキーボードなのですが、このままではAndroid上で操作できません。
操作に対応するコードPlayerControllerをタッチ操作できるように書き換えます。
以下ページが参考になりました。
https://docs.unity3d.com/jp/460/ScriptReference/Input.GetTouch.html

Unity Remote

実機上でデバッグするためにはAndroid用にビルドする必要があるのですが、非常に時間がかかります。
PC上だけでは、タッチ操作などAndroidに依存する機能はデバッグできないため、簡易的にデバッグするツールとしてUnity Remoteが使えます。

ソースコード管理

ここまでできたところで、GitHub上でソースコード管理してみようと考えてGitHub for Unityを導入しました。
手順は以下ページが参考になりました。
http://kan-kikuchi.hatenablog.com/entry/GitHub_for_Unity
Unity上から簡単にGitHubへのpushまで行えました。
gitignoreファイルが自動で作成されるので非常に助かります。
今回作成したリポジトリは以下になります。
https://github.com/SatoshiTakahama/Roll-a-Ball.git

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

coroutineとViewModelを使ってDialogFragmentを実装する

https://qiita.com/FKbelm/items/f85949dff7a3c0e7781d で試した内容の改善版です。

上の記事では、メモリリークを引き起こしていたので、ViewModelの力を使って再実装を目指しました。

ViewModelの作成

MainActivityViewModel.kt
class MainActivityViewModel() : ViewModel() {

    val hogeDialogEvent = MutableLiveData<Unit>()

    private val _hogeDialogChannel = Channel<Boolean>()
    val hogeDialogChannel = _hogeDialogChannel as SendChannel<Boolean>

    private suspend fun showHogeDialog(): Boolean {
        // ダイアログを表示
        hogeDialogEvent.postValue(Unit)

        // suspendで結果を取得
        // Channelに値が入るまで待つ
        val result = _hogeDialogChannel.receive()

        // 結果からなんかやって返す
        return TODO(result)
    }
}

はActivityにダイアログを開くよう通知するhogeDialogEventに通知を出して、_hogeDialogChannel.receive()で結果が入ってくるのを待ちます。

Activityの作成

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val viewModel by lazy {
        ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        viewModel.hogeDialogChannel.observe(this, Observer {
            // ダイアログが存在しないときに開く処理
            if (supportFragmentManager.findFragmentByTag(TAG_HOGE) == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(AlertDialogFragment(), TAG_HOGE)
                    .commitAllowingStateLoss()
            }
        })
    }
}

ActivityはDialogFragmentを開く処理のみを担っています。

DialogFragmentの作成

ChannelDialogFragment.kt
// 大元になるDialogFragment
abstract class ChannelDialogFragment<T> : DialogFragment() {
    protected abstract val channel: SendChannel<T>?
}

SendChannel<T>はcoroutinesのChannel<T>でのうち、送信機能のみのインターフェイスです。
ここから結果を送信してあげます。

AlertDialogFragment.kt
// 実際に使うDialogFragment
class AlertDialogFragment : ChannelDialogFragment<AlertDialogFragmentResult>() {

    // ActivityのViewModelを取得し、そこからchannelを取得する
    override val channel by lazy {
        activity?.let {
            ViewModelProviders.of(it).get(MainActivityViewModel::class.java).hogeDialogChannel
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val context = this.context ?: return super.onCreateDialog(savedInstanceState)

        return AlertDialog.Builder(context).apply {
            setTitle(R.string.hoge_title)
            setMessage(R.string.hoge_message)

            setPositiveButton(R.string.hoge_positive) { _, _ ->
                channel?.offer(true) // ViewModelのreceiveが受け取る
            }
            setNegativeButton(R.string.hoge_negative) { _, _ ->
                channel?.offer(false) // ViewModelのreceiveが受け取る
            }
        }.create()
    }
}

ChannelをViewModelに持たせることでActivityの破棄,再生成後にもChannelを容易に再取得できるようになりました。

Channelを直接公開せずにViewModelにインターフェイスを実装させることでも実現できそうですが、複数のDialogFragmentを持ちたいといったことがあった場合はChannelを直接扱う方が楽かなと思いました。

ViewModelの力は大きいですね。

参考

Coroutine時代のDialogFragment
https://qiita.com/idaisuke/items/b4f3c2e0a872544b97d0

Prism 5とReactiveProperty
https://blog.okazuki.jp/entry/2014/05/09/083315

Android Architecture ComponentsのViewModelとDialogFragment
http://sys1yagi.hatenablog.com/entry/2017/08/24/125416

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

coroutineとViewModelを使ってDialogFragmentを実装する

https://qiita.com/KyoPeeee/items/f85949dff7a3c0e7781d で試した内容の改善版です。

上の記事では、メモリリークを引き起こしていたので、ViewModelの力を使って再実装を目指しました。

ViewModelの作成

MainActivityViewModel.kt
class MainActivityViewModel() : ViewModel() {

    val hogeDialogEvent = MutableLiveData<Unit>()

    private val _hogeDialogChannel = Channel<Boolean>()
    val hogeDialogChannel = _hogeDialogChannel as SendChannel<Boolean>

    private suspend fun showHogeDialog(): Boolean {
        // ダイアログを表示
        hogeDialogEvent.postValue(Unit)

        // suspendで結果を取得
        // Channelに値が入るまで待つ
        val result = _hogeDialogChannel.receive()

        // 結果からなんかやって返す
        return TODO(result)
    }
}

はActivityにダイアログを開くよう通知するhogeDialogEventに通知を出して、_hogeDialogChannel.receive()で結果が入ってくるのを待ちます。

Activityの作成

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val viewModel by lazy {
        ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        viewModel.hogeDialogChannel.observe(this, Observer {
            // ダイアログが存在しないときに開く処理
            if (supportFragmentManager.findFragmentByTag(TAG_HOGE) == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(AlertDialogFragment(), TAG_HOGE)
                    .commitAllowingStateLoss()
            }
        })
    }
}

ActivityはDialogFragmentを開く処理のみを担っています。

DialogFragmentの作成

ChannelDialogFragment.kt
// 大元になるDialogFragment
abstract class ChannelDialogFragment<T> : DialogFragment() {
    protected abstract val channel: SendChannel<T>?
}

SendChannel<T>はcoroutinesのChannel<T>でのうち、送信機能のみのインターフェイスです。
ここから結果を送信してあげます。

AlertDialogFragment.kt
// 実際に使うDialogFragment
class AlertDialogFragment : ChannelDialogFragment<AlertDialogFragmentResult>() {

    // ActivityのViewModelを取得し、そこからchannelを取得する
    override val channel by lazy {
        activity?.let {
            ViewModelProviders.of(it).get(MainActivityViewModel::class.java).hogeDialogChannel
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val context = this.context ?: return super.onCreateDialog(savedInstanceState)

        return AlertDialog.Builder(context).apply {
            setTitle(R.string.hoge_title)
            setMessage(R.string.hoge_message)

            setPositiveButton(R.string.hoge_positive) { _, _ ->
                channel?.offer(true) // ViewModelのreceiveが受け取る
            }
            setNegativeButton(R.string.hoge_negative) { _, _ ->
                channel?.offer(false) // ViewModelのreceiveが受け取る
            }
        }.create()
    }
}

ChannelをViewModelに持たせることでActivityの破棄,再生成後にもChannelを容易に再取得できるようになりました。

Channelを直接公開せずにViewModelにインターフェイスを実装させることでも実現できそうですが、複数のDialogFragmentを持ちたいといったことがあった場合はChannelを直接扱う方が楽かなと思いました。

ViewModelの力は大きいですね。

参考

Coroutine時代のDialogFragment
https://qiita.com/idaisuke/items/b4f3c2e0a872544b97d0

Prism 5とReactiveProperty
https://blog.okazuki.jp/entry/2014/05/09/083315

Android Architecture ComponentsのViewModelとDialogFragment
http://sys1yagi.hatenablog.com/entry/2017/08/24/125416

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリの GPGSはストア経由じゃないと動作しない?

その1

GPGS(GooglePlayGameServices)の機能でリーダーボード(ランキング)を実装しようと
いろいろと調べて作成しました。

主にこちらのブログを参考にして
https://indie-du.com/entry/2017/02/13/070000

認証つまずくも、試行錯誤の末できたはずが・・
apkを作成して、端末にインストール

Social.ShowLeaderboardUI();

でリーダーボードが表示された。
おお!
では、スコアを登録

Social.ReportScore(mScore, nLeaderboard[mPlayMode], (bool success) => {
       // handle success or failure
       isFinished = true;
       if (!success){
           isError = true;
       }
});

できたようだ。

再び、スコアボードの表示を行うと、対象のボードを表示するとエラーが

Playゲームで問題が発生しました
のエラーが表示されてランキング表示されない。
スコアを登録するとこの現象が発生し、未登録の場合は

このゲームではまだハイスコアが公開されていません。

のメッセージ・・

調査した結果
Unityや、adb.exe 経由でinstallしたアプリだと、GPGSの動作に影響がでるようで意図した動作にならなかった。
logcat で確認したところ、それっぽい警告が出ていたのですが、保存してなかったのと
再現ができなくてちょっと確信がもてないけど・・

ストア経由で同じapkを入れたところ問題なく動作ができた。

エビデンスがちゃんと取れていないので、個人的な備忘録です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

StatusBar や AppBar を透過にする

StatusBar や AppBar を透過したいなと思ったときに毎回調べてはハマったりしているので自分用にメモとして残したいと思います。

前提

~.NoActionBar を継承したスタイルはどこかで設定して自前で ActionBar を実装している前提です。

StatusBar

AndroidManifest にある該当の Activity の theme に以下を設定します。

<style name="TranslucentStatusBar">
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

v23 以降はコレを設定します。

<style name="TranslucentStatusBar">
    <item name="android:statusBarColor">@android:color/transparent</item>
    <item name="android:windowLightStatusBar">true</item>
</style>

Y.A.M の 雑記帳: StatusBar 透明化の正しい方法 にとても詳しく書かれています。ちゃんと理解したい人はみてください。

AppBar

AppBarLayouttheme として以下を設定します。

<style name="TranslucentAppBar">
    <item name="colorPrimary">@android:color/transparent</item>
    <item name="android:windowActionBarOverlay">true</item>
    <item name="windowActionBarOverlay">true</item>
</style>

おそらく elevation が残るので、レイアウトに直接 app:elevaiton="0dp" を設定します。

戻るボタン

Toolbar に以下を theme として設定します。もしかしたら他にいい方法あるかもしれないので知っている人は教えてください :pray:

<style name="ToolBar">
    <item name="navigationIcon">@drawable/ic_close_white</item>
</style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityでモバイル端末のバイブレーションをコントロールする

Unityに用意されているバイブレーションAPI

Handheld.Vibrate ();

これで、1秒くらいのバイブレーションがブーンとなりますが、
使うシーンによってはちょっとマッチしない。
タップだったりに合わせて鳴らすにはもう少し短いものがほしいですよね。

調べるといろいろ出てきて、その寄せ集めの情報にはなりますが、
iOSとAndroidの情報がまとまってるものが意外となかったので書いておきます。

iOS

秒数での制御ができないので、プリセットのリストから選んで鳴らすことになります。

IOSUtil.cs
#if UNITY_IOS && !UNITY_EDITOR
        [DllImport ("__Internal")]
        static extern void _playSystemSound(int n);
#endif

        public static void PlaySystemSound(int n) //引数にIDを渡す
        {
#if UNITY_IOS && !UNITY_EDITOR
            _playSystemSound(n);
#endif
        }
IOSUtil.mm
#import <Foundation/Foundation.h>
#import <AudioToolBox/AudioToolBox.h>

extern "C" void _playSystemSound (int soundId)
{
    AudioServicesPlaySystemSound(soundId);
}

タップに反応する短いものだと 1519 なんかが良さそう。
ただ、ドキュメントを探しても、1519 1520 1521 あたりは載って無くて、
使ってて大丈夫なのかは若干不安なところ..

Android

秒数(ミリ秒)で制御することができます。参考リンクまんまですが。

AndroidUtil.cs
#if UNITY_ANDROID && !UNITY_EDITOR
    public static AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    public static AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
    public static AndroidJavaObject vibrator = currentActivity.Call<AndroidJavaObject>("getSystemService", "vibrator");
#else
        public static AndroidJavaClass unityPlayer;
        public static AndroidJavaObject currentActivity;
        public static AndroidJavaObject vibrator;
#endif

        public static void Vibrate(long milliseconds)
        {
            if (isAndroid())
                vibrator.Call("vibrate", milliseconds);
            else
                Handheld.Vibrate();
        }
        private static bool isAndroid()
        {
#if UNITY_ANDROID && !UNITY_EDITOR
            return true;
#else
            return false;
#endif
        }

端末によって差異はありそうですが、
試した感じ結構細かい数値の違いでも、体感の変化を感じられました。
上記で記載したiOSの 1519 の感触に近づけるには 3ms くらいの短い指定でもよさそう。

Handheld.Vibrate(); の記述について、
コードに残しておくことでAndroidのパーミッションをUnity側でビルド時に自動で追加してくれます。
手動で管理する場合はこの分岐は省略して良いかと思います。

プレビュー

実機で動かして試せるアプリを用意しました。
https://github.com/mrhdms/VibrationTester
iOS, Androidそれぞれで動きます。

iOSはプリセットリストから選択して再生。(音しかならないもの、音とバイブのもの、バイブレーションだけのものが混在)
Androidは0-1秒の間でスライダで調整しながら確認できます。

参考

http://smartgames.hatenablog.com/entry/2019/02/17/125413
http://greenkour.hateblo.jp/entry/2017/11/20/100000

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Jetpack Component ~Navigation 編~

androidx についてネットを徘徊していたところ
面白そうな物を見つけたのでやってみました。

Navigation Architecture Component

Navigation Architecture Componentを使用すると、一般的ではあるが複雑なナビゲーション要件をアプリケーションに実装できるので、一貫した予測可能なエクスペリエンスをより簡単にユーザーに提供できます。

Navigation  |  Android Developers

要するに ナビゲーション=画面遷移 が簡単にできる様になったよって事なのかな?

No more fragment transaction!!!

めっちゃ書かれてました。

どれだけ簡単になるのか。
Fragment の遷移をやってみたいと思います。

環境

今回使用した環境です。
Navigation を使用するためには Android Studio 3.2 以上が必須です。

  • Android Studio 3.2.1
  • Kotlin 1.2.71

navigation の編集をするために
Preference -> Experimental -> Editor から
Enable Navigation Editor にチェックが入っていなければチェックして再起動しましょう。

build.gradle

現在の最新版は 1.0.0-rc02 ですが、最新版にすると gradle 3.3 が必須になるらしく
今回の環境でやろうとすると R ファイルが見つからなかったりワーニング出たりとよくわからん事象が発生してたので少しバージョン落としてます?

これか...??Android Studio のバージョン上げればちゃんと動くのかな...?

project/build.gradle
buildscript {
    dependencies {
        ...

        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha11"
    }
app/build.gradle
apply plugin: "androidx.navigation.safeargs"

dependencies {
    ...

    implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha11'
    implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha11'
    implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha11'
}

画面遷移するだけなら必要なのは

implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha11'
implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha11'

ここだけです。

safeargs については後で出て来ますので必要であれば。。。

Navigation

Resource

まずはリソースファイルの作成をします。

いつも通りに Android Resource File を新規作成します。
ただし、今回選択する Resource Type は Navigation を選択します。

Enable Navigation Editor にチェックが入ってなければ選択できない様なので
選択肢に出てこない場合は設定しましょう。

名前はなんでもいいです。
今回は Get Started に書いてあるので nav_graph.xml にしました。

OK すると res/navigation ディレクトリが作成されてこんな感じの xml ができあがります。

nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android">

</navigation>

a04ae75b.png

Transition

Design タブの左上に + アイコンがあり、そこから Fragment を追加します。

Fragment はいつも通りに作成したものを準備しました。

  • FirstFragment
  • SecondFragment

+ アイコン から Create Blank Destination を選択すると Fragment の作成もできます。
Text タブから xml 直書きもできます。
今回はどんな風に出来上がるのかを見るために GUI で動かしていきますね。

まずは Fisrt Fragment を追加してみましょう。

nav_graph.xml
<!-- layout を追加すると Design で Fragment 内のレイアウトが確認できる -->
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          tools:layout="@layout/fragment_first"/>

fragment タグは新鮮な感じがしますねー。
同じ様に SecondFragment も追加します。

2つ揃ったらいよいよ遷移させてみます。
FirstFragment から矢印を SecondFragment に向けて引っ張るだけです。

a8984681.png

nav_graph.xml
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          tools:layout="@layout/fragment_first">

        <action android:id="@+id/action_firstFragment_to_SecondFragment"
                app:destination="@id/SecondFragment"/>

</fragment>

<fragment android:id="@+id/secondFragment"
          android:name="com.taku.navigation.fragments.SecondFragment"
          android:label="SecondFragment"
          tools:layout="@layout/fragment_second"/>

action タグが追加されてますね。
これで準備は整いました。

後は遷移させたいタイミングで

binding.button.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment)
}

みたいにやると SecondFragment に遷移します。

最終的に xml 内はこうなります。
navigation タグに startDestination を追加すると初期表示画面を選択できます。

action タグに transition を設定するとアニメーションの設定ができたりします。

nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
            app:startDestination="@id/firstFragment">

    <fragment android:id="@+id/firstFragment"
              android:name="com.taku.navigation.fragments.FisrtFragment"
              android:label="FisrtFragment"
              tools:layout="@layout/fragment_fisrt">

        <action android:id="@+id/action_firstFragment_to_SecondFragment"
                app:destination="@id/SecondFragment"/>

    </fragment>

    <fragment android:id="@+id/secondFragment"
              android:name="com.taku.navigation.fragments.SecondFragment"
              android:label="SecondFragment"
              tools:layout="@layout/fragment_second"/>
</navigation>

後は Activity の xml 内で fragment タグを追加します。
name には NavHostFragment を指定します。
NavHostFragment の中身を入れ替えて画面遷移を実現している様ですね。

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

        <fragment
                android:id="@+id/nav_host_fragment"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:name="androidx.navigation.fragment.NavHostFragment"
                app:defaultNavHost="true"
                app:navGraph="@navigation/nav_graph"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

これでアプリを起動すれば FirstFragment からスタートします。

FragmentManager が出てこない!!しゅごい!!

実際の遷移処理は FragmentNavigator がやってくれています。

findNavController で渡された View から親を辿って NavController を探し出すみたいですね。
この NavController に nav_graph.xml が bind されてグラフに基づいて navigation を管理してくれているんですね。

NavController が Navigator に繋ぎます。
navigate 関数の中で className から instantiate で Fragment を作って FragmentManager.beginTransaction.replace ってやってます。
NavOptions を使ってアニメーションの設定もゴリゴリやってくれています。

要するに今回の Navigation ライブラリは Fragment 遷移に関して言うと今まで頭を捻っていた Transaction をモダンな仕様でいい感じにラッピングしてくれているんでしょう。

  • popBackStack

Back ボタンに対応させる時は Activity で

override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()

ってやると内部で popBackStack() してくれてるっぽいので画面が戻ります。

バックスタックの管理もできます。

app:popUpTo="@id/firstFragment"

ってやると指定した Fragment から上に積まれてる Fragment を pop してくれます。

Safe args

最初に入れた safeargs とか言う怪しいプラグインはなんだったのか。
先に言うとめっちゃ感動します。

順番に見て行きましょう。

まず、fragment タグの中に argument タグが入れれるんですね。
これで Bundle の受け渡しができます。
どんな感じでやるかというと

nav_graph.xml
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          tools:layout="@layout/fragment_first">

        <action android:id="@+id/action_firstFragment_to_SecondFragment"
                app:destination="@id/SecondFragment"/>
</fragment>

<fragment android:id="@+id/secondFragment"
          android:name="com.taku.navigation.fragments.SecondFragment"
          android:label="SecondFragment"
          tools:layout="@layout/fragment_second">

        <argument android:name="message"
                  android:defaultValue="default"
                  app:argType="string"/>
</fragment>

あとは遷移元で navigate の引数に Bundle を渡してあげると
遷移先でいつものように arguments から取得できます。

// from
class FirstFragment {
    ...
    val bundle = bundleOf("message" to "to destination")
    Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment, bundle)
}

// to
class SecondFragment {
    ...
    val message = arguments?.getString("message") ?: ""
}

...すてきやん?

個人的に今回の中で一番感動してます。
わざわざ newInstance 関数生やして setArgmunets してインスタンス返さなくてもよくなった!スッキリ!

さて、これだけでも結構感動してるんですけどここから涙がちょちょぎれます。

上のやり方って、key の管理とか type が String だから getString 使うとか色々と大変なこと残ってますよね。
デフォルト値設定できるのに arguments が Nullable やし...

そんな時に使うのが Safe args !!

これを使うとどう変わるのか。デデーン

// from
class FirstFragment {
    ...
    Navigation.findNavController(it).navigate(
        FirstFragmentDirections.actionFirstFragmentToSecondFragment().apply {
            message = "to destination"
        }
    )
}

// to
class SecondFragment {
    ...
    val message = SecondFragmentArgs.fromBundle(arguments ?: return).message
}

٩(๑❛ᴗ❛๑)۶

ん〜!!!!
煩わしいことがなくなって超絶スッキリ。

ここでのポイントは DirectionsArgs。付けられた ID を元に自動生成されます。
めっちゃわかりやすくなりますよね。各 Fragment にそれぞれ Directions と Args のサフィックスがつくだけです。
使う時も名前が入ってる方って覚えておけばどっちだっけってならなくなります。

で、遷移時の navigate() の引数気づきました?
Action の ID と Bundle じゃなくて Directions しか渡してない んですよね。
渡す ID を間違えた!なんてことがなくなります。

これすごくないですか。
Args で取得したメンバーは補完が効いててキャストいらないし、設定もメソッドを選ぶだけで key 違うじゃんとかもない。
arguments 使わなくても Directions 渡せば遷移できちゃうので乱用しそう。

これだけのために Navigation 入れてもいいんじゃないかな。。。
Percelable なクラスしかやりとりできないんですけど Json ライブラリとか入れれば data class のやりとりもできそうだよね。

こんな事もできちゃうみたい。可能性しか感じていない。

感想

すっごい、Xcode のストーリーボードだなって...
ただストーリーボードの xml と違ってややこしくないから Text から直書きマンには嬉しいです。

わざわざ Activity にイベント通知して FragmentManager 呼んで Transaction 準備して Commit して...
って煩わしい処理がなくなったので、謳い文句通り簡単に遷移ができてますね。
チームでの開発とかでも Transaction の管理とかで変な差が出にくくなりそうだからいいのかも??

Safe args はほんと神だと思うので積極的に使って行きたい!!stable 版に期待大!!
Jetpack 自体がですけどリリースノートを見る限り現状かなり頻繁に更新されてますね。

DeppLink とか navigation タグのネストとか他にもありそうなので色々やってみたいなー。

参考

Navigation Codelab

Android: Navigationのsafeargs Gradle pluginだけを使ってもいいかもしれない - stsnブログ

Navigation Architecture Componentを試してみる - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シークバーのツマミのX座標の計算

シークバーのツマミの位置にViewを重ねて表示する機会があったので、備忘録としその際に使用したツマミの位置のX座標の計算方法を記載しようと思います。

シークバーのツマミのX座標をピクセルで取得

public float getThumbPositionX(SeekBar seekBar) {
    float width = seekBar.getWidth()
            - seekBar.getPaddingLeft()
            - seekBar.getPaddingRight()
            - seekBar.getThumb().getIntrinsicWidth();

    float thumbPositionX = width
            * (float) seekBar.getProgress()
            / (float) seekBar.getMax()
            + (float) seekBar.getPaddingLeft();

    return thumbPositionX
}

これでOKのはず...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む