■アカウント
■Google Play Console
https://play.google.com/apps/publish/?hl=ja
アプリを公開するためのもの
■Firebase
https://console.firebase.google.com/
プッシュなどを利用する場合に使用
■AndroidStudio環境の作成
■公式ページ
Download Android Studio and SDK tools | Android Developers
https://developer.android.com/studio/
WindowsもMacも、インストールで詰まることは無かった
インストール完了後も、何かと追加で色々とダウンロード&インストールさせられる
エミュレータも、初回起動時はイメージのダウンロードが必要
Ver 3.1.4 時点では、主に以下の書籍を参考にした
基本からしっかり身につくAndroidアプリ開発入門
https://www.amazon.co.jp/dp/479739580X
■インストール参考ページ(Ver 3.1.2 時点)
Android Studioインストール
https://akira-watson.com/android/adt-windows.html
JDKインストール
http://techfun.cc/java/windows-jdk-install.html
http://techfun.cc/java/windows-jdk-pathset.html
■インストールメモ(Ver 2.3.3 時点)
http://www.oracle.com/technetwork/java/javase/downloads/index.html
の
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
から
jdk-8u91-windows-x64.exe
をダウンロード&インストール
https://developer.android.com/studio/
から
android-studio-bundle-143.2821654-windows.exe
をダウンロード&インストール
Android Studio の起動時にエラーが表示されたことがあったが、JavaとJDKの再インストールで直った。
SDK Manager で、Android 4.3 〜 7.1.1 までインストール。
■日本語化(Ver 3.3.2 時点)
※日本語化は Android Studio 終了中に実行しないと反映されないことがあるかも
※毎回バージョンアップが手間なので、Ver 4 からは英語のまま使っている
以下の方法で日本語化できた
studio64.exe.vmoptions は初めから config 内にあった
Android Studio3.1を日本語化する | HIROMARTBLOG
http://hiromart.com/blog/androidstudiojapanese/
Macの場合、以下の手順で日本語化できた
MacでAndroid Studioを日本語化する - ソフラボの技術ブログ
http://shinsuke789.hatenablog.jp/entry/2018/08/27/130714
■日本語化(Ver 2.3.3 時点)
以下の方法で日本語化できた。一部は英語のままだけど特に問題はなし
Android Studio 2.0 を日本語化してみた | 寿司すき焼き相撲
http://s3wordpress.wpblog.jp/2016/05/18/android-studio-2-0-%E3%82%92%E6%97%A5%E6%9C%AC%E8%AA%9E%E5%8...
■ショートカットの変更
Ctrl+Yは、Redoではなく行削除が割り当てられている
一般的なショートカットと異なるので余計なトラブルのもとだが、設定で変更はできる
http://qiita.com/decchi/items/f8603ccccec03a71a4d9
■エディタの設定
ファイル → 設定 → Editor → 外観
行番号を表示する
空白を表示する
■デバッグ
画面下部の「ログキャット」をクリックすると、デバッグログを確認できる
端末、アプリ、デバッグレベルなどを絞り込んで確認する
■メモリの割り当て
※現状未設定
http://tools.android.com/tech-docs/configuration
■AndroidStudioのバージョンアップ
http://www.youfit.co.jp/archives/885
Ver 2.1.2 → Ver 2.2.3 のとき、この手順でバージョンアップできた
Ver 2.2.3 → Ver 3.1.2 のとき、この手順でバージョンアップできた。が、メニューが英語表記に戻った
Ver 3.1.2 → Ver 3.2.0 のとき、この手順でバージョンアップできた。が、メニューが英語表記に戻った
■AndroidStudioが起動しなくなった場合
Macで発生
アップデートして使えているかと思ったが、Mac再起動後はAndroidStudioを起動できなくなった
エラーなどは何も確認できない
最新の日本語化パッチを当てると起動できるようになった
AndroidStudioがアップデート後に起動しない時 - Qiita
https://qiita.com/filu_/items/6470ab95b45a4382e34e
■メニューが英語表記に戻った場合
「AndroidStudio環境を作った時のメモ」の「日本語化」の手順を再度行って日本語化する
Ver 2.3.3 時点のメモだが、同じ手順で日本語化できた
■Kotlin Gradle plugin version のエラーになった場合
The Android Gradle plugin supports only Kotlin Gradle plugin version 1.2.51 and higher. Project 'HelloKotlin' is using version 1.2.30.
と表示された場合、指示に従ってGradleファイルを書き換える
C:\Users\Refirio\AndroidStudioProjects\HelloKotlin\build.gradle
ext.kotlin_version = '1.2.30'
↓
ext.kotlin_version = '1.2.51'
書き換えたら「再試行」を実行する
■AndroidStudioをベータ版から正式版にしたときのメモ
以下からAndroidStudioをダウンロード&インストール
https://developer.android.com/studio/
■Gitの連携
■SourceTreeとの連携
AndroidStduio内でgit操作は可能らしいが、SourceTreeで管理する方法
http://ameblo.jp/hunnyjams/entry-11961454576.html
gitのパスは以下のようになる。環境によって変わる可能性はある
C:\Program Files\Git\cmd\git.exe
AndroidStudioが build/ や app/build/ などに自動でファイルを吐き出すので、
可能なら最初の段階で .gitignore の対象にしておく。一度リポジトリに入れてしまうと、後から外すのは面倒
今は何もしなくてもAndroidStudioが .gitignore を自動作成してくれるみたい?
■.gitignore
AndroidStudioが自動で作成するかも。要確認
以下は設定例
*.iml
.gradle
/local.properties
/.idea/caches/build_file_checksums.ser
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild
以下を参考に作成すると良さそう
Androidアプリの.gitignore - Qiita
https://qiita.com/jumperson/items/fa66995fb68de2847ffd
Android Studioのバージョン管理対象ファイル - ソフトウェアエンジニアリング - Torutk
http://www.torutk.com/projects/swe/wiki/Android_Studio%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%...
■プロジェクトの作成
■プロジェクトの新規作成(Ver 3.3.2 時点)
以下の手順で作成できる
プロジェクトが作成されたら、何も変更せずにいったんエミュレータと実機での起動を確認しておくといい
新規 Android Studio プロジェクトの開始
↓
プロジェクトの選択
「空のアクティビティ」が選択されているので、そのまま「次へ」
↓
プロジェクトの構成
名前: helloworld
パッケージ名: net.refirio.helloworld (名前をもとに自動入力される)
保存ロケーション: C:\Users\refirio\AndroidStudioProjects\helloworld (アプリケーション名をもとに自動入力される)
言語: Kotlin
最小APIレベル: API 19: Android 4.4 (KitKat)
「完了」
■アプリの作成
■Kotlin入門
JavaプログラマのためのKotlin入門 - Qiita
https://qiita.com/koher/items/bcc58c01c6ff2ece658f
KotlinでAndroid開発 - Qiita
https://qiita.com/paulbarre/items/fb4565c37287d173a78f
Android開発を受注したからKotlinをガッツリ使ってみたら最高だった - Qiita
https://qiita.com/omochimetaru/items/98e015b0b694dd97f323
正式採用の「Kotlin」で挑戦! 初めてのAndroidアプリ開発 〜ストップウォッチを作ってみよう〜 - エンジニアHub|若手Webエンジニアのキャリアを考える!
https://employment.en-japan.com/engineerhub/entry/2017/06/23/110000
最近話題の「Kotlin」は本当に業務に使えるの? ―国内第一人者と「Yahoo!ニュース」Android版開発者が語るKotlin開発実践のコツ:CodeZine(コードジン)
https://codezine.jp/article/detail/10730
■Kotlinメモ
【Kotlin入門】配列よりも実用性のあるListの使い方|茶トラネコ日記
https://itneko.com/kotlin-list/
Kotlin のコレクション使い方メモ - Qiita
https://qiita.com/opengl-8080/items/36351dca891b6d9c9687
変数を定義する (val, var) | まくまくKotlinノート
https://maku77.github.io/kotlin/basic/var.html
Android(Java)のあれ、Kotlinでどう書くの? - Qiita
https://qiita.com/tporange/items/6cde7a64b4f41072b1c9
Kotlin の Collection まとめ 〜List編〜 - Qiita
https://qiita.com/kiririnyo/items/aee905225902d096f7c0
Kotlin のコレクション使い方メモ - Qiita
https://qiita.com/opengl-8080/items/36351dca891b6d9c9687
Kotlinの小技 - Qiita
https://qiita.com/kichinaga/items/6d57bb868af6a13496c0
[Kotlin] Set型の特徴とList型との相互変換 | プログラミング日和
https://pouhon.net/kotlin-set/1422/
KotlinでMutableSetを作る - Qiita
https://qiita.com/mihyaeru21/items/9a293cd1fefd59116751
[Kotlin] List型の特徴とMutableList | プログラミング日和
https://pouhon.net/kotlin-list/1411/
Kotlin のコレクション・配列早見表 - Qiita
https://qiita.com/arumati-yk/items/ad2492f481b1f5ed6cdc
[android開発] AndroidManifest.xmlの設定一覧(ネットワーク系) - 行け!偏差値40プログラマー
http://hensa40.cutegirl.jp/archives/6501
AndroidStudio付属のエミュレータがネットワークに繋がらない時の対処法(API27で動作確認) | takelab.note
https://wandering-engineer.tech/2019/08/23/post-4626/
【正誤情報】『基本からしっかり身につくAndroidアプリ開発入門 Android Studio 3.x対応』|SBクリエイティブ
https://www.sbcr.jp/support/14723/
Kotlinで文字列を数値、日付文字列を日付(java.util.Date)、日付を文字列(String)に変換する - Qiita
https://qiita.com/emboss369/items/5a3ddea301cbf79d971a
Android アプリの ListView で非同期の画像を表示する方法 - Qiita
https://qiita.com/tomalatte001/items/345c55603148b1ce41f2
[Android] Picasso でネット上の画像をGridViewで表示
https://akira-watson.com/android/gridview-picasso.html
【Android】picassoでURLから画像をよしなに扱う【Kotlin】 - wrongwrongな開発日記
https://wrongwrong163377.hatenablog.com/entry/2018/09/09/165250
Androidアプリ開発でImageViewを追加する方法【初心者向け】 | TechAcademyマガジン
https://techacademy.jp/magazine/3573
Kotlinで初期化を遅延する | RE:ENGINES
https://re-engines.com/2018/11/15/kotlin%E3%81%A7%E5%88%9D%E6%9C%9F%E5%8C%96%E3%82%92%E9%81%85%E5%BB...
ActivityとFragmentのライフサイクルと罠 - Qiita
https://qiita.com/chibi929/items/78f0d3aa2ab4a0229978
Kotlin スコープ関数 用途まとめ - Qiita
https://qiita.com/ngsw_taro/items/d29e3080d9fc8a38691e
Android はじめてのFragment イベント編 - Qiita
https://qiita.com/Reyurnible/items/d6397f5fbb03ee4fb93b
ActivityとFragmentの連携を理解する (Android Kotlin)
https://101010.fun/posts/android-try-fragment.html
android - Fragmentをreplaceしても一つ前のFragmentが残る - スタック・オーバーフロー
https://ja.stackoverflow.com/questions/6342/fragment%E3%82%92replace%E3%81%97%E3%81%A6%E3%82%82%E4%B...
■Kotlinファースト
今後ツールやライブラリは、まずKotlin向けが最初に作られるとのこと
(ただしその後、JavaやC++向けのものも作られるとのことなので、他の言語が使えなくなるわけではない)
可能ならKotlinで作るほうが良さそう
Google、Androidにおける「Kotlinファースト」強化を表明。Google I/O 2019 − Publickey
https://www.publickey1.jp/blog/19/googleandroidkotlingoogle_io_2019.html
Android の Kotlin ファースト アプローチ | Android デベロッパー | Android Developers
https://developer.android.com/kotlin/first?hl=ja
■ハローワールド
src/main/java/org/refirio/helloworld/MainActivity.kt
package org.refirio.helloworld
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ハローワールド!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
■ボタンとトースト
src/main/java/org/refirio/helloworld/MainActivity.kt
package org.refirio.helloworld
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val testButton = findViewById<Button>(R.id.test_button)
testButton.setOnClickListener {
Toast.makeText(applicationContext, "これはトーストです", Toast.LENGTH_SHORT).show();
}
}
}
src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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">
<Button
android:id="@+id/test_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ボタン"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
■ビューバインディング
AndroidStudio4.1から、ビューバインディングという機能が用意されている
これはfindViewByIdの後継となる機能で、findViewByIdよりも高速に動作するらしい
build.gradle(Androidビューでは「Gradle Scripts」直下の「build.gradle (Module: helloworld.app)」が該当する)
android {
〜略〜
buildFeatures {
viewBinding = true
}
}
コードを変更したら「Sync Now」をクリック
さらに、アクティビティを以下のように変更
src/main/java/org/refirio/helloworld/MainActivity.kt
package org.refirio.helloworld
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.refirio.helloworld.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.testButton.setOnClickListener {
Toast.makeText(applicationContext, "これはトーストです", Toast.LENGTH_SHORT).show();
}
}
}
これで先と同じ結果が得られる
ビューバインディングでは、レイアウトXMLファイルに対応するクラスが自動で生成される
今回の場合レイアウトファイルは activity_main.xml なので、これに接尾語「Binding」を加えた「ActivityMainBinding」が自動的に生成されている
このクラスを使うように指定する。そしてonCreateメソッド内で
・生成されたバインディングクラスに含まれるinflateメソッドを呼び出す
・rootプロパティからルートビューへの参照を取得する
・取得したルートビューをsetContentViewにわたす
という処理を行う(これはビューバインディングを使う場合のお決まりの処理となる)
「testButton」は、ボタンのID「test_button」をキャメルケースにしたものを指定するみたい
■画像の表示
任意の画像をコピーする
プロジェクトウインドウのツリーで「res」内の「drawable」を選択する
その状態でペーストを行う
コピー先の選択として「drawable」と「drawable-24」のように表示されるが、「drawable」にペーストする
(「drawable-24」はAPI24(Android7.0)以降のバージョンでのみ使用したいリソースを入れる場所)
コピーダイアログが開くので、そのまま「Refactor」をクリックする
これで「drawable」フォルダ内に画像が表示される
レイアウトのXMLを開き、「common」内の「ImageView」をドラッグ&ドロップで配置する
画像の選択ダイアログが表示されるので、配置したい画像を選択して「OK」をクリック
画像が配置されるので、あとはテキストやボタンと同様に表示位置の調整を行う
■アクティビティの追加とインテント
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Activity → Empty Activity」を選択する
表示されたダイアログで「Activity Name」で「ResultActivity」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「ResultActivity」と「activity_result.xml」が追加される
以下のようにすると、追加したアクティビティに遷移するためのボタンとして機能する
src/main/java/org/refirio/helloworld/MainActivity.kt
binding.testButton.setOnClickListener {
val intent = Intent(this, ResultActivity::class.java)
startActivity(intent)
}
■staticメソッドを定義する
companion object を使ってstaticメソッドを定義する
Kotlinで静的変数・メソッドを定義する方法 - goroyaのSE日記
http://gogoroya.hatenadiary.jp/entry/2017/06/05/234348
kotlinはJavaからどう見えるか? - Qiita
https://qiita.com/boohbah/items/167233c7eafe17f3150b
■Kotlin RPEL
※XcodeでいうPlaygroundのようなもの
プロジェクトを開いた状態で、メニューの「ツール → Kotlin → Kotlin RPEL」から実行できる
■アプリのアイコンを変更する
※未検証
Android Studio 開発【アプリアイコンの変更】 - ハコニワ デザイン
http://hakoniwadesign.com/?p=4908
Androidアプリのアイコン画像を変更する方法【初心者向け】 | TechAcademyマガジン
https://techacademy.jp/magazine/2710
■スプラッシュ画面を表示する
※未検証
Splash画面でpostDelayedして一定時間画面を表示する - Qiita
https://qiita.com/shanonim/items/35296c02494ffdbd7273
Androidでスプラッシュ画面を作る方法 - Qiita
https://qiita.com/glayash/items/646e5c0d5de82cfc17bc
■カメラ
【Android】カメラ機能に触れてみる(Android5.0〜) - vaguely
https://mslgt.hatenablog.com/entry/2015/05/12/013013
AndroidのCamera2 APIのサンプル : 時々、失業SEの開発日誌
http://blog.kotemaru.org/2015/05/23/android-camera2-sample.html
【kotlin】CameraXでAndroidカメラを実装してみた | RE:ENGINES
https://re-engines.com/2019/10/31/%E3%80%90kotlin%E3%80%91camerax%E3%81%A7android%E3%82%AB%E3%83%A1%...
Getting Started with CameraX
https://codelabs.developers.google.com/codelabs/camerax-getting-started/#2
【Android】CameraX試してみた - Qiita
https://qiita.com/emusute1212/items/6195cf18bfcbea2ef1d1
Android CameraXでプレビューを表示する | Developers.IO
https://dev.classmethod.jp/smartphone/android/android-camerax-preview/
Android4と5・6でカメラは仕様が大きく変わっているみたい
以下、2021年6月時点で改めて調べたときの記事(要検証)
CameraX の概要 | Android デベロッパー | Android Developers
https://developer.android.com/training/camerax?hl=ja
プレビューを実装する | Android デベロッパー | Android Developers
https://developer.android.google.cn/training/camerax/preview?hl=ja
【初心者向け】CameraXでAndroidのカメラアプリを作る - Qiita
https://qiita.com/senju797/items/7b846fce6a828004279c
AndroidのカメラサポートライブラリのCameraXを使ってみました | Kotlin | アプリ関連ニュース | ギガスジャパン
http://www.gigas-jp.com/appnews/archives/9555
AndroidでCameraXを使ってみる(プレビューの表示) - くらげになりたい。
https://www.memory-lovers.blog/entry/2021/01/25/230000
■プッシュ
「Googleクラウドメッセージング(GCM)」が1年後に廃止、「Firebase Cloud Messaging(FCM)」への移行が必要に:Googleのアプリメッセージング基盤が完全に交代 - @IT
http://www.atmarkit.co.jp/ait/articles/1804/13/news051.html
■アプリの作成(フラグメント)
■フラグメント
Fragmentの基本 - Qiita
https://qiita.com/naoi/items/3e1125d1e1418d09f77a
Android はじめてのFragment - Qiita
https://qiita.com/Reyurnible/items/dffd70144da213e1208b
■フラグメントでページの切り替え
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: fragment
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
アクティビティを追加で作成する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Activity → Empty Activity」を選択する
表示されたダイアログで「Activity Name」で「SubActivity」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「SubActivity」と「activity_sub.xml」が追加される
タイトル表示用のフラグメントを作成する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Fragment → Fragment(Blank)」を選択する
表示されたダイアログで「Fragment Name」で「TitleFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「TitleFragment.kt」と「fragment_title.xml」が追加される
fragment_title.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TitleFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
このファイルを、以下のように変更する
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TitleFragment" >
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="タイトル"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
TitleFragment
package org.refirio.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [TitleFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class TitleFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_title, container, false)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment TitleFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
TitleFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
このファイルを、以下のように変更する
package org.refirio.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.refirio.fragment.databinding.FragmentTitleBinding
class TitleFragment : Fragment() {
private var _binding: FragmentTitleBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentTitleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
fun setTitle(title: String) {
binding.titleText.text = title
}
}
タイトル表示用のフラグメントをアクティビティへ配置する
activity_main.xml を開き、「Hello World」のテキストビューを削除してから、
「common」内の「<fragment>」をドラッグ&ドロップで中央に配置する
「Fragments」ダイアログに、存在するフラグメントが一覧表示されるので、先程作成した「TitleFragment」を作成して「OK」をクリックする
成約は上端と左右を画面の端に接続し、「Attribute」でマージンを0に設定する(もともと0になっているはず)
また、以下の設定を行う
id: titleFragment
layout_width: 0dp
layout_height: wrap_content
フラグメントのプレビューを有効にする
プログラムの動作には影響しないが、レイアウトがイメージしやすくなるので設定しておくといい
配置したフラグメントを選択し、「All Attrivute」内の「layout」の「[]」をクリックする
「Pick a Resource」ダイアログが開くので、先ほど作成した「fragment_title」を選択して「OK」をクリックする
ここまで作業できたらいったん実行してみる
画面上部にフラグメント内の文字列が表示される
サブ画面を作成する
「activity_sub.xml」を開き、初期配置されているテキストビューがあれば削除する
「layout」から「FrameLayout」を画面中央にドラッグ&ドロップで配置
成約は上端と左右を画面の端に接続し、「Attribute」でマージンを0に設定する
また、以下の設定を行う
id: titleFrame
layout_width: 0dp
layout_height: wrap_content
「activity_sub.xml」に「Common」内の「Button」を3つ配置する
位置はtitleFrameの下なら適当でいい
また、それぞれに以下の設定を行う
id: firstButton
text: ボタン1
id: secondButton
text: ボタン2
id: thirdButton
text: ボタン3
ボタンに制約を追加しておく
一例だが、3つのボタンを選択して「Align」ボタンから「Top Edges」を選択する
これで各ボタンが水平に並ぶ
さらに3つのボタンを選択して、いずれかのボタンを右クリックして「Chains → Create Horizontal Chain」を選択する
これで画面サイズやボタンサイズが変更されても水平方向に均等に配置される
フラグメント表示用のビューを配置する
「Layout」内の「FrameLayout」をドラッグ&ドロップでボタンの下に配置する
以下の設定を行う
id: container
layout_width: 0dp
layout_height: 0dp
制約は、上端を真ん中のボタンの下に、下端と左右は画面の端に接続する
マージンはそれぞれ8を設定しておく
各画面のフラグメントを用意する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Fragment → Fragment(Blank)」を選択する
表示されたダイアログで以下を入力し、あとはデフォルトのまま「Finish」ボタンを押す
FirstFragment
SecondFragment
ThirdFragment
プロジェクトウインドウのツリーに以下が追加される
FirstFragment.kt
SecondFragment.kt
ThirdFragment.kt
fragment_pfirst.xml
fragment_second.xml
fragment_third.xml
画面の切り替えがわかるように、それぞれに配置されているテキストを変更しておく
SubAcrivity.kt
package org.refirio.fragment
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class SubActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sub)
}
}
以下のように変更する
package org.refirio.fragment
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.refirio.fragment.databinding.ActivitySubBinding
class SubActivity : AppCompatActivity() {
private lateinit var binding: ActivitySubBinding
private lateinit var title: TitleFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySubBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.firstButton.setOnClickListener {
supportFragmentManager.beginTransaction().apply {
replace(R.id.container, FirstFragment())
addToBackStack(null)
commit()
}
}
binding.secondButton.setOnClickListener {
supportFragmentManager.beginTransaction().apply {
replace(R.id.container, SecondFragment())
addToBackStack(null)
commit()
}
}
binding.thirdButton.setOnClickListener {
supportFragmentManager.beginTransaction().apply {
replace(R.id.container, ThirdFragment())
addToBackStack(null)
commit()
}
}
title = TitleFragment()
supportFragmentManager.beginTransaction().apply {
replace(R.id.titleFrame, title)
//addToBackStack(null)
commit()
}
}
override fun onResume() {
super.onResume()
title.setTitle("サブ画面")
}
}
タイトル画面を完成させる
activity_main.xml にボタンを配置し、上下左右に制約を追加し、以下の設定を行う
id: startButton
layout_width: wrap_content
layout_height: wrap_content
MainActivity.kt
package org.refirio.fragment
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.refirio.fragment.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.startButton.setOnClickListener {
val intent = Intent(this, SubActivity::class.java)
startActivity(intent)
}
}
}
何故か「android Unresolved reference」のエラーになったが、
メニューから「Build → Clean Project」を実行するとビルドできるようになった
■フラグメントと値のやりとり
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: fragment
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
ボタン表示用のフラグメントを作成する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Fragment → Fragment(Blank)」を選択する
表示されたダイアログで「Fragment Name」で「ButtonFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「ButtonFragment.kt」と「fragment_button.xml」が追加される
fragment_button.xml
<?xml version="1.0" encoding="utf-8"?>
<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=".ButtonFragment">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
ラベル表示用のフラグメントを作成する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Fragment → Fragment(Blank)」を選択する
表示されたダイアログで「Fragment Name」で「LabelFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「LabelFragment.kt」と「fragment_label.xml」が追加される
fragment_label.xml
<?xml version="1.0" encoding="utf-8"?>
<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=".LabelFragment" >
<TextView
android:id="@+id/counterView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
フラグメントのプログラムを実装する
ButtonFragment.kt
package org.refirio.fragment
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.refirio.fragment.databinding.FragmentButtonBinding
class ButtonFragment : Fragment() {
private var _binding: FragmentButtonBinding? = null
private val binding get() = _binding!!
/*
* アクティビティがボタンクリック時のコールバックインターフェイスを実装していることを確認
*/
override fun onAttach(context: Context) {
super.onAttach(context)
if (context !is OnButtonClickListener) {
throw RuntimeException("リスナーを実装してください")
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentButtonBinding.inflate(inflater, container, false)
binding.button.setOnClickListener {
/*
* ボタンクリック時のリスナーをセット
*/
val listener = context as? OnButtonClickListener
listener?.onButtonClicked()
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
/*
* ボタンクリック時のコールバックインターフェイスを定義
*/
interface OnButtonClickListener {
fun onButtonClicked()
}
}
LabelFragment.kt
package org.refirio.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.refirio.fragment.databinding.FragmentLabelBinding
class LabelFragment : Fragment() {
private var _binding: FragmentLabelBinding? = null
private val binding get() = _binding!!
private var counter = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
counter =
// フラグメントが再生成されたら値を取り出す
savedInstanceState?.getInt("counter")
// フラグメントのargumentsプロパティから値を取り出す
?: arguments?.getInt("counter")
// 値がなければ0をセット
?: 0
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentLabelBinding.inflate(inflater, container, false)
binding.counterView.text = counter.toString()
return binding.root
}
/*
* フラグメントの再生性に対応するため、カウントの値を保存する
*/
override fun onSaveInstanceState(outState: Bundle) {
//super.onSaveInstanceState(outState)
outState.putInt("counter", counter)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
/*
* カウントアップして表示を更新する
*/
fun update() {
counter++
binding.counterView.text = counter.toString()
}
}
/*
* フラグメントのコンストラクタに引数を渡せるようにする
*/
fun newLabelFragment(value : Int) : LabelFragment {
val fragment = LabelFragment()
val args = Bundle()
args.putInt("counter", value)
// フラグメントのargumentsプロパティは、フラグメントが再生性されても引き継がれる
fragment.arguments = args
return fragment
}
メインアクティビティとそのレイアウトを実装する
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<fragment
android:id="@+id/buttonFragment"
android:name="org.refirio.fragment.ButtonFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout="@layout/fragment_button" />
<FrameLayout
android:id="@+id/container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline">
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
package org.refirio.fragment
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.refirio.fragment.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity(), ButtonFragment.OnButtonClickListener {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
/*
* フラグメントを動的に追加する
*/
if (supportFragmentManager.findFragmentByTag("LabelFragment") == null) {
supportFragmentManager.beginTransaction().apply {
add(R.id.container, newLabelFragment(0), "LabelFragment")
commit()
}
}
}
/*
* ButtonFragmentのコールバックインターフェイスを実装する
*/
override fun onButtonClicked() {
val fragment = supportFragmentManager.findFragmentByTag("LabelFragment") as LabelFragment
fragment.update()
}
}
■スライドショー
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: slideshow
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
使用する画像を用意する
画像を選択してコピーし、app/res/drawable へコピーする
画像表示用のフラグメントを作成する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Fragment → Fragment(Blank)」を選択する
表示されたダイアログで「Fragment Name」で「ImageFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「ImageFragment.kt」と「fragment_image.xml」が追加される
フラグメントに画像を配置する
fragment_image.xml を開き、最初から表示されているTextViewを削除し、
「common」内の「ImageView」をドラッグ&ドロップで中央に配置する
画像選択のダイアログが表示されるので、「backgrounds/scenic」を選択して「OK」をクリックする
これ背景用の風景写真が表示されるサンプルデータ。プレビュー用なので、実際にプログラム実行時には表示されない
(レイアウトエディタで画像は未設定にしておき、プログラムから表示する画像を指定するような場合に使用することができる)
また、以下の設定を行う
id: imageView
layout_width: match_parent
layout_height: match_parent
scaleType: centerCrop
ImageFragment.kt を以下のように変更する
package org.refirio.slideshow
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.refirio.slideshow.databinding.FragmentImageBinding
private const val IMG_RES_ID = "IMG_RES_ID"
class ImageFragment : Fragment() {
private var imageResId: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
imageResId = it.getInt(IMG_RES_ID)
}
}
private var _binding: FragmentImageBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentImageBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
@JvmStatic
fun newInstance(imageResId: Int) =
ImageFragment().apply {
arguments = Bundle().apply {
putInt(IMG_RES_ID, imageResId)
}
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
imageResId?.let {
binding.imageView.setImageResource(it)
}
}
}
ViewPager2を使う
activity_main.xml を開き、最初から表示されているTextViewを削除し、
「Containers」内の「ViewPager2」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは0にする
また、以下の設定を行う
id: pager
layout_width: 0dp
layout_height: 0dp
MainActivity を以下のように変更する
package org.refirio.slideshow
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import org.refirio.slideshow.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
class MyAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
private val resources = listOf(
R.drawable.brownie,
R.drawable.caramel,
R.drawable.donut,
R.drawable.lemon
)
override fun getItemCount(): Int = resources.size
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(resources[position])
}
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.pager.adapter = MyAdapter(this)
}
}
実行すると、画像がスワイプで順に表示される
なお、上記コードの onCreate を以下のように変更すると、5秒ごとに自動で表示が切り替わる
(「val handler」以降のみを追加)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.pager.adapter = MyAdapter(this)
val handler = Handler(Looper.getMainLooper())
timer(period = 5000) {
handler.post {
binding.apply {
pager.currentItem = (pager.currentItem + 1) % 4
}
}
}
}
また、activity_main.xml の「Attrivutes」で「All Atrrivutes → orientation」プロパティを「vertical」に設定すると、
左右ではなく上下で画面が切り替わるようになる
■アプリの作成(データの保存)
■SharedPreferencesでデータを保存
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: sharedpreferences
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
ビューにWebViewを配置する
activity_main.xml を開き、最初から表示されているTextViewを削除し、
「Text」内の「PlainText」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは0にする
垂直方向のバイアスは30にしておく
また、以下の設定を行う
id: editText
layout_width: wrap_content
layout_height: wrap_content
text: Text
「Buttons」内の「Button」をドラッグ&ドロップで中央に配置する
上をtextViewの下に接続し、下左右を画面の端に接続し、マージンは0にする
垂直方向のバイアスは20にしておく
また、以下の設定を行う
id: saveButton
layout_width: wrap_content
layout_height: wrap_content
text: 保存
MainActivity を以下のように変更する
package org.refirio.sharedpreferences
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.refirio.sharedpreferences.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// SharedPreferencesオブジェクトを取得
val testPref = getSharedPreferences("test", Context.MODE_PRIVATE)
// プリファレンスから、保存されている文字列を取得
val storedText = testPref.getString("edit", "テスト")
binding.editText.setText(storedText)
// ボタンがタップされたときの処理
binding.saveButton.setOnClickListener {
val inputText = binding.editText.text.toString()
testPref.edit().putString("edit", inputText).apply()
}
}
}
■アプリの作成(WebView)
■WebViewでWebページを表示
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: webview
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
マニフェストファイルを編集し、インターネットに接続できるようにする
追加場所は、ルートであるmanifestの直下でいい
manifests/AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
ビューにWebViewを配置する
activity_main.xml を開き、最初から表示されているTextViewを削除し、
「Wedgets」内の「WebView」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは0にする
また、以下の設定を行う
id: webView
layout_width: match_parent
layout_height: match_parent
さらに、アクティビティを以下のように変更
MainActivity.kt
package org.refirio.webview
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import org.refirio.webview.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.webView.loadUrl("https://refirio.net/")
binding.webView.setWebViewClient(object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
return false
}
})
}
}
GitHub - tyfkda/GawaNativeAndroid: 全画面に配置したWebViewでAndroidアプリを作るテスト
https://github.com/tyfkda/GawaNativeAndroid
[Android] アプリのタイトルバーを非表示、全画面表示にする、Theme.NoTitleBar
https://akira-watson.com/android/theme-notitlebar.html
WebViewでlinkタップ時にブラウザに飛ばないようにする - 布団の中にいたい
https://asahima.hatenablog.jp/entry/2017/01/08/000000
■アプリの作成(リスト表示)
■ListViewで一覧を表示
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: listview
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
ビューにListViewを配置する
activity_main.xml を開き、最初から表示されているTextViewを削除し、
「Legacy」内の「ListView」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは0にする
また、以下の設定を行う
id: timezonesView
layout_width: match_parent
layout_height: match_parent
リスト用のレイアウトを作成する
プロジェクトウインドウのツリーで「app → res → layout」を右クリックし、
「New → Layout resource file」を選択する
「New Resource File」ダイアログが開くので、「File name」に「list_timezone_cell」と入力して「OK」をクリックする
作成された list_timezone_cell.xml を開き、
「Text」内の「TextView」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは8にする
また、以下の設定を行う
id: timezoneView
layout_width: wrap_content
layout_height: wrap_content
さらに、アクティビティを以下のように変更
MainActivity.kt
package org.refirio.listview
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.refirio.listview.databinding.ActivityMainBinding
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// リストに表示するタイムゾーン
val timeZones = TimeZone.getAvailableIDs()
//val timeZones = listOf("タイムゾーン1", "タイムゾーン2", "タイムゾーン3")
// アダプタを作成
val adapter = ArrayAdapter<String>(
this,
R.layout.list_timezone_cell,
R.id.timezonesView,
timeZones
)
// リストにアダプタをセット
binding.timezonesView.adapter = adapter
// リストのアイテムタップ時の動作
binding.timezonesView.setOnItemClickListener { parent, view, position, id ->
// アダプタから、タップされた位置のタイムゾーンを得る
val timeZone = adapter.getItem(position)
// Toastで表示
Toast.makeText(applicationContext, timeZone, Toast.LENGTH_SHORT).show();
}
}
}
■RecyclerViewで一覧を表示
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: recyclerview
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
ビューにRecyclerViewを配置する
activity_main.xml を開き、最初から表示されているTextViewを削除し、
「Containers」内の「RecyclerView」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは0にする
また、以下の設定を行う
id: timezonesView
layout_width: match_parent
layout_height: match_parent
リスト用のレイアウトを作成する
プロジェクトウインドウのツリーで「app → res → layout」を右クリックし、
「New → Layout resource file」を選択する
「New Resource File」ダイアログが開くので、「File name」に「list_timezone_cell」と入力して「OK」をクリックする
作成された list_timezone_cell.xml を開き、
「Text」内の「TextView」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは8にする
また、以下の設定を行う
id: timezoneView
layout_width: match_constraint
layout_height: wrap_content
親のConstraintLayoutに以下の設定を行う
layout_width: match_parent
layout_height: wrap_content
リスト用のアダプタを作成する
プロジェクトウインドウのツリーで「app → java → org.refirio.recyclerview」を右クリックし、
「New → Kotlin Class/File」を選択する
「New Kotlin Class/File」ダイアログが開くので、「File name」に「TimezoneAcapter」と入力してEnterを入力する
作成された TimezoneAcapter を開き、以下を入力する
package org.refirio.recyclerview
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class TimezoneAdapter(
context: Context,
private val timeZones: Array<String>,
private val onItemClicked: (String) -> Unit
) : RecyclerView.Adapter<TimezoneAdapter.TimezoneViewHolder>() {
// レイアウトからViewを生成するInflater
private val inflater = LayoutInflater.from(context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimezoneViewHolder {
// Viewを生成する
val view = inflater.inflate(R.layout.list_timezone_cell, parent, false)
// ViewHolderを作る
val viewHolder = TimezoneViewHolder(view)
// Viewをタップしたときの処理
view.setOnClickListener {
// アダプター上の家を得る
val position = viewHolder.adapterPosition
// 位置に応じたデータを得る
val timeZone = timeZones[position]
// コールバックを呼び出す
onItemClicked(timeZone)
}
return viewHolder
}
override fun getItemCount() = timeZones.size
override fun onBindViewHolder(holder: TimezoneViewHolder, position: Int) {
// 位置に応じたデータを得る
val timeZone = timeZones[position]
// 表示内容を更新する
holder.timezone.text = timeZone
}
// Viewへの参照を持っておくViewHolder
class TimezoneViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val timezone = view.findViewById<TextView>(R.id.timezoneView)
}
}
さらに、MainActivity を以下のように変更
package org.refirio.recyclerview
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import org.refirio.recyclerview.databinding.ActivityMainBinding
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// リストに表示するタイムゾーン
val timeZones = TimeZone.getAvailableIDs()
//val timeZones = arrayOf("タイムゾーン1", "タイムゾーン2", "タイムゾーン3")
// アダプタを作成
val adapter = TimezoneAdapter(this, timeZones) {
timeZone ->
// Toastで表示
Toast.makeText(this, timeZone, Toast.LENGTH_SHORT).show();
}
// リストにアダプタをセット
binding.timezonesView.adapter = adapter
// 縦に直線的に表示するレイアウトマネージャをセット
binding.timezonesView.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL,
false
)
}
}
■RecyclerViewを並び替え
【Android】簡潔に RecyclerView を使う。 - 山崎屋の技術メモ
https://www.shookuro.com/entry/android-recycler-view
【Android】イメージを含んだリッチな行を持つ RecyclerView - 山崎屋の技術メモ
https://www.shookuro.com/entry/2021/02/11/145746
【Android】RecyclerView、行をドラッグして並び替え - 山崎屋の技術メモ
https://www.shookuro.com/entry/recycler-view3
【Android】RecyclerView つまみ(ハンドル)をドラッグして並び替え - 山崎屋の技術メモ
https://www.shookuro.com/entry/recycler-view4
Kotlin beginner: kotlin リストをドラッグして並べ替え(ItemTouchHelper)
https://cony-kotlin.blogspot.com/2020/10/kotlin-recyclerviewitemtouchhelper.html
RecyclerView の使い方。ドラッグ&ドロップで並び替え、スワイプで削除する。【Android】
https://negichou.com/recyclerview-and-itemtouchhelper-sample/
■アプリの作成(パーミッション)
■パーミッション
Marshmallow端末で、Permission利用確認をする。
http://qiita.com/mattak/items/82ba07259cfe3a2ce4b1
Android6では AndroidManifest.xml でのパーミッション指定を行った上で、
protectionLevel が dangerous 以上のパーミッションについてはさらに権限の確認を行う必要がある
■ファイル一覧を表示
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: filelist
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
マニフェストファイルを編集し、インターネットに接続できるようにする
追加場所は、ルートであるmanifestの直下でいい
manifests/AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
ビューにRecyclerViewを配置する
activity_main.xml を開き、最初から表示されているTextViewを削除し、
「Containers」内の「RecyclerView」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは0にする
また、以下の設定を行う
id: filesView
layout_width: match_parent
layout_height: match_parent
リスト用のレイアウトを作成する
プロジェクトウインドウのツリーで「app → res → layout」を右クリックし、
「New → Layout resource file」を選択する
「New Resource File」ダイアログが開くので、「File name」に「list_file_cell」と入力して「OK」をクリックする
作成された list_file_cell.xml を開き、
「Text」内の「TextView」をドラッグ&ドロップで中央に配置する
上下左右を画面の端に接続し、マージンは8にする
また、以下の設定を行う
id: fileView
layout_width: wrap_content
layout_height: wrap_content
親のConstraintLayoutに以下の設定を行う
layout_width: match_parent
layout_height: wrap_content
リスト用のアダプタを作成する
プロジェクトウインドウのツリーで「app → java → org.refirio.filelist」を右クリックし、
「New → Kotlin Class/File」を選択する
「New Kotlin Class/File」ダイアログが開くので、「File name」に「FileAcapter」と入力してEnterを入力する
作成された FileAcapter を開き、以下を入力する
package org.refirio.filelist
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import java.io.File
class FileAdapter(
context: Context,
private val files: List<File>,
private val onItemClicked: (File) -> Unit
) : RecyclerView.Adapter<FileAdapter.FileViewHolder>() {
// レイアウトからViewを生成するInflater
private val inflater = LayoutInflater.from(context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
// Viewを生成する
val view = inflater.inflate(R.layout.list_file_cell, parent, false)
// ViewHolderを作る
val viewHolder = FileViewHolder(view)
// Viewをタップしたときの処理
view.setOnClickListener {
/*
// アダプター上の家を得る
val position = viewHolder.adapterPosition
// 位置に応じたデータを得る
val timeZone = timeZones[position]
*/
// コールバックを呼び出す
onItemClicked(files[viewHolder.adapterPosition])
}
return viewHolder
}
override fun getItemCount() = files.size
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
// 位置に応じたデータを得る
val file = files[position].name
// 表示内容を更新する
holder.file.text = file
}
// Viewへの参照を持っておくViewHolder
class FileViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val file = view.findViewById<TextView>(R.id.fileView)
}
}
さらに、MainActivity を以下のように変更
package org.refirio.filelist
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.refirio.filelist.databinding.ActivityMainBinding
import java.io.File
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var currentDir : File = Environment.getExternalStorageDirectory()
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.filesView.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL,
false
)
// パーミッションを確認
if (hasPermission()) {
// ファイル一覧を表示
showFiles()
}
}
private fun showFiles() {
val adapter = FileAdapter(
this,
currentDir.listFiles().toList()
) { file ->
if (file.isDirectory) {
currentDir = file
showFiles()
} else {
// Toastで表示
Toast.makeText(this, file.absolutePath, Toast.LENGTH_SHORT).show();
}
}
// リストにアダプタをセット
binding.filesView.adapter = adapter
// アプリバーに表示中のディレクトリのパスを設定する
title = currentDir.path
}
private fun hasPermission() : Boolean {
// パーミッションを持っているか確認
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 持っていないならパーミッションを要求
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
return false
}
return true
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
//super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (!grantResults.isEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showFiles()
} else {
finish()
}
}
override fun onBackPressed() {
if (currentDir != Environment.getExternalStorageDirectory()) {
currentDir = currentDir.parentFile
showFiles()
} else {
super.onBackPressed()
}
}
}
■アプリの作成(ドロワーメニュー)
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: drawer
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
メニュー用のフラグメントを作成する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Fragment → Fragment(Blank)」を選択する
表示されたダイアログで「Fragment Name」で「MenuFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「MenuFragment.kt」と「fragment_menu.xml」が追加される
fragment_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<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">
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/firstButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="First" />
<Button
android:id="@+id/secondButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Second" />
<Button
android:id="@+id/thirdButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Third" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MenuFragment
package org.refirio.drawer
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
class MenuFragment() : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_menu, container, false)
val firstButton = view.findViewById<Button>(R.id.firstButton)
firstButton.setOnClickListener {
val listener = context as? OnButtonClickListener
listener?.onFirstButtonClicked()
}
val secondButton = view.findViewById<Button>(R.id.secondButton)
secondButton.setOnClickListener {
val listener = context as? OnButtonClickListener
listener?.onSecondButtonClicked()
}
val thirdButton = view.findViewById<Button>(R.id.thirdButton)
thirdButton.setOnClickListener {
val listener = context as? OnButtonClickListener
listener?.onThirdButtonClicked()
}
return view
}
interface OnButtonClickListener {
fun onFirstButtonClicked()
fun onSecondButtonClicked()
fun onThirdButtonClicked()
}
}
コンテンツ用のフラグメントを作成する
プロジェクトウインドウのツリーで「app」を右クリックし、
「New → Fragment → Fragment(Blank)」を選択する
表示されたダイアログで「Fragment Name」で「FirstFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す
プロジェクトウインドウのツリーに「FirstFragment.kt」と「fragment_first.xml」が追加される
fragment_first.xml
<?xml version="1.0" encoding="utf-8"?>
<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">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="First"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FirstFragment.kt
package org.refirio.drawer
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class FirstFragment() : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_first, container, false)
return view
}
}
同様に SecondFragment と ThirdFragment を作成する
(SecondFragment.kt と fragment_second.xml と ThirdFragment.kt と fragment_third.xml が追加される。)
メインアクティビティとそのレイアウトを実装する
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- メインコンテンツ -->
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</FrameLayout>
<!-- ドロワーコンテンツ -->
<FrameLayout
android:id="@+id/drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?android:attr/colorBackground">
<fragment
android:id="@+id/fragment"
android:name="org.refirio.drawer.MenuFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_menu" />
</FrameLayout>
</androidx.drawerlayout.widget.DrawerLayout>
MainActivity.kt
package org.refirio.drawer
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.drawerlayout.widget.DrawerLayout
class MainActivity : AppCompatActivity(), MenuFragment.OnButtonClickListener {
// ドロワーの状態操作用オブジェクト
private var drawerToggle : ActionBarDrawerToggle? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (supportFragmentManager.findFragmentByTag("ContentFragment") == null) {
supportFragmentManager.beginTransaction()
.add(R.id.container, FirstFragment(), "ContentFragment")
.commit()
}
// レイアウトからドロワーを探す
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
// ドロワーを作成する
val toggle = ActionBarDrawerToggle(this, drawerLayout, R.string.app_name, R.string.app_name)
toggle.isDrawerIndicatorEnabled = true
drawerLayout.addDrawerListener(toggle)
// ドロワーの状態を保持
drawerToggle = toggle
// ドロワーの設定を行う
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setHomeButtonEnabled(true)
}
}
override fun onFirstButtonClicked() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, FirstFragment())
.addToBackStack(null)
.commit()
}
override fun onSecondButtonClicked() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, SecondFragment())
.addToBackStack(null)
.commit()
}
override fun onThirdButtonClicked() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, ThirdFragment())
.addToBackStack(null)
.commit()
}
// アクティビティの生成が終わった後に呼ばれる
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// ドロワーのトグルの状態を回復する
drawerToggle?.syncState()
}
// 画面構成が変わったときに呼ばれる
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 状態の変化をドロワーに伝える
drawerToggle?.onConfigurationChanged(newConfig)
}
// オプションメニューがタップされたときに呼ばれる
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// ドロワーに伝える
if (drawerToggle?.onOptionsItemSelected(item) == true) {
return true
} else {
return super.onOptionsItemSelected(item)
}
}
}
ここまででいったん完成
引き続き、
・縦表示ならドロワーメニュー
・横表示ならサイドメニュー
としてみる
横表示用にレイアウトを追加する
activity_main.xml を開き、「Design」タブで表示する
タブ内のツールバーから「Orientation in Editor → Create Landscape Variation」を選択する
以下のファイルが作成される。内容はもとの activity_main.xml と同じものになる
app/src/main/res/layout-land/activity_main.xml
このファイルを以下のように修正する
(rootのIDをdrawerLayoutから別のものに変えると、「Configurations for activity_main.xml must agree on the root element's ID.」のエラーになった
このエラーを回避するために、rootのDrawerLayoutは触らずにLinearLayoutを追加している
正しい方法かどうかは不明)
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- ドロワーコンテンツ -->
<FrameLayout
android:id="@+id/drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?android:attr/colorBackground">
<fragment
android:id="@+id/fragment"
android:name="org.refirio.drawer.MenuFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_menu" />
</FrameLayout>
<!-- メインコンテンツ -->
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</FrameLayout>
</LinearLayout>
</androidx.drawerlayout.widget.DrawerLayout>
MainActivity.kt を以下のように変更する
package org.refirio.drawer
import android.content.res.Configuration
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.drawerlayout.widget.DrawerLayout
class MainActivity : AppCompatActivity(), MenuFragment.OnButtonClickListener {
// ドロワーの状態操作用オブジェクト
private var drawerToggle : ActionBarDrawerToggle? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (supportFragmentManager.findFragmentByTag("ContentFragment") == null) {
supportFragmentManager.beginTransaction()
.add(R.id.container, FirstFragment(), "ContentFragment")
.commit()
}
val orientation = resources.configuration.orientation
// 縦向きの場合
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
val drawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
setupDrawer(drawerLayout)
}
}
override fun onFirstButtonClicked() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, FirstFragment())
.addToBackStack(null)
.commit()
}
override fun onSecondButtonClicked() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, SecondFragment())
.addToBackStack(null)
.commit()
}
override fun onThirdButtonClicked() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, ThirdFragment())
.addToBackStack(null)
.commit()
}
// アクティビティの生成が終わった後に呼ばれる
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// ドロワーのトグルの状態を回復する
drawerToggle?.syncState()
}
// 画面構成が変わったときに呼ばれる
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 状態の変化をドロワーに伝える
drawerToggle?.onConfigurationChanged(newConfig)
}
// オプションメニューがタップされたときに呼ばれる
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// ドロワーに伝える
if (drawerToggle?.onOptionsItemSelected(item) == true) {
return true
} else {
return super.onOptionsItemSelected(item)
}
}
private fun setupDrawer(drawerLayout: DrawerLayout) {
// ドロワーを作成する
val toggle = ActionBarDrawerToggle(this, drawerLayout, R.string.app_name, R.string.app_name)
toggle.isDrawerIndicatorEnabled = true
drawerLayout.addDrawerListener(toggle)
// ドロワーの状態を保持
drawerToggle = toggle
// ドロワーの設定を行う
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setHomeButtonEnabled(true)
}
}
}
■アプリの作成(カメラ)
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: camerax
ビューバインディングを使えるようにする
さらに、カメラを使うために以下も追加する
dependencies {
〜略〜
def camerax_version = "1.0.0-alpha01"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
}
build.gradle を変更したら「Sync Now」をクリック
マニフェストファイルを編集し、撮影と保存ができるようにする
追加場所は、ルートであるmanifestの直下でいい
manifests/AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
引き続きまとめ中
■アプリの作成(テンプレートから作成)
Androidでログイン機能を作る場合、AndroidStudio標準のテンプレートにあるので利用できるかも
その他、標準のテンプレートが以下で解説されている
テンプレートからコードを追加する | Android Developers
https://developer.android.com/studio/projects/templates?hl=ja
以下で新規にプロジェクトを作成
プロジェクトの選択: Empty Activity
プロジェクトの名前: reader
ビューバインディングを使えるようにする
build.gradle を変更したら「Sync Now」をクリック
画像表示のために、Picassoを使えるようにする
同ファイルの dependencies 内の implementation の一覧の最後に以下を追加
implementation 'com.squareup.picasso:picasso:2.71828'
build.gradle を変更したら「Sync Now」をクリック
なお、バージョンとして「2.5.2」を指定すると「Sync Now」は表示されなかった
https://github.com/square/picasso を参考に、「2.71828」を指定すると表示された
Webページ表示のために、Chrome Custom Tabsを使えるようにする
同ファイルの dependencies 内の implementation の一覧の最後に以下を追加
implementation 'androidx.browser:browser:1.0.0'
build.gradle を変更したら「Sync Now」をクリック
今も「androidx.browser:browser:1.0.0」の指定で正しいかどうかは確認しておきたい
なお「implementation 'com.android.support:customtabs:27.1.1'」という指定は古い方法だと思われる
APK creation failure due to Duplicate class android.support - Stack Overflow
https://stackoverflow.com/questions/59239626/apk-creation-failure-due-to-duplicate-class-android-sup...
AWS CognitoにAndroidネイティブアプリでサインインする - Qiita
https://qiita.com/poruruba/items/12985ec474364594be55
マニフェストファイルを編集し、インターネットに接続できるようにする
追加場所は、ルートであるmanifestの直下でいい
manifests/AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
リスト用のレイアウトを作成する
res/layout/list_article_cell.xml
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content">
<TextView
android:id="@+id/titleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.08"
tools:text="ニュースタイトル" />
<TextView
android:id="@+id/nameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView"
tools:text="名前" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.98"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:drawable/ic_popup_sync" />
</androidx.constraintlayout.widget.ConstraintLayout>
メイン画面のレイアウトを調整する
res/layout/lactivity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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.recyclerview.widget.RecyclerView
android:id="@+id/articlesView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/list_article_cell" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/java/org.refirio.reader/HttpTask.kt
package org.refirio.reader
import android.os.AsyncTask
import android.util.Log
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
class HttpTask(callback: (String?) -> Unit) : AsyncTask<String, Unit, String>() {
var callback = callback
override fun doInBackground(vararg params: String): String? {
val url = URL(params[1])
val httpClient = url.openConnection() as HttpURLConnection
httpClient.setReadTimeout(10 * 1000)
httpClient.setConnectTimeout(10 * 1000)
httpClient.requestMethod = params[0]
if (params[0] == "POST") {
httpClient.instanceFollowRedirects = false
httpClient.doOutput = true
httpClient.doInput = true
httpClient.useCaches = false
httpClient.setRequestProperty("Content-Type", "application/json; charset=utf-8")
}
try {
if (params[0] == "POST") {
httpClient.connect()
val os = httpClient.getOutputStream()
val writer = BufferedWriter(OutputStreamWriter(os, "UTF-8"))
writer.write(params[2])
writer.flush()
writer.close()
os.close()
}
if (httpClient.responseCode == HttpURLConnection.HTTP_OK) {
val stream = BufferedInputStream(httpClient.inputStream)
val data: String = readStream(inputStream = stream)
return data
} else {
Log.d("HttpTask", "ERROR: ${httpClient.responseCode}")
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
httpClient.disconnect()
}
return null
}
fun readStream(inputStream: BufferedInputStream): String {
val bufferedReader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
bufferedReader.forEachLine { stringBuilder.append(it) }
return stringBuilder.toString()
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
callback(result)
}
}
app/java/org.refirio.reader/ArticleAcapter.kt
package org.refirio.reader
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.json.JSONArray
import org.json.JSONObject
import java.text.ParseException
import java.text.SimpleDateFormat
class ArticleAcapter(
context: Context,
private val articles: JSONArray,
private val onItemClicked: (JSONObject) -> Unit
) : RecyclerView.Adapter<ArticleAcapter.ViewHolder>() {
// レイアウトからViewを生成するInflater
private val inflater = LayoutInflater.from(context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// Viewを生成する
val view = inflater.inflate(R.layout.list_article_cell, parent, false)
// ViewHolderを作る
val viewHolder = ViewHolder(view)
// Viewをタップしたときの処理
view.setOnClickListener {
// アダプター上の家を得る
val position = viewHolder.adapterPosition
// 位置に応じたデータを得る
val article = articles.getJSONObject(position)
// コールバックを呼び出す
onItemClicked(article)
}
return viewHolder
}
override fun getItemCount() = articles.length()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// 位置に応じたデータを得る
val article = articles.getJSONObject(position)
// 表示内容を更新する
holder.title.text = article.getString("title")
holder.name.text = "by " + article.getString("name") + " at " + article.getString("datetime").toDateTime("MM/dd HH:mm")
Picasso.get()
.load(article.getString("image")) // 画像のURL
.resize(100, 100) // 表示サイズを指定
.centerCrop() // 中央から切り出し
.into(holder.image) // imageViewに流し込み
}
// Viewへの参照を持っておくViewHolder
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val title = view.findViewById<TextView>(R.id.titleView)
val name = view.findViewById<TextView>(R.id.nameView)
val image = view.findViewById<ImageView>(R.id.imageView)
}
}
fun String.toDateTime(pattern: String = "yyyy/MM/dd HH:mm:ss"): String {
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val datetime = try {
dateFormat.parse(this)
} catch (e: ParseException) {
null
}
if (datetime == null) {
return ""
} else {
return SimpleDateFormat(pattern).format(datetime)
}
}
app/java/org.refirio.reader/MainActivity.kt
package org.refirio.reader
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent
import androidx.recyclerview.widget.LinearLayoutManager
import org.json.JSONObject
import org.refirio.reader.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// JSONを読み込み
HttpTask({
if (it == null) {
println("Data is empty.")
return@HttpTask
}
// JSONを解析する
val json = JSONObject(it)
val articles = json.getJSONArray("articles")
// RecyclerViewをレイアウトから探す
//val recyclerView = findViewById<RecyclerView>(R.id.articles)
// RecyclerViewにアダプターをセットする
binding.articlesView.adapter = ArticleAcapter(this, articles) { article ->
// 記事をタップしたらChrome Custom Tabsで開く
val intent = CustomTabsIntent.Builder().build()
intent.launchUrl(this, Uri.parse(article.getString("url")))
}
// レイアウトマネージャーをセットする
binding.articlesView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
}).execute("GET", "https://refirio.org/reader/?type=json")
}
}
■非同期通信
※未検証
※AsyncTaskと同等の処理をCoroutinesという機能で実装できるようになっているとのこと
Kotlin Coroutine 入門1: 起動と suspend - Qiita
https://qiita.com/wm3/items/48b5b5c878561ff4761a
KotlinとCoroutinesでAyncTask - Qiita
https://qiita.com/naoi/items/8be7d7e331668f6c67d4
Kotlin 1.3をサクッと学ぶ - CoroutineとKotlin/Nativeを触って理解しよう - エンジニアHub|Webエンジニアのキャリアを考える!
https://eh-career.com/engineerhub/entry/2020/02/04/103000
■製品用、開発用などの切り分け
パッケージ名が同じアプリは上書きインストールされる
つまり、GooglePlayからインストールした本番アプリがあると、その端末には開発版をインストールできない
が、フレーバーを使用することによりこの問題を解消できる
flavorDimensionsによるflavorの指定方法 - Qiita
https://qiita.com/boohbah/items/389b159a1693247b15de
■前提
productFlavors
production ... 製品用
develop ... 開発用
buildTypes
release ... 製品用
debug ... 開発用
と設定するものとする
■プロジェクトの作成
新規 Android Studio プロジェクトの開始
↓
新規プロジェクトの作成
アプリケーション名: Build Test1
会社ドメイン: refirio.net
プロジェクトの場所: C:\Users\refirio\AndroidStudioProjects\BuildTest1 (アプリケーション名をもとに自動入力される)
パッケージ名: net.refirio.buildtest1 (アプリケーション名と会社ドメインをもとに自動入力される)
Kitlinサポートを含める: チェックする
「次へ」
↓
ターゲットAndroidデバイス
「API 19: Android 4.4 (KitKat)」にして「次へ」
↓
Mobile にアクティビティを追加する
「空のアクティビティ」が選択されているので、そのまま「次へ」
↓
アクティビティの設定
特に変更せず「完了」
エミュレータと実機で、アプリを起動できるかテストする
■Flavorの設定
app の build.gradle の
buildTypes {
の上(下ではない)に以下のコードを追加
flavorDimensions "mode"
productFlavors {
production {
}
develop {
applicationIdSuffix ".dev"
}
}
さらに
buildTypes {
の中に以下のコードを追加
(デバッグ版書き出し時に「.debug」を付けたくないなら、省略しても良さそう。要検証)
debug {
applicationIdSuffix ".debug"
}
さらに manifestPlaceholders を追加
(デバッグ版とリリース版で別のアプリ名を表示する準備)
android {
compileSdkVersion XX
defaultConfig {
〜 略 〜
manifestPlaceholders = [appName:"@string/app_name"]
}
〜 略 〜
productFlavors {
〜 略 〜
develop {
〜 略 〜
manifestPlaceholders = [appName:"@string/app_name_dev"]
}
}
}
app\src\main\AndroidManifest.xml のアプリ名を調整
(デバッグ版とリリース版で別のアプリ名を表示する準備)
android:label="@string/app_name"
↓
android:label="${appName}"
app\src\main\res\values\strings.xml で開発版のアプリ名を追加
(デバッグ版とリリース版で別のアプリ名を定義)
<string name="app_name">Build Test1</string>
<string name="app_name_dev">Build Test1 Dev</string> … 追加
■ビルドの切り替え
画面左端の「ビルド・バリアント」をクリックすると、その中の「ビルド・バリアント」部分でビルドを切り替えられる
「developDebug」と「productionDebug」を切り替えると、名前の違うアプリをそれぞれインストールできる
「developRelease」と「productionRelease」は何故かエラーになって実行できない
プロジェクト作成直後も「debug」と「release」から選択できるが、この「release」も実行できない
リリース版は署名の登録が必要…などがあるみたい
要勉強
Android Studio : debugビルドとReleaseビルドの切替、releaseビルドの追加方法、署名付きapk作成方法 - 生活を良くします-怠惰なプログラミング
https://www.what-a-day.net/entry/2016/12/11/001948
■ビルド設定によるプログラムの分岐
設定が完了した上で実行すると、以下にプログラムが自動作成される
app\build\generated\source\buildConfig\production\debug\jp\terraport\buildtest1\BuildConfig.java
この内容を利用して、以下のように分岐する
if (BuildConfig.DEBUG) {
Log.d("TEST", "DEBUG")
} else {
Log.d("TEST", "RELEASE")
}
if ("develop".equals(BuildConfig.FLAVOR)) {
Log.d("TEST", "develop")
} else {
Log.d("TEST", "production")
}
■その他参考になりそうなページ
アプリケーション ID の設定 | Android Developers
https://developer.android.com/studio/build/application-id?hl=ja
https://developer.android.com/studio/build/application-id?hl=ja#%E3%83%93%E3%83%AB%E3%83%89_%E3%83%9...
■野良アプリとして書き出す
■APKの書き出し
ビルド
Build Bundle(s) / APK(s)
APKのビルド
プロジェクトの
app\build\outputs\apk\debug
内に app-debug.apk という名前で出力されている
(実行しなくても、ビルドの度に自動作成されているみたい?)
■ダウンロードページの作成
/download/index.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>テスト</title>
</head>
<body>
<h1>テスト</h1>
<p>デバイスのブラウザより、以下のリンクからインストールをお願いします。</p>
<ul>
<li><a href="apk/app-debug.apk">Android用アプリをインストール</a></li>
</ul>
</body>
</html>
■ダウンロードと起動
※あらかじめ、APKから直接インストールできるように端末を設定しておく
Android.txt の「野良アプリのインストールを許可」を参照
上記ページにChromeでアクセスし、「Android用アプリをインストール」リンクを長押しする(タップだとインストールできないことがある)
ダイアログが表示されるので
「リンクをダウンロード」
を選択する。ダウンロードが完了すると
「開く」
が表示されるのでタップ
「このアプリケーションをインストールしてもよろしいですか?」
と聞いてくる
「インストール」
をタップでインストールされる
もしくは、過去にダウンロード済みのファイルをタップする
ダイアログが表示され
「インストール」
を選択すると
「この既存のアプリにアップデートをインストールしますか?」
と聞いてくる
「インストール」
をタップでインストールされる
■ダウンロードと起動の補足
※文言や細かな挙動は、端末によって異なる可能性がある
「ファイルをダウンロードするには、Chromeでストレージへのアクセスを許可する必要があります」
と表示された場合、
「続行」
を選択。遷移先の画面で
「権限」の「ストレージ」
で許可して元の画面に戻る
「セキュリティ上の理由から、お使いのスマートフォンではこの提供元からの不明なアプリをインストールすることはできません」
と表示された場合、
「設定」
を選択。遷移先の画面で
「この提供元のアプリを許可」
で許可して元の画面に戻る
「この種類のファイルはお使いの端末に悪影響を与える可能性があります。」
と表示された場合、
「OK」
にするとダウンロードされる
「もう一度ダウンロードしますか?」
と聞いてきた場合、
「ダウンロード」
にするとダウンロードされる
開く際にアプリの選択を求められたら
「パッケージインストーラー」
を選択する
ダウンロードしたファイルをタップして
「ファイルを開けません」
と聞いてきた場合、最初の手順でリンクをタップせずに長押ししてダウンロードする
「パッケージの解析中にエラーが発生しました」
と表示された場合、要求されるOSのバージョンを満たしていない可能性がある
それでもインストールできない場合、AndroidStudioでの書き出し手順に問題が無いか確認する
■アンインストール
設定 → アプリケーション → (アプリ名) → アンインストール
を選択すると
「このアプリをアンインストールしますか?」
と聞いてくるので
「OK」
でアンインストールされる
その状態で再度インストールすると、普通にインストールされた
なお、アプリ一覧画面の右上ボタンで「アプリの設定をリセット」を選択すると、諸々の許可設定などがリセットされる
間違った選択をしてインストールできなくなった場合などに試す
以下の問題は過去のものかもしれないが、インストールできない場合に確認する
Android Lollipopでapkがインストールできない問題 - Qiita
https://qiita.com/orangain/items/a70f5b774296e609fb75
■作業アカウントの追加
■Google Play
デベロッパー アカウント ユーザーの追加と権限の管理 - Play Console ヘルプ
https://support.google.com/googleplay/android-developer/answer/2528691?hl=ja
■Firebase
プロジェクト メンバーを管理する - Firebase ヘルプ
https://support.google.com/firebase/answer/7000272?hl=ja
■テスト
テスト公開については要調査
以下はテスターを追加したときのメモ
■テスターの追加
Google Play Consoleで
「設定 → テスターの管理 → リストを作成」
からグループとユーザを追加。すでにグループが存在していれば、該当のリストを編集してメールアドレスを追加
■リリース
Google Play Storeにアプリを公開する - Qiita
https://qiita.com/minuro/items/536ac3f7c27c1442a1cb
【Androidアプリ開発 vol.12】完成したAndroidアプリをGooglePlayストアで公開する手順? | くねおの電脳リサーチ
https://kuneoresearch.com/android-appdev12-release-to-google-play01/
■トラブル
■エミュレータがインターネットに繋がらない
MacでAPI27のエミュレータがインターネットに繋がらない場合、
Mac側で「システム環境設定 → ネットワーク → 詳細 → DNS」に「8.8.8.8」と「8.8.4.4」を追加する
「OK → 適用」として確認する
AndroidStudio付属のエミュレータがネットワークに繋がらない時の対処法(API27で動作確認) | takelab.note
https://wandering-engineer.tech/2019/08/23/post-4626/
■既存プロジェクトを開くとエラー
古いプロジェクトを開くと、以下のようなエラーが表示されるものがあった
Calendar
エラー :(52, 0) Could not find property 'debugKeystore' on SigningConfig_Decorated{name=debug, storeFile=C:\Users\refirio\.android\debug.keystore, storePassword=android, keyAlias=AndroidDebugKey, keyPassword=android, storeType=C:\Users\refirio\.android\debug.keystore}.
<a href="openFile:C:\Users\refirio\AndroidStudioProjects\Calendar\app\build.gradle">Open File</a>
Camera
エラー :(52, 0) Could not get unknown property 'debugKeystore' for SigningConfig_Decorated{name=debug, storeFile=C:\Users\refirio\.android\debug.keystore, storePassword=android, keyAlias=AndroidDebugKey, keyPassword=android, storeType=C:\Users\refirio\.android\debug.keystore, v1SigningEnabled=true, v2SigningEnabled=true} of type com.android.build.gradle.internal.dsl.SigningConfig.
<a href="openFile:C:\Users\refirio\AndroidStudioProjects\Camera\app\build.gradle">Open File</a>
gradle.properties を作成する
C:\Users\refirio\AndroidStudioProjects\Camera\gradle.properties
#デバッグ用のデバッグ署名
debugKeystore="../../../../../.android/debug.keystore"
DEV_KEY_ALIAS=androiddebugkey
DEV_STORE_PASSWORD=android
DEV_KEY_PASSWORD=android
#release用の署名情報
productKeystore="../../../xxxx.keystore"
KEY_ALIAS=xxxx
STORE_PASSWORD=xxxx
KEY_PASSWORD=xxxx
設定するとエラーの内容が変わった
エラー :Failed to find target with hash string 'android-25' in: C:\Users\refirio\AppData\Local\Android\Sdk
<a href="install.android.platform">Install missing platform(s) and sync project</a>
リンクをクリックすると、Android SDK Platform 25 のダウンロードとインストールがはじまった
その後も何度かインストールを促されるので、すべてインストール
ひととおりインストールしたら実行ボタン(実機書き出し)を押せるようになったが、実行すると
C:\Users\refirio\AndroidStudioProjects\Camera\app\src\main\java\xxx\app\util\GoogleAnalyticsUtil.java
エラー :(65, 70) エラー: シンボルを見つけられません
シンボル: 変数 analytics
場所: クラス xml
と言われ、ソースコードの
tracker = GoogleAnalytics.getInstance(mContext).newTracker(R.xml.analytics);
にフォーカスが当たった
1行目の package xxx.app.util; に赤線が引かれて
The SDK platform-tools version (24.0.1) is too old to check APIs compiled with API 25
と言われる。APIレベル25までインストールが必要
ツール → Android → SDK Manager
でAndroid7.1.1(APIレベル25)までインストール
AndroidStudioを再起動
アップデートがあると言われたのでインストール
プロジェクトを開くと、Gradleの同期とビルドが始まった
実行してみるが、それでも
C:\Users\refirio\AndroidStudioProjects\Camera\app\src\main\java\xxx\app\util\GoogleAnalyticsUtil.java
エラー :(65, 70) エラー: シンボルを見つけられません
シンボル: 変数 analytics
場所: クラス xml
と言われた。リポジトリ作成者に確認すると、res/xml/ 内に必要なファイルが無かった
プッシュされていなかっただけだったので、プッシュ&プルで取り込んで解決した
■ビルド時に Kotlin Gradle plugin version のエラーになる
このファイルの「AndroidStudioをバージョンアップしたときのメモ」を参照
■Android Studio が起動しない
studio.exe と同じ場所に studio.bat があるので、これをコマンドプロンプトから実行する
起動時にエラーがあれば、以下のようにエラーメッセージが表示される
>studio.bat
Error opening zip file or JAR manifest missing : C:\Users\Refirio\.AndroidStudio3.2\config\jp.sourceforge.mergedoc.pleiades\pleiades.jar
Error occurred during initialization of VM
agent library failed to init: instrument
■その他メモ
Androidアプリエンジニアの基礎知識 - Speaker Deck
https://speakerdeck.com/nein37/androidapurienziniafalseji-chu-zhi-shi
もしあなたが急にAndroidアプリを業務で作るはめになった場合の選択肢(2021年初頭版) - Qiita
https://qiita.com/Gazyu/items/dafdb74c4aadf722da92
[kotlin]アンドロイドでリアルタイム画像認識アプリをつくる - Qiita
https://qiita.com/YS-BETA/items/cd412524932dda9ac44c
【Kotlin】OpenCVをインストールして使う方法 | 西住工房
https://algorithm.joho.info/programming/kotlin/opencv-install-kt/