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

Android x86 9.0-r1 をHP Pavilion 11-k128TU x360にインストールしてみた

最初、Chromium OSをインストールしようとしたんだけど、CloudReadyはUSBメモリから起動はするけどインストールできないし、ChromiumOSのイメージをUSBメモリに入れても起動しないし、もう、ChromiumOSあきらめた!
ということで、Android x86を入れてみた。
ちなみに、HP Pavilion 11-k128TU x360はノートPCの状態から、キーボードを360度回転させることでタブレットとしても使える2015年発売のPCです。

入れ方とかはググれば出てくるので、インストールしてみてどうなのかを書き残しておく。

書いててスクショ取ろうとしたら、許可されてなくてスクショが撮れなかったのでスマホのカメラで撮影することになるとは・・・
P_20200305_232142.jpg

タブレットのようにつかえるのか?

このPC、冒頭にも触れましたが、タブレットとしても使えるPCです。
ある程度、キーボードを回転させると、キーボードが無効になって、タブレットモードになります。
買った時のようにWindows10が入っていれば。
Android x86を入れると、タブレットモードにしても、キーボードが有効なままになります。
おそらく、出荷時のWindows10では専用ドライバが入っていて対応していたのではないかと思います。
そんな状況なので、タブレットのように使うのはあまり向いていません。
常に物理キーボードが接続されている状態になるので、折りたたんでも、仮想キーボードが出ないので、設定で物理キーボードが接続されていても仮想キーボードが出るようにしないといけません。
キーボードを回転させて、自立させることもでき、その場合に入力が困るので、使い方次第ではこの設定は必須となります。
P_20200305_232221.jpg

アプリは動くのか?

ゲームはMinecraftは動きます。
他にいくつか入れてみましたが、ほぼ起動せずに落ちます。

ランチャーはNova Launcharは問題なく動きます。
Win X Launcher、Win 10 Launcherをれてみましたが、なんか動作が怪しいところがありました。
この辺入れるとPCっぽくなって面白いんですけどね^^;

お絵かき関係でメディパンペイント、アイビスペイントXを入れてみました。
メディパンペイントは起動時に落ちましたが、アイビスペイントXは起動しました。お絵かきもできました。

音楽関係はMusic StudioとKORG Kaossilatorを入れてみました。
Music Studioは起動時に落ちました。
KORG Kaossilatorはちゃんと動きました。

その他Amazon Prime Video、Amazon Kindleなどを入れましたが、動くもの動かないものがありました。

という感じで、動かないアプリが割とあります。

とりあえず、Youtube、Twitterは動くので、仕事しながら動画見たりTwitter見ることはできます。
あ、Twitterはアプリより、PWA版を使ったほうが使いやすいです。

他のPCにAndroid x86を入れた場合にどうなるかわかりませんが・・・

PWAを試してみた

なぜかインストールしてもアイコンが出なかったりしました。
twitterは出るようになったけど、自作PWAのLT Timerはなぜかアイコンが表示される。
ちゃんとしたAndroid端末やiPadでは動くので、PWAの開発や動作確認ではあまり使わないほうが良いのかも。

開発環境として使えるのか

AIDEがインストール可能で動きます。
課金すればUIもデザイナで編集可能です。
P_20200305_230413.jpg

ただ、Android Studioとは違った操作性なので慣れがかなり必要そう。
とりあえず、開発は可能・・・と思ったんですがなにか謎のエラーが出て、動かない・・・
P_20200305_230330.jpg
エラーも文字化けしててなんだかわからない・・・
これ、AIDEの設定ファイルか何かなのかな?

ちなみに、AIDEはJavaコンソールアプリやC/C++のコンソールアプリ、PhoneGapなどの開発も可能です。
Javaコンソールは実行までできたので、Javaの勉強は出来そうです。
PhoneGapやHTML,CSS,JavaScriptは追加でインストールが必要となります。

終わりに

Android x86はYoutubeを見たり、twitterを見たりする用途には使用できます。
ゲームはPCによる可能性がありますが、期待はしないほうがいいです。
開発は、エラーが何とかなればAndroidアプリの開発ができるんじゃないかと思います。
ただ、CPUがarmではないので、NDK周りがどうなのか気になります。
また、動作確認で利用することにも向いていないと思います。

Android x86でAIDEでなんとかAndroidアプリをビルドしてみたい!

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

【Android開発】MotionLayoutを触ってみる

MotionLayoutとは

Viewのアニメーションを管理できるViewGroupです。

ConstraintLayoutのサブクラスで、ConstraintLayoutを利用しているレイアウトなら、まるっとMotionLayoutに置き換えることができます。

※MotionLayoutはAPIレベル14との下位互換性があります。

導入

gradleに依存関係を追加します。
MotionLayoutは ConstraintLayoutのライブラリの2.0.0から導入 されたものですので、ConstraintLayoutのバージョンを上げる必要があります。
※2020/3/5の時点での最新バージョンは2.0.0-beta4でした。

build.gradle(app)
// AndroidXを導入している場合はこちら
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'

// それ以外はこちら
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'

MotionLayoutで利用するアニメーションファイル※1を入れるためのXMLフォルダ※2を作成します。

※1 厳密にはアニメーション開始時と終了時のViewの状態を記述したファイルです。
※2 AndroidStudioのメニューから、File > New > Folder > XML Resource Folderで作成。

触ってみる

凝ったレイアウトを作る時間がないので、デフォルトの activity_main.xml にMotionLayoutを適応してみます。

activity_main.xml
<!-- ConstraintLayoutからMotionLayoutに置き換えました。 -->
<androidx.constraintlayout.motion.widget.MotionLayout
    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"
    >

    <!-- アニメーションさせたいViewにidをつけます。 -->
    <TextView
        android:id="@+id/main_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</androidx.constraintlayout.motion.widget.MotionLayout>

次に res/xml/ にアニメーションファイルを作成します。

res/xml/motion_scene_main.xml
<!-- MotionScene※3タグで大枠を作ります。xmlns:motion="~"を忘れずに。 -->
<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto"
    >

    <!-- 開始、終了アニメーションの指定と実行時間を記述します。 -->
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000"
        >
        <!-- トリガーとなるアクションとViewのidを紐付けます。 -->
        <OnClick motion:targetId="@id/main_text_view"/>
    </Transition>

    <!-- アニメーション開始時のViewの状態を記述します。 -->
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            />
    </ConstraintSet>

    <!-- アニメーション終了時のViewの状態を記述します。 -->
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            />
    </ConstraintSet>

</MotionScene>

※3 xmlns:motion="~"を含めれば、MotionSceneという命名でなく、どのような名前を付けても認識してくれます。

最後に、MotionLayoutとアニメーションファイルを紐づけてやります。

activity_main.xml
<androidx.constraintlayout.motion.widget.MotionLayout

     省略 

    app:layoutDescription="@xml/motion_scene_main"
    >

端末で確認するとしてみます。
テキストをタップするとしっかりアニメーションしていますね。
20200305.gif

MotionLayoutはView自体をアニメーションさせるだけでなく、Viewのパラメータを動的に変更することもできます。
アニメーションファイルに少し手を加えてみます。

res/xml/motion_scene_main.xml
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto"
    >

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000"
        >
        <OnClick motion:targetId="@id/main_text_view" />
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            >
            <!-- アニメーション開始時のテキストの色を指定します。 -->
            <CustomAttribute
                motion:attributeName="textColor"
                motion:customColorValue="#ff0000"
                />
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            >
            <!-- アニメーション終了時のテキストの色を指定します。 -->
            <CustomAttribute
                motion:attributeName="textColor"
                motion:customColorValue="#0000ff"
                />
        </Constraint>
    </ConstraintSet>

</MotionScene>

20200305_2.gif

CustomAttribute というタグは、attributeNameに指定したViewのパラメータをアニメーションに合わせて変更してくれます。
色を変える他に、アニメーションに合わせてViewを変形させたり、画像をクロスフェードさせたりできます。
CustomAttributeを複数指定することももちろん可能です。

ここでは特に触れないので気になった方は調べてみてください。

追記1

上記ではアニメーションファイルにトリガーとなるViewを紐づけていましたが、実際のプロジェクトでは、コードで指定する方が一般的かと思います。

res/xml/motion_scene_main.xml
<!-- ここは削除して構いません。 -->
<OnClick motion:targetId="@id/main_text_view" />
MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val motionLayout = findViewById<MotionLayout>(R.id.main_motion_layout)
        // トリガーとなるViewのクリック時に、指定したid※4のアニメーションを開始します。
        findViewById<TextView>(R.id.main_text_view).setOnClickListener {
            // 現在のアニメーションの状態を判定して、開始するアニメーションを決めます。
            if (motionLayout.currentState == R.id.start) {
                motionLayout.transitionToEnd()
            } else {
                motionLayout.transitionToStart()
            }
        }
    }
}

※4 アニメーションファイルで定義したConstraintSetのidです。

追記2

要件によってはアニメーションの進行状況を検知したい場合があると思います。
そんな時は、 MotionLayout.TransitionListener をセットすればコールバックが得られます。

MainActivity.kt
motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
    override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
        // トリガーした時に呼ばれます。
    }

    override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
        // アニメーション開始時に呼ばれます。
    }

    override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
        // アニメーション中に呼ばれます。
    }

    override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
        // アニメーション終了時に呼ばれます。
    }
})

感想

MotionLayoutの魅力は、ほぼ既存の実装に影響せず、線形補完の滑らかなアニメーションで手軽にリッチな表現ができるところにあると思いました。また、学習コストが比較的低いのも◎。

参考

https://developer.android.com/training/constraint-layout/motionlayout

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

【Android】MotionLayoutを触ってみる

MotionLayoutとは

Viewのアニメーションを管理できるViewGroupです。

ConstraintLayoutのサブクラスで、ConstraintLayoutを利用しているレイアウトなら、まるっとMotionLayoutに置き換えることができます。

※MotionLayoutはAPIレベル14との下位互換性があります。

導入

gradleに依存関係を追加します。
MotionLayoutは ConstraintLayoutのライブラリの2.0.0から導入 されたものですので、ConstraintLayoutのバージョンを上げる必要があります。
※2020/3/5の時点での最新バージョンは2.0.0-beta4でした。

build.gradle(app)
// AndroidXを導入している場合はこちら
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'

// それ以外はこちら
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'

MotionLayoutで利用するアニメーションファイル※1を入れるためのXMLフォルダ※2を作成します。

※1 厳密にはアニメーション開始時と終了時のViewの状態を記述したファイルです。
※2 AndroidStudioのメニューから、File > New > Folder > XML Resource Folderで作成。

触ってみる

凝ったレイアウトを作る時間がないので、デフォルトの activity_main.xml にMotionLayoutを適応してみます。

activity_main.xml
<!-- ConstraintLayoutからMotionLayoutに置き換えました。 -->
<androidx.constraintlayout.motion.widget.MotionLayout
    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"
    >

    <!-- アニメーションさせたいViewにidをつけます。 -->
    <TextView
        android:id="@+id/main_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</androidx.constraintlayout.motion.widget.MotionLayout>

次に res/xml/ にアニメーションファイルを作成します。

res/xml/motion_scene_main.xml
<!-- MotionScene※3タグで大枠を作ります。xmlns:motion="~"を忘れずに。 -->
<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto"
    >

    <!-- 開始、終了アニメーションの指定と実行時間を記述します。 -->
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000"
        >
        <!-- トリガーとなるアクションとViewのidを紐付けます。 -->
        <OnClick motion:targetId="@id/main_text_view"/>
    </Transition>

    <!-- アニメーション開始時のViewの状態を記述します。 -->
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            />
    </ConstraintSet>

    <!-- アニメーション終了時のViewの状態を記述します。 -->
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            />
    </ConstraintSet>

</MotionScene>

※3 xmlns:motion="~"を含めれば、MotionSceneという命名でなく、どのような名前を付けても認識してくれます。

最後に、MotionLayoutとアニメーションファイルを紐づけてやります。

activity_main.xml
<androidx.constraintlayout.motion.widget.MotionLayout

     省略 

    app:layoutDescription="@xml/motion_scene_main"
    >

端末で確認するとしてみます。
テキストをタップするとしっかりアニメーションしていますね。
20200305.gif

MotionLayoutはView自体をアニメーションさせるだけでなく、Viewのパラメータを動的に変更することもできます。
アニメーションファイルに少し手を加えてみます。

res/xml/motion_scene_main.xml
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto"
    >

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000"
        >
        <OnClick motion:targetId="@id/main_text_view" />
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            >
            <!-- アニメーション開始時のテキストの色を指定します。 -->
            <CustomAttribute
                motion:attributeName="textColor"
                motion:customColorValue="#ff0000"
                />
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/main_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            >
            <!-- アニメーション終了時のテキストの色を指定します。 -->
            <CustomAttribute
                motion:attributeName="textColor"
                motion:customColorValue="#0000ff"
                />
        </Constraint>
    </ConstraintSet>

</MotionScene>

20200305_2.gif

CustomAttribute というタグは、attributeNameに指定したViewのパラメータをアニメーションに合わせて変更してくれます。
色を変える他に、アニメーションに合わせてViewを変形させたり、画像をクロスフェードさせたりできます。
CustomAttributeを複数指定することももちろん可能です。

ここでは特に触れないので気になった方は調べてみてください。

追記1

上記ではアニメーションファイルにトリガーとなるViewを紐づけていましたが、実際のプロジェクトでは、コードで指定する方が一般的かと思います。

res/xml/motion_scene_main.xml
<!-- ここは削除して構いません。 -->
<OnClick motion:targetId="@id/main_text_view" />
MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val motionLayout = findViewById<MotionLayout>(R.id.main_motion_layout)
        // トリガーとなるViewのクリック時に、指定したid※4のアニメーションを開始します。
        findViewById<TextView>(R.id.main_text_view).setOnClickListener {
            // 現在のアニメーションの状態を判定して、開始するアニメーションを決めます。
            if (motionLayout.currentState == R.id.start) {
                motionLayout.transitionToEnd()
            } else {
                motionLayout.transitionToStart()
            }
        }
    }
}

※4 アニメーションファイルで定義したConstraintSetのidです。

追記2

要件によってはアニメーションの進行状況を検知したい場合があると思います。
そんな時は、 MotionLayout.TransitionListener をセットすればコールバックが得られます。

MainActivity.kt
motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
    override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
        // トリガーした時に呼ばれます。
    }

    override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
        // アニメーション開始時に呼ばれます。
    }

    override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
        // アニメーション中に呼ばれます。
    }

    override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
        // アニメーション終了時に呼ばれます。
    }
})

感想

MotionLayoutの魅力は、ほぼ既存の実装に影響せず、線形補完の滑らかなアニメーションで手軽にリッチな表現ができるところにあると思いました。また、学習コストが比較的低いのも◎。

参考

https://developer.android.com/training/constraint-layout/motionlayout

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

【Android】Glideを使って画像表示後Blur�をかける

どういうときに使う?

  • スクロールしたらヘッダーのイメージをぼかしたい時とか。
  • 表示後に動的Blurにする必要が無いのであれば必要ない。

使うもの

コード

val url = "https://hogehoge"

// 初回読み込み
Glide.with(imageView.context)
     .load(url)

// Blurをかける時
Glide.with(imageView.context)
     .load(url)
     .transform(
         GrayscaleTransformation(),
         BlurTransformation(int)
     )
     .placeholder(imageView.drawable)
     .into(imageView)

ポイントはplaceholderで 既に表示されている ImageView の Drawable を指定する所
上記のことを忘れるとチカチカする。

Blur の他にも表示後に何かの値を変更するみたいなときとかも同じようにやればいいと思う。
CoilとかPiccasoでも同じ事ができると思う。

Seekbarとかと組み合わせて試してみて:wave:

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

アンドロイドアプリでスマートニュース風のタブを実装する

これだけでした。

tabs.setTabMode(TabLayout.MODE_SCROLLABLE);

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

スマホ向けセカンドスクリーンという新セグメント

RoshScreen.png

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

Androidで、ktor + coroutines で通信をする。

Android で、通信処理を実装しようとすると、retrofit が1番最初に思いつきます。
ただ、選択肢は多い方がいいと思いますので、マルチプラットフォームで実装出来る ktor を紹介したいと思います。

gradle

  • ktor

ktor は、マルチプラットフォーム対応なので、通信ライブラリーも選択可能です。
Androidの場合、OkHttp, Android があります。
今回は、OkHttp にします。
また、Json も同様に Gson, Jackson, Kotlinx.Serialization が選択が出来ます。
今回は、Kotlinx.Serialization にします。

dependencies {
    implementation "io.ktor:ktor-client-core:1.3.1"
    implementation "io.ktor:ktor-client-android:1.3.1"
    implementation "io.ktor:ktor-client-okhttp:1.3.1"
    implementation "io.ktor:ktor-client-serialization-jvm:1.3.1"
}
  • coroutines
dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
}
  • serialization
    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
    }
apply plugin: 'kotlinx-serialization'

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
}

OpenWeather から天気の情報を取得してみたいと思います。
ログインして、Keyは取得済みとします。

ViewModel

まず、ktorを初期化する。
OkHttp と KotlinxSerializer を使用します。

    private val httpClient = HttpClient(OkHttp) {
        install(JsonFeature) {
            serializer = KotlinxSerializer(
                Json(
                    JsonConfiguration.Stable.copy()
                )
            )
        }
    }

上記を使って、天気情報を取得する処理を実装します。
viewModelScope.launch が、coroutines の実装になります。
これだけで、非同期で通信する処理が実装出来ます。
また、取得できたら、LiveData を使って、UI側に通知します。

    val openWeatherMapLiveData = MutableLiveData<OpenWeatherMapJson>()

    fun getOpenWeatherMapJson() {
        viewModelScope.launch {
            try {
                val openWeatherMapJson = httpClient.post<OpenWeatherMapJson>("https://api.openweathermap.org/data/2.5/weather?q=yokohama&appid={KEY}") {
                    contentType(ContentType.Application.Json)
                }
                openWeatherMapLiveData.postValue(openWeatherMapJson)
            } catch (e:Exception) {
                Log.e("w", e.message)
            }
        }
    }
  • OpenWeatherMapJson

Json部分の実装は、省略しますが、下記のような data class を用意します。

@Serializable
data class OpenWeatherMapJson(
    val coord: Coord,
    val weather: List<Weather>,
    val base: String,
    val main: Main,
    val visibility: Int,
    val wind: Wind,
    val clouds: Clouds,
    val dt: Int,
    val sys: Sys,
    val timezone: Int,
    val id: Int,
    val name: String,
    val cod: Int
)

Activity

実際に取得する処理を実装します。
ViewModel を使って、非同期で通信し、レスポンスを受け取ったら、UIに表示する処理になります。

class MainActivity : AppCompatActivity() {

    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        fab.setOnClickListener { view ->
            viewModel.getOpenWeatherMapJson()
        }
        viewModel.openWeatherMapLiveData.observe(this, Observer {
            textView.text = it.name
        })
    }
}

まとめ

retrofit でも同様なことが出来ます。
Android で、通信処理を実装する場合の選択肢として、ktor も使えますので是非使ってみてください。

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

ターゲットを狙うようなカスタムViewを作成してみた。

カスタムView(カスタムViewだとなんのことか分からなくなりそうなので、TargetViewと呼びます。) の作り方とアニメーションのやり方を学ぶための備忘録になります。
とりあえず見た目重視の勉強用で作成したため、画面回転時などは何も考えていおりません。

今回作ったもの

TargetView.gif

QRコードを読み込む際に使えそうな感じですね。(といいつつも用途不明です)

TargetView

今回作成したカスタムViewはxmlに定義したFrameLayoutの中に、

  • 鍵かっこを表示するView
  • グレーの矩形を表示するView
  • 真ん中の十字を表示するView

を重ねて表示しています。

Viewそれぞれにはxml内でマージンを設定し、特に鍵かっこViewとグレーの矩形がアニメーションした際に重ならないように調整しています。

3つのViewはそれぞれのクラス内のonDrawCanvasのメソッドを使用して横線、縦線、矩形を描画しています。

鍵かっことグレーの矩形のアニメーションは縮小・拡大を交互に行うようにしています。

鍵かっこを表示するView

鍵かっこViewの作成手順
1. Viewを継承したクラスを作成
2. CanvasクラスとPaintクラスを使用して縦線と横線を描画
3. 拡大→縮小のアニメーションを作成

Viewを継承する

class TargetFrameView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    constructor(context: Context): this(context, null, 0, 0)
    // xmlに定義するため、このコンストラクタは必須となります。
    constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): this(context, attrs, defStyleAttr, 0)

    // 省略...
}

Viewを継承する際はxmlから呼び出す場合とコードから呼び出す場合とで必要となるコンストラクタが違うため、用途に合わせて継承する必要があります。
今回はxmlから呼び出すことを想定しているため、最低限ContextAttributeSetのコンストラクタを追加する必要があります。

鍵かっこ描画

class TargetFrameView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    // 省略...

    private val c: Context by lazy {
        context
    }

    // 線の長さ
    private val margin: Float = 50f

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        drawLeftTopLine(canvas, setupLinePaint())
        drawLeftBottomLine(canvas, setupLinePaint())
        drawRightTopLine(canvas, setupLinePaint())
        drawRightBottomLine(canvas, setupLinePaint())

        // 省略...
    }

    private fun drawLeftTopLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(0.0f, 0.0f, margin, 0.0f, paint)
        // 縦線
        canvas?.drawLine(0.0f, 0.0f, 0.0f, margin, paint)
    }

    private fun drawLeftBottomLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(0.0f, height.toFloat(), margin, height.toFloat(), paint)
        // 縦線
        canvas?.drawLine(0.0f, height.toFloat() - margin, 0.0f, height.toFloat(), paint)
    }

    private fun drawRightTopLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(width.toFloat() - margin, 0.0f, width.toFloat(), 0.0f, paint)
        // 縦線
        canvas?.drawLine(width.toFloat(), 0.0f, width.toFloat(), margin, paint)
    }

    private fun drawRightBottomLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(width.toFloat() - margin, height.toFloat(), width.toFloat(), height.toFloat(), paint)
        // 縦線
        canvas?.drawLine(width.toFloat(), height.toFloat() - margin, width.toFloat(), height.toFloat(), paint)
    }

    private fun setupPaint(): Paint {
        return Paint().apply {
            isAntiAlias = true // アンチエイリアスを有効にするかの設定。基本的にはtrueしておくと良いと思います。
            color = ContextCompat.getColor(c, android.R.color.holo_green_dark)
            strokeWidth = 10f
        }
    }

    // 省略...
}

鍵かっこ描画での登場人物は、CanvasPaintです。
Canvasとは簡単に言うとView上に図形や画像を描画する際に使われるAPIです。4つの基本的なコンポーネントを使用して、渡された情報を元に描画を行います。
PaintとはCanvasに線を作成したりする際などに、色や太さなどの情報を渡すために使用します。

実際の線の描画については、Canvas#drawLineを使用しています。
Canvas#drawLineの使い方は始点の座標と終点の座標、さらに線を描画する際の設定を渡すだけです。

描画される線の長さは、50.0固定で設定しています。(marginという変数に格納しています。)

最終的には以下のように描画されます。
鍵かっこ.png

拡大→縮小アニメーション作成

class TargetFrameView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    // 省略...

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        // 省略...

        expandedAnimation()
    }

    private fun expandedAnimation() {
        val expandedScaleX = PropertyValuesHolder.ofFloat("scaleX", 1.05f, 0.98f)
        val expandedScaleY = PropertyValuesHolder.ofFloat("scaleY", 1.05f, 0.98f)

        ObjectAnimator.ofPropertyValuesHolder(this, expandedScaleX, expandedScaleY).apply {
            duration = 1100
            repeatCount = ValueAnimator.INFINITE // アニメーションはループさせる
            repeatMode = ValueAnimator.REVERSE   // 拡大後は縮小させる
        }.start()
    }
}

拡大→縮小アニメーションは、ObjectAnimatorを使用して行っています。
ObjectAnimatorは渡したObjectに対してアニメーションさせるためのAPIです。
PropertyValuesHolderというアニメーション情報を保持するAPIを使用して、複数のアニメーション情報を作成しています。
アニメーション自体はX軸方向、Y軸方向に大きくなるというアニメーションです。

鍵かっこ完成

以上で鍵かっこは完成です。

鍵かっこ.gif

コード全文

import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat

class TargetFrameView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    constructor(context: Context): this(context, null, 0, 0)
    constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): this(context, attrs, defStyleAttr, 0)

    private val c: Context by lazy {
        context
    }

    private val margin: Float = 50f

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        drawLeftTopLine(canvas, setupLinePaint())
        drawLeftBottomLine(canvas, setupLinePaint())
        drawRightTopLine(canvas, setupLinePaint())
        drawRightBottomLine(canvas, setupLinePaint())

        expandedAnimation()
    }

    private fun drawLeftTopLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(0.0f, 0.0f, margin, 0.0f, paint)
        // 縦線
        canvas?.drawLine(0.0f, 0.0f, 0.0f, margin, paint)
    }

    private fun drawLeftBottomLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(0.0f, height.toFloat(), margin, height.toFloat(), paint)
        // 縦線
        canvas?.drawLine(0.0f, height.toFloat() - margin, 0.0f, height.toFloat(), paint)
    }

    private fun drawRightTopLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(width.toFloat() - margin, 0.0f, width.toFloat(), 0.0f, paint)
        // 縦線
        canvas?.drawLine(width.toFloat(), 0.0f, width.toFloat(), margin, paint)
    }

    private fun drawRightBottomLine(canvas: Canvas?, paint: Paint) {
        // 横線
        canvas?.drawLine(width.toFloat() - margin, height.toFloat(), width.toFloat(), height.toFloat(), paint)
        // 縦線
        canvas?.drawLine(width.toFloat(), height.toFloat() - margin, width.toFloat(), height.toFloat(), paint)
    }

    private fun setupPaint(): Paint {
        return Paint().apply {
            isAntiAlias = true // アンチエイリアスを有効にするかの設定。基本的にはtrueしておくと良いと思います。
            color = ContextCompat.getColor(c, android.R.color.holo_green_dark)
            strokeWidth = 10f
        }
    }

    private fun expandedAnimation() {
        val expandedScaleX = PropertyValuesHolder.ofFloat("scaleX", 1.05f, 0.98f)
        val expandedScaleY = PropertyValuesHolder.ofFloat("scaleY", 1.05f, 0.98f)

        ObjectAnimator.ofPropertyValuesHolder(this, expandedScaleX, expandedScaleY).apply {
            duration = 1100
            repeatCount = ValueAnimator.INFINITE // アニメーションはループさせる
            repeatMode = ValueAnimator.REVERSE   // 拡大後は縮小させる
        }.start()
    }
}

グレーの矩形を表示するView

グレー矩形作成手順
1. Viewを継承したクラスを作成
2. CanvasクラスとPaintクラスを使用して矩形を描画
3. 縮小→拡大のアニメーションを作成

Viewを継承する

class TargetRectangleView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    constructor(context: Context): this(context, null, 0, 0)
    // xmlに定義するため、このコンストラクタは必須となります。
    constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): this(context, attrs, defStyleAttr, 0)

    // 省略...
}

鍵かっこと同じなので解説は飛ばします。

グレー矩形描画

class TargetRectangleView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    // 省略...

    private val c: Context by lazy {
        context
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        drawCenterRectangle(canvas, setupRectPaint())

        // 省略...
    }

    private fun drawCenterRectangle(canvas: Canvas?, paint: Paint) {
        val rect = Rect().apply {
            set(0, 0, width, height)
        }
        canvas?.drawRect(rect, paint)
    }

    private fun setupRectPaint(): Paint {
        return Paint().apply {
            isAntiAlias = true
            color = ContextCompat.getColor(c, R.color.rectangle_color)
        }
    }

    // 省略...
}

今回は矩形を表示するため、Canvas#drawRectを使用しています。
Rectを使用して矩形の大きさをCanvasに渡しています。
Paintに関しては鍵かっこで説明したので省かせていただきます。

矩形.png

縮小→拡大アニメーション作成

class TargetRectangleView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    // 省略...

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        // 省略...

        shrinkAnimation()
    }

    // 省略...

    private fun shrinkAnimation() {
        val shrunkScaleX = PropertyValuesHolder.ofFloat("scaleX", 0.94f, 1.03f)
        val shrunkScaleY = PropertyValuesHolder.ofFloat("scaleY", 0.94f, 1.03f)

        ObjectAnimator.ofPropertyValuesHolder(this, shrunkScaleX, shrunkScaleY).apply {
            duration = 1100 // 拡大→縮小アニメーションと同じ秒数
            repeatCount = ValueAnimator.INFINITE //拡大→縮小アニメーションと同じ
            repeatMode = ValueAnimator.REVERSE //拡大→縮小アニメーションと同じ
        }.start()
    }
}

こちらも鍵かっことほとんど同じです。
矩形に適応するアニメーションは縮小→拡大なので、PropertyValuesHolder#ofFloatの第二引数と第三引数の大小関係が違っています。

グレー矩形完成

わかりづらいですが、縮小→拡大アニメーションがついています。
矩形完成.gif

コード全文

import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
import com.example.targetviewsample.R

class TargetRectangleView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    constructor(context: Context): this(context, null, 0, 0)
    constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): this(context, attrs, defStyleAttr, 0)

    private val c: Context by lazy {
        context
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        drawCenterRectangle(canvas, setupRectPaint())

        shrinkAnimation()
    }

    private fun drawCenterRectangle(canvas: Canvas?, paint: Paint) {
        val rect = Rect().apply {
            set(0, 0, width, height)
        }
        canvas?.drawRect(rect, paint)
    }

    private fun setupRectPaint(): Paint {
        return Paint().apply {
            isAntiAlias = true
            color = ContextCompat.getColor(c, R.color.rectangle_color)
        }
    }

    private fun shrinkAnimation() {
        val shrunkScaleX = PropertyValuesHolder.ofFloat("scaleX", 0.94f, 1.03f)
        val shrunkScaleY = PropertyValuesHolder.ofFloat("scaleY", 0.94f, 1.03f)
        ObjectAnimator.ofPropertyValuesHolder(this, shrunkScaleX, shrunkScaleY).apply {
            duration = 1100
            repeatCount = ValueAnimator.INFINITE
            repeatMode = ValueAnimator.REVERSE
        }.start()
    }
}

真ん中の十字を表示するView

真ん中の十字作成手順
1. Viewを継承したクラスを作成
2. CanvasクラスとPaintクラスを使用して線を描画

十字に関しては鍵かっことかなり似ているため、全文を載せます。

基本的には、幅と高さを半分にして十字Viewの中心を出し、そこから計算を始点と終点を計算し縦線横線を描画しています。

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat

class TargetCrossView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

    constructor(context: Context): this(context, null, 0, 0)
    constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): this(context, attrs, defStyleAttr, 0)

    private val c: Context by lazy {
        context
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        drawCrossLine(canvas, setupCrossLinePaint())
    }

    private fun drawCrossLine(canvas: Canvas?, paint: Paint) {
        val halfWidth = (width / 2).toFloat()
        val halfHeight= (height / 2).toFloat()
        val margin = 25f

        // 横線
        canvas?.drawLine(halfWidth - margin, halfHeight, halfWidth + margin, halfHeight, paint)
        // 縦線
        canvas?.drawLine(halfWidth, halfHeight - margin, halfWidth, halfHeight + margin, paint)
    }

    private fun setupCrossLinePaint(): Paint {
        return Paint().apply {
            isAntiAlias = true
            color = ContextCompat.getColor(c, android.R.color.white)
            strokeWidth = 5f
        }
    }
}

十字View完成

コード上では十字の色は白色だったのですが、完成画像を見せる際に見えないため黒くしています。

十字.png

TargetView作り

レイアウトファイルに定義

最後に今まで作成してきた3つのViewをFrameLayoutを親として定義したxmlレイアウトに配置し、FrameLayoutを継承したカスタムViewGourpのクラスで全てのViewを組み合わせます。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <view class="com.example.targetviewsample.targetview.TargetFrameView"
        android:id="@+id/indicatorView"
        android:layout_margin="16dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <view
        android:id="@+id/rectangleView"
        class="com.example.targetviewsample.targetview.TargetRectangleView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="24dp" />

    <view class="com.example.targetviewsample.targetview.TargetCrossView"
        android:id="@+id/crossView"
        android:layout_margin="24dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"/>
</FrameLayout>

Viewを配置していく順番ですが、 最前面から十字→矩形→鍵かっこという順番になるように配置していきます。

FrameLayoutを継承したクラスを作成

import android.content.Context
import android.graphics.Point
import android.util.AttributeSet
import android.util.Size
import android.view.ViewGroup
import android.widget.FrameLayout
import com.example.targetviewsample.R

class TargetView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {

    constructor(context: Context): this(context, null, 0, 0)
    constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): this(context, attrs, defStyleAttr, 0)

    private val c: Context by lazy {
        context
    }

    init {
        inflate(c, R.layout.item_target_view, this)
    }
}

作成したカスタムViewGroup内で先ほど3つのViewを定義したレイアウトファイルをinflateし、Activityなどで使用します。

TargetViewの完成

最初のGIFを再掲
TargetView.gif

使用感

GIFが用意できずわかりづらいのですが、こんな感じとういうのが伝われば幸いです。

Screenshot_20200305-062735.png

最後に

過去に何回かカスタムViewを作成する機会はあったのですが、アニメーションこみでは作ったことがなかったので勉強になりました。
今回作成したものはまだまだ改良の余地がありますので、さらに勉強していこうと思います。

朝まで記事を書いてしまったため、誤字脱字や見づらい箇所があると思います。。。

見ていただきありがとうございます。

参考にさせていただいた記事

Custom Viewsで複数のviewを1つにまとめて部品化する

AndroidでもiPhoneに負けないようなアニメーションを実装してみよう

公式リファレンス

カスタムViewの作り方
View
ObjectAnimator
PropertyValuesHolder
Canvas
Canvas#drawLineのリファレンス
Paint
Rect
Canvas#drawRect)

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