20200213のAndroidに関する記事は8件です。

51歳から(現52)のプログラミング 備忘 Fragment 静的動的

Fragment 静的生成

Fragmentを静的に描写する流れ

1.MainActivity.classを実行すると、onCreate()でレイアウト(activity_main.xml)を生成
2.activity_main.xmlに<fragmen>タグを記載しておくと、
  レイアウト(activity_main.xml)を生成するときに、
  <fragment>を記載した表示領域でFragment.classを呼び出す
3.呼び出されたFragment.classのonCreate()でレイアウト(fragment.xml)が生成され、
  activity_main.xmlで記載した表示領域にfragment.xmlが描写される

MainActivity.class -> activity_main.xml -> Fragment.class -> fragment.xml

サンプルコード Fragmentの静的生成

MainActivity.java
@Override 
protected void onCreate(Bundle savedInstanceState){
   super.onCreate(savedInstanceState);
   setContentCView(R.layout.activity_main); // <- ここでレイアウト生成
}}
activity_main.xml
//...
<fragment
   android:id="@+id/fragmentArea"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   class="(パッケージ名).Fragment" //<- ここでFragment.javaを呼び出す
   />
Fragment.java
public class Fragment extends Fragment{
   @Override 
   public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState){
      return inflater.inflate(R.layout.fragment,container,false);// <- fragmen.xmlをインフレート!
   }

   @Override
   public void onActivityCreated(Bundle savedInstanceState){ // Fragment生成後の処理
      super.onActivityCreated(savedIndtanceState);
      TextView text = getActivity().findViewById(R.id.text);
      text.setText("From Fragment !");
   }
}
fragment.xml
//...
<TextView
   android:id="@+id/text"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   />
}

Fragment 動的生成

Fragmentを動的に生成する流れ

1.MainActivity.classを実行すると、onCreate()でレイアウト(activity_main.xml)を生成
2.activity_main.xmlで<frameLayout>を記載し、Fragmentの表示領域を確保
3.MainActivity.classで、Fragmentオブジェクトを生成して、
  fragmentTransactionで、activity_main.xmlの<frameLayout>にFragmentオブジェクトを配置

MainActivity.class -> activity_main.xml -> fragmentTransaction

サンプルコード Fragmentの動的生成

activity_main.xml
<frameLayout
   android:id="@+id/fragmentArea"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   />
MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   FragmentManager manager = getSupportFragmentManager();
   FragmentTransaction transaction = manager.beginTransaction();//managerでTransactionを開始
   Fragment fragment = new Fragment();          //Fragmentオブジェクト生成
   transaction.add(R.id.fragmentArea,fragment); //transactionでactivity_main.xmlの\<framLayout>の描写領域にfragmentオブジェクトをセット
   transaction.commit();
Fragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment,container,false); // <-レイアウト(fragment.xml)をインフレート!
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) { // fragmentオブジェクト生成後の処理
    super.onActivityCreated(savedInstanceState);
    TextView text = (TextView) getActivity().findViewById(R.id.text);
}
fragment.xml
<TextView
   android:id="@+id/text"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   />

サンプルコード Fragmentで画面遷移風

ファイル構成

020215-1.JPG

activity_main.xml
<fragment
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/fragmentArea"
    class="cat.baby.sample.Fragment_Toolbar"
    />

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/frameArea"
    />
MainActicity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    Fragment01 fragment_main = new Fragment01("1");
    transaction.add(R.id.frameArea,fragment_main);
    transaction.commit();
}
Fragment_Toolbar.java
AppCompatActivity activity;
FrameLayout frameLayout;
FragmentManager manager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

    activity    = (AppCompatActivity) getActivity();
    frameLayout = getActivity().findViewById(R.id.frameArea);
    manager     = activity.getSupportFragmentManager();

    View view       = inflater.inflate(R.layout.fragment_toolbar,container,false);
    Toolbar toolbar = view.findViewById(R.id.toolbar);
    activity.setSupportActionBar(toolbar);
    setHasOptionsMenu(true);
    return view;
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.overflow_menu01,menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()){
        case R.id.menu01:

            FragmentTransaction transaction_menu01 = manager.beginTransaction();
            Fragment01 fragment_main = new Fragment01("1");
            transaction_menu01.replace(R.id.frameArea,fragment_main);
            transaction_menu01.commit();
            return true;

        case R.id.menu02:

            FragmentTransaction transaction_menu02 = manager.beginTransaction();
            Fragment01 fragment_second = new Fragment01("2");
            transaction_menu02.replace(R.id.frameArea,fragment_second);
            transaction_menu02.commit();
            return true;

        case R.id.menu03:
            FragmentTransaction transaction_menu03 = manager.beginTransaction();
            Fragment01 fragment_third = new Fragment01("3");
            transaction_menu03.replace(R.id.frameArea,fragment_third);
            transaction_menu03.commit();

    }
    return super.onOptionsItemSelected(item);
}
Fragment01.java
int fragmentRes;
public Fragment01(String no) {
    switch(no){
        case "1":
            fragmentRes = R.layout.fragment_main;
            break;
        case "2":
            fragmentRes = R.layout.fragment_second;
            break;
        case "3":
            fragmentRes = R.layout.fragment_third;
            break;
        default:
            fragmentRes = R.layout.fragment_main;
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
    View view = inflater.inflate(fragmentRes,container,false);
    return view;
}
fragment_toolbar.xml
<androidx.appcompat.widget.Toolbar
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="#abb"
    android:id="@+id/toolbar"
    />
fragment_main.xml
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="main"
    />
fragment_second.xml
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="second"
    />
fragment_third.xml
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="third"
    />

でもこれだけじゃ、戻れない!

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

AAC Navigationでnavigate同時実行時のクラッシュへの対処法

問題

例えば、AAC Navigationのnavigate()するボタンを2つ同時に押すなどした時に以下のように怒られクラッシュする。

2020-02-13 15:24:08.709 21427-21427/jp.studysapuri.for_school.dev.debug E/AndroidRuntime: FATAL EXCEPTION: main
    Process: jp.studysapuri.for_school.dev.debug, PID: 21427
    java.lang.IllegalArgumentException: navigation destination jp.studysapuri.for_school.dev.debug:id/action_mypageFragment_to_searchSchoolByConditionFragment is unknown to this NavController

原因

navigate()が2つ同時に実行された場合、2つ目の実行時には現在の画面(ナビゲーションgraphのnode)が1つ目の遷移先になっているため、画面に紐つくactionが無いことになってしまう。

回避策

今いるnode(画面)に紐つくactionが有るかを確認しあればnavigate()を実行、なければ何もしないようにするような拡張関数(以下の例ではnavigateSafe())を作り、navigate()の代わりに使う。

import android.os.Bundle
import androidx.annotation.IdRes
import androidx.navigation.*

fun NavController.navigateSafe(
    @IdRes resId: Int,
    args: Bundle? = null,
    navOptions: NavOptions? = null,
    navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(resId, args, navOptions, navExtras)
    }
}

fun NavController.navigateSafe(
    node: NavDirections,
    navOptions: NavOptions? = null
) {
    val action = currentDestination?.getAction(node.actionId) ?: graph.getAction(node.actionId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(node, navOptions)
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TextView内の特定の文字列に対して手軽にタップイベントを追加する

Androidアプリ内のTextViewに対してWebのハイパーリンクのようなタップイベントをつけたい事がたまによくあるので、通常のonClickイベントと同じ使い勝手でなるべく簡単に扱えるようKotlinの拡張関数を書いてみました

使用感イメージ

下記のスクリーンショットのように使うイメージです。
よくある利用シーンとしては「利用規約」や「プライバシーポリシー」の文言にタップイベントをつけたりするときとかですかね

利用側のコードもここまで簡潔に記述するのを目的にしてみます

MainActivity.kt
hyperLinkTextView.text = "Hello World!"
hyperLinkTextView.addHyperLink("Hello") {
    Toast.makeText(this, "'Hello' clicked!", Toast.LENGTH_SHORT).show()
}

ソースコード

実際のタップイベントや文字色の変更、下線を引いたりするのはSpannableStringを使用しますが、ベタ書きすると煩雑なコードになりやすいので今回の利用用途に特化した拡張関数を書いてみます。

HyperLinkExtension.kt
fun TextView.addHyperLink(linkText: String, callback: ((view: View) -> Unit)) {
    val spannableMessage = SpannableString(text)
    val pattern = Pattern.compile(linkText)
    val matcher = pattern.matcher(text)
    while (matcher.find()) {
        spannableMessage.setSpan(object : ClickableSpan() {
            override fun onClick(textView: View) {
                callback.invoke(textView)
            }
        }, matcher.start(), matcher.end(), Spanned.SPAN_INCLUSIVE_INCLUSIVE)
    }
    text = spannableMessage
    movementMethod = LinkMovementMethod.getInstance()
}

また、追加でこんな形の引数をとる拡張関数も同時に生やしておくと便利かもしれません

HyperLinkExtension.kt
fun TextView.addHyperLink(@StringRes vararg linkTextRes: Int, callback: ((view: View) -> Unit)) {
    linkTextRes.map { addHyperLink(it, callback) }
}

fun TextView.addHyperLink(@StringRes linkTextRes: Int, callback: ((view: View) -> Unit)) {
    addHyperLink(context.getString(linkTextRes), callback)
}

fun TextView.addHyperLink(vararg linkText: String, callback: ((view: View) -> Unit)) {
    linkText.map { addHyperLink(it, callback) }
}

制限

今回書いたコードでは利用側の簡潔さと引き換えに以下の制限が存在します。

  • 判定に含まれる文字列が複数ある場合はすべてタップイベントの対象となります。
  • 同じTextViewに対してaddHyperLink()を複数使用しタップイベントを適用する文字列が重なった場合は、先にaddHyperLink()したタップイベントが優先的に処理されます。
  • 内部的にsetText()を行なっているため、改めて外側からsetText()を行なった場合は条件に一致していたとしてもリンクが無効になります。

この辺りが要件に合わない場合は内部の判定の仕方や代入の方法を調整してみてください :bow:
あと正規表現とか駆使して判定したい方はMatcherあたりをごにょってもらえるといいかもしれないです。

参考

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

【Android】アプリを多言語化/ローカライズしたい(RTL対応あり)

概要

作成していたAndroidアプリを国外で配信する可能性があったため、アプリを日本語以外にも対応させたかった。
そこで、アプリを多言語化(ローカライズ)する方法や、アラビア語などの右読み言語(RTL; Right To Left)の場合はどうするかを調べてまとめた。

多言語対応方法

リソースを用意する

<アプリプロジェクト>/app/src/main/resの中には、例えば以下のようなリソースのディレクトリが格納されている。

  • drawable(画像)
  • values(色や文字列などの値)
  • layout(レイアウト)

対応したい言語のリソースディレクトリを用意すると、Android端末の設定を拾って、その言語に対応した表示になる。
ディレクトリの名前は次のようにする。

  • <リソースのタイプ>-<言語コード>[-r<地域コード>]
    res/values-jares/values-en-rGBなど
  • <リソースのタイプ>-b+<言語コード>[+<地域コード>]
    res/values-b+en+001など

そしてディレクトリの中に該当するリソースを用意する。

res/values/strings.xml
<resources>
  <string name="title">Title</string>
<resources>
res/values-ja/strings.xml
<resources>
  <string name="title">タイトル</string>
<resources>

※端末の設定に当てはまるものがない場合、読み込むリソースがなくなりアプリの強制終了などが起こり得るため、言語を指定しないデフォルトのリソースディレクトリを用意しておく。

多言語対応の要素を表示する

上で用意したリソースを要素に指定する。
指定の仕方は@<リソースのタイプ>/<リソースのname>

main.xml
<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/title" />

これで、言語設定が日本語の場合は"タイトル"、それ以外の場合"Title"という文字列が表示される。

多言語対応の要素をコードで利用する

R.<リソースのタイプ>.<リソースのname>で利用することができる。

Main.kt
textView.text = R.string.title // TextViewにテキストをセットする

RTL、双方向テキストへの対応

アラビア語は右から左に読む(RTL)。
             مرحبا هناك

しかし、テキスト中に数字・左から右に読む言語(LTR; 英語など)を含む場合は、その部分はLTRになる。
             مرحبا هناك12345678←

基本的にはシステムのデフォルト処理によってその通りに表示されるが、ローカライズしたテキストに逆方向のテキストを挿入した場合などは適当な表示にならないことがある。

  • メッセージの先頭に挿入されている
  • 数字や区切り記号で始まる

その場合はBidiFormatterクラスのunicodeWrap()メソッドを用いる。

また、Android 4.2(APIレベル17)以上ではレイアウトミラーリングもサポートされている。
AndroidManifest.xmlに以下を記述する。

AndroidManifest.xml
<application
    ...(略)...
    android:supportsRtl="true">
</application>

参考記事

Localize your app
複数の言語と文化をサポートする
言語とロケール
代替リソースを提供する

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

各OSのBluetoothの仕様

各OSのBluetoothの仕様

iOS

場所

25.5 Advertising Interval
The accessory should first use the recommended advertising interval of 20 ms for at least 30 seconds.
If it is not discovered within the initial 30 seconds, Apple recommends using one of the following longer
intervals to increase chances of discovery by the device:
● 152.5 ms
● 211.25 ms
● 318.75 ms
● 417.5 ms
● 546.25 ms
● 760 ms
● 852.5 ms
● 1022.5 ms
● 1285 ms

推奨される広告パターンと広告間隔は次のとおりです。
最初に、20ミリ秒間隔で少なくとも30秒間アドバタイズします
30秒後に検出されない場合は、152.5ミリ秒、211.25ミリ秒、318.75ミリ秒、417.5ミリ秒、546.25ミリ秒、760ミリ秒、852.5ミリ秒、1022.5ミリ秒、1285ミリ秒のいずれかの長い間隔に変更できます。
重要: これらの特定の間隔は、正確に使用される間隔です!これらの実際の間隔からのわずかな偏差でさえ、発見までの時間を劇的に増加させる可能性があります。

Android

The blog post “What to keep in mind when developing your BLE Android app” contains a section with advertising. According to the blog, the allowed advertising intervals are the following:
a. ADVERTISE_MODE_LOW_LATENCY – Advertising interval: 100 ms
b. ADVERTISE_MODE_BALANCED – Advertising interval: 250 ms
c. ADVERTISE_MODE_LOW_POWER – Advertising interval: 1000 ms

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

BleManager option

BleManageroption

BleManager

スキャンoptionでconnect成功率、速度向上できます

Android

まずAndroidじゃ!

  • scanningOptions - JSON - [Android only] after Android 5.0, user can control specific ble scan behaviors:

    • numberOfMatches - Number - corresponding to setNumOfMatches
    • matchMode - Number - corresponding to setMatchMode
    • scanMode - Number - corresponding to setScanMode
    • reportDelay - Number - corresponding to setReportDelay // 現在サポートされていない模様
  • scanningOptions - JSON - [Androidのみ] Android 5.0以降、ユーザーは特定のbleスキャン動作を制御できます。

    • numberOfMatches - Number - Bluetooth LEスキャンフィルターのハードウェア一致の一致数を設定する
    • matchMode - Number - Bluetooth LEスキャンフィルターの一致モードをハードウェア一致に設定する
    • scanMode - Number - Bluetooth LEスキャンのスキャンモードを設定します。

### 詳細

  • numberOfMatches

    • MATCH_NUM_MAX_ADVERTISEMENT
    • hwのリソースの現在の機能と可用性に依存します。
    • 定数値:3(0x00000003)
    • MATCH_NUM_FEW_ADVERTISEMENT
    • フィルターごとにいくつかの広告に一致します。hwのリソースの現在の機能と可用性に依存します
    • 定数値:2(0x00000002)
    • MATCH_NUM_ONE_ADVERTISEMENT
    • フィルターごとに1つの広告に一致
    • 定数値:1(0x00000001)
  • setMatchMode

    • MATCH_MODE_AGGRESSIVE
    • アグレッシブモードでは、信号強度が弱く、期間中に目撃/一致の数が少ない場合でも、hwは一致をより早く決定します。
    • 定数値:1(0x00000001)
    • MATCH_MODE_STICKY
    • スティッキーモードの場合、hwでレポートする前に、信号強度と目撃のより高いしきい値が必要です。
    • 定数値:2(0x00000002)

      • scanMode
      • SCAN_MODE_LOW_POWER
      • 低電力モードでBluetooth LEスキャンを実行します。これは最小の電力を消費するため、デフォルトのスキャンモードです。このモードは、スキャンアプリケーションがフォアグラウンドにない場合に適用されます。
      • 定数値:0(0x00000000)
      • SCAN_MODE_BALANCED
      • バランス電源モードでBluetooth LEスキャンを実行します。スキャン結果は、スキャン頻度と消費電力のバランスが取れたレートで返されます。
      • 定数値:1(0x00000001)
      • SCAN_MODE_LOW_LATENCY
      • 最高のデューティサイクルを使用してスキャンします。アプリケーションがフォアグラウンドで実行されている場合にのみ、このモードを使用することをお勧めします。
      • 定数値:2(0x00000002)
      • SCAN_MODE_OPPORTUNISTIC
      • 特別なBluetooth LEスキャンモード。このスキャンモードを使用するアプリケーションは、BLEスキャンを開始せずに他のスキャン結果を受動的にリッスンします。
      • 定数値:-1(0xffffffff)

iOS

まず参考記事

For example, when your app is relaunched by system, you can retrieve all the restoration identifiers for the central manager objects the system was preserving for your app, like this:
After you have the list of restoration identifiers, simply loop through it and reinstantiate the appropriate central manager objects.

システムがアプリのために保存していた中央マネージャーオブジェクトのすべての復元識別子を取得できます。
復元識別子のリストを取得したら、単純にループして、適切な中央マネージャーオブジェクトを再インスタンス化します。

効果

前のスキャンでconnectしたBLEに対してスキャンすっ飛ばしてconnectするようになるので劇的に早くなる(3秒)
見つけられなければ通常スキャンに切り替わる

使い方

  • xcodebackgroud processing on
  • infoにrestoreIdentifierKey任意のキー追加
  • retrieveServicesooption指定すればokよ

例 

if (!isAndroid) {
BleManager.start({ showAlert: false, restoreIdentifierKey: 'bitkey', forceLegacy: true });
}

気をつけないと行けないのがretrieveServicesooptionを指定してしまうとAndroidのスキャンが正常に動かないので
条件分岐などで避けてください!

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

TensorFlow Liteを使用して、Androidで人間のポーズをリアルタイムで追跡する

オンデバイスインテリジェンスのためのGoogle Coralソリューション:

✅姿勢推定:さまざまな身体の関節を特定することにより、画像内の人々の姿勢を推定します。

alt

PoseNetは、主要な身体部分の位置を検出することにより、画像またはビデオ内の人物の姿勢を推定するビジョンモデルです。 例として、モデルは画像内の人の肘や膝の位置を推定できます。 ポーズ推定モデルは、画像内の人物を識別しません。 主要な身体部分の位置のみ。

TensorFlow Liteは、デバイスのカメラを使用してリアルタイムで1人の主要な身体部分を検出および表示するAndroidサンプルアプリケーションを共有しています。

alt

このサンプルアプリケーションを使用すると、アプリ開発者や機械学習の専門家が軽量モバイルモデルの可能性を簡単に探ることができます。

PoseNetサンプルアプリケーション

Javaで記述された既存のAndroidサンプルとは対照的に、PoseNetサンプルアプリはKotlinで開発されました。 このアプリの開発目標は、誰でも簡単にPoseNetモデルを最小限のオーバーヘッドで使用できるようにすることでした。 サンプルアプリには、モデルの複雑さを抽象化するPoseNetライブラリが含まれています。 以下の図は、アプリケーション、PoseNetライブラリ、およびTensorFlow Liteライブラリ間のワークフローを示しています。

alt

PoseNetライブラリ

PoseNetライブラリは、処理されたカメラ画像を取得し、人の主要な身体部位がどこにあるかに関する情報を返すインターフェースを提供します。 この機能は、推定されたRGBビットマップでTensorFlow Liteインタープリターを実行し、Personオブジェクトを返すメソッドであるtimateSinglePose()によって提供されます。 このページでは、PoseNetの入力と出力を解釈する方法について説明します。

// Estimate the body part positions of a single person.
// Pass in a Bitmap and obtain a Person object.
estimateSinglePose(bitmap: Bitmap): Person {...}

Personクラスには、主要な身体部分の位置とそれに関連する信頼スコアが含まれています。 人の信頼スコアは、各キーポイントの信頼スコアの平均です。これは、キーポイントがその位置に存在する確率を示します。

// Person class holds a list of key points and an associated confidence score.
class Person {
  var keyPoints: List<KeyPoint> = listOf<KeyPoint>()
  var score: Float = 0.0f
}

各KeyPointは、特定のBodyPartの位置とそのキーポイントの信頼スコアに関する情報を保持します。

// KeyPoint class holds information about each bodyPart, position, and score.
class KeyPoint {
  var bodyPart: BodyPart = BodyPart.NOSE
  var position: Position = Position()
  var score: Float() = 0.0f
}

// Position class contains the x and y coordinates of a key point on the bitmap. 
class Position {
  var x: Int = 0
  var y: Int = 0
}

// BodyPart class holds the names of seventeen body parts.
enum class BodyPart {
  NOSE, 
  LEFT_EYE, 
  RIGHT_EYE, 
  ... 
  RIGHT_ANKLE
}
PoseNetサンプルアプリ

PoseNetサンプルアプリは、カメラからフレームをキャプチャし、画像上のキーポイントをリアルタイムでオーバーレイするデバイス上のカメラアプリです。
アプリケーションは、着信カメラ画像ごとに次の手順を実行します。

  • カメラプレビューから画像データをキャプチャし、YUV_420_888からARGB_888形式に変換します。
  • RGB形式のフレームデータのピクセルを保持するBitmapオブジェクトを作成します。 モデルに渡すことができるように、ビットマップを切り取ってモデルの入力サイズに合わせます。
  • PoseNetライブラリーのtimateSinglePose()関数を呼び出して、Personオブジェクトを取得します。
  • ビットマップを画面サイズに戻します。 Canvasオブジェクトに新しいビットマップを描画します。
  • Personオブジェクトから取得したキーポイントの位置を使用して、キャンバスにスケルトンを描画します。 特定のしきい値(既定では0.5)を超える信頼スコアを持つキーポイントを表示します。

ポーズレンダリングをカメラフレームと同期するために、ポーズとカメラの個別のViewインスタンスの代わりに、単一のSurfaceViewが出力表示に使用されました。 SurfaceViewは、Viewキャンバスを取得、ロック、およびペイントすることにより、画面を遅滞なく画面上に配置します。

デバイス上で実行する

GitHubからソースコードをダウンロードし、READMEを参照して実行方法を確認して、アプリを試してみることをお勧めします。

ロードマップ上

将来的には、このサンプルアプリのその他の機能を検討していきたいと考えています。
- マルチポーズ推定
- GPUデリゲートを使用したGPUアクセラレーション
- NNAPIデリゲートを使用したNNAPIアクセラレーション
- レイテンシを減らすためのモデルのトレーニング後の量子化
- ResNet PoseNetモデルなどの追加のモデルオプション

(Eileen MaoとTanjin Prity、Googleのエンジニアリング実習インターン)

alt

Google Coral海外代理店:Gravitylink (https://store.gravitylink.com/global)

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

【Android】Navigationの遷移先にあわせてToolbarをカスタマイズする

はじめに

みなさん、Navigation Component使ってますか?

Fragment間の画面遷移や値の受け渡しなどを非常に楽にしてくれるNavigationですが、ToolBarと併せて使用した時にいくつか詰まったポイントがありましたので、本記事で紹介したいと思います。

※2020/02/14 FragmentContainerViewについて追記しました

この記事で紹介すること

  • Navigation + Toolbarを扱う時に困りがちなことの解決方法

この記事で紹介しないこと

  • Navigationの基本的な使い方

Navigationの使い方を知りたい場合はこちらの記事が参考になります。

リポジトリ

こちらに今回のサンプルを置いておきます。
https://github.com/nanaten/Navigation-and-Toolbar-Sample

前提

Activityのレイアウトはこんな感じです。
ActivityにToolbarとFragmentを乗っけています。
styleNoActionBarにしています。

activity_main.xml
<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">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.google.android.material.appbar.AppBarLayout>

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/appbar"
        app:navGraph="@navigation/main_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

Tips

1. 特定のFragmentだけUpアイコンを表示させない

ログイン後のホーム画面など、この画面にはUpアイコン(←)を表示させたくない、という場合があると思います。
そういう時は AppBarConfigration にUpアイコンを表示させたくないFragmentのIDを Set で渡します。

val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(setOf(R.id.firstFragment))
setupActionBarWithNavController(navController, appBarConfiguration)

アイコンを表示させたくないFragmentが複数ある場合は、以下のように複数のIDを渡せます。

val appBarConfiguration = AppBarConfiguration(setOf(R.id.firstFragment, R.id.secondFragment))

2. 特定のFragmentだけToolbarを消したい

特定のFragmentに移動した時だけToolbarを表示させないようにしたい!と思ったことはありませんか?
Fragmentから操作しても良いのですが、なんとなくFragmentからActivityにあるコントロールを操作するのは気持ち悪い…
と思っていたら、navController.addOnNavigationDestinationListenerという便利なリスナーが存在しました。

val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { controller, destination, arguments ->  }

例えば ThirdFragment に移動した時だけToolbarを消したい、という場合は以下のように使えます。

val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
    toolbar.visibility = if(destination.id == R.id.thirdFragment) View.GONE else View.VISIBLE
}

3. タイトルを動的に切り替える

例えばリスト選択→詳細表示へ遷移した際に、選択した項目に合わせて動的にタイトルを設定したいという場合、以下の方法が使えます。

  1. NavGraphで設定する
  2. addOnDestinationChangedListener で設定する

準備

どちらの方法も準備は同じです。
NavGraphでタイトル用のargumentを設定します。

NavGraph
    <fragment
        android:id="@+id/fourthFragment"
        ...>
        <argument
            android:name="title"
            app:argType="string" />
    </fragment>

SafeArgsでFragmentに引数を渡してあげます。
SafeArgsについてはこちらの記事が参考になります。

val title = "test title"
val args = FirstFragmentDirections.actionFirstToFourth(title)
findNavController().navigate(args)

1. NavGraphで設定する

NavGraphで設定する場合は、fragmentのラベルに以下のように引数名を設定します。

NavGraph
    <fragment
        android:id="@+id/fourthFragment"
        ...
        android:label="{title}">
        <argument
            android:name="title"
            app:argType="string" />
    </fragment>

これだけで引数に渡した値がタイトルとして表示されるようになります!

2. addOnDestinationChangedListenerで設定する

こちらは先ほどの addOnDestinationChangedListener を用いた方法になります。
3番目の引数 arguments からFragmentへ渡したargumentが取得できるため、そちらを利用します。

navController.addOnDestinationChangedListener { _, _, arguments ->
    if(arguments?.getString("title") != null) {
        supportActionBar?.title = arguments.getString("title")
    }
}

destination でIDを判別しても良いですが、上記のようにargumentsの取得結果がnullかどうかで判別すれば、複数箇所で title というargumentを使っている場合に対応が簡単になります。

Toolbarの設定をActivityで一括管理したい場合は addOnDestinationChangedListener を使うのも一つの手かもしれません。

おまけ FragmentContainerViewを使う場合のfindNavControllerのやり方

自分でやってみて詰まったのでメモ代わりに追記しておきます。
FragmentContainerView とは、今までFragmentのコンテナとして利用されていたFrameLayoutの代替Viewとして登場したものです。
詳しい解説はこちらの記事が参考になります。

レイアウトの fragment を以下のように置き換えることが可能ですが、普通にonCreateで findNavController すると「NavControllerが見つからない」というエラーが発生します。

activity_main.xml
<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.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/appbar"
        app:navGraph="@navigation/main_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
エラーが出る書き方
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val navController = findNavController(R.id.nav_host_fragment)
    ...
}

こちらにIssueがありますが、FragmentContainerView に対して、onCreateの段階では fragmentManager がINITIALIZING状態にあるために起きているそうです(たぶん…)

FragmentContainerView を使う場合は、 navController の取得方法を以下のように修正する必要があります。

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

FragmentContainerView から直接ではなく、FragmentContainerView にアタッチしたNavHostFragmentから取得しています(たぶん…)

おわりに

本記事を書くにあたって、以下のリンク先を参考にさせて頂きました。
[stsnブログ] メモ Android: Navigation Component + Toolbar(ActionBar)周りのコードを読んで見る
[Qiita] 特定のFragmentが表示される時にAppBarLayoutとBottomNavigationViewを非表示にする
[Kenji Abe] Navigationでタイトルを設定する

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