- 投稿日:2019-03-07T22:20:08+09:00
TornadeFXでメインのウインドウを閉じたり閉じたときの処理を実装する
tornadoFXで出てくるメインウィンドウを閉じいたときの処理で詰まったのでメモ
方法
primaryStageのclose()[閉じるとき]やsetOnCloseRequest()またはshowingProperty().addListener()[閉じる処理を書くとき]を呼ぶ
Main.ktimport tornadofx.* class Main:App(MyView::class) class MyView :View() { override val root = Form() val textfield = textfield ("hello world") init{ with(root){ fieldset{ textfield //閉じるボタン button { action { primaryStage.close() } } } } //プログラム上から閉じたらこっちは呼ばれない //primaryStage.setOnCloseRequest { e -> println("close")} //こっちだとプログラム上で消しても呼ばれる primaryStage.showingProperty().addListener {obs,oldValue,newValue-> if (oldValue == true && newValue == false) { println("close") } } } }説明
Appのコンストラクタで指定してるのはJavaFXでいうところのStageに乗せるSceneなので、指定してるViewでcloseしても意味がない。Viewが載っているStageにprimaryStageでアクセスできるので、それのclose()を呼べば閉じる。
消したときの処理も同様。ただし、onCloseRequestはプログラム上からcloseしたときは反応しない(ウインドウ上のXボタンを押したときのみ)ので、このページで言われているようにshowingProperty()を使ったほうがいい。
あとがき
javaFXとの対応関係が解れば当たり前の話だったけど、main周りがかなり違って対応関係が解らずかなり詰まった。
参考
- 投稿日:2019-03-07T05:00:24+09:00
あの素晴らしいlicense-tools-pluginをもう一度
はじめに
Qiita初投稿になります。6年程会社でプログラマとして仕事をし、今はフリーランスでAndroidアプリを中心にJava/Kotlinでゴリゴリ開発してます。今回、Androidアプリで使っているライブラリの表示をする必要がありまして、以前採用させていただいたクックパッドさんの
license-tools-pluginを使おうとしたのですが、プラグイン自体のアップデートやIDE(Android Studio)の更新もあり、すんなり導入することができませんでした。公式が クックパッド開発者ブログ で解説していますが、導入方法とハマったポイントを少し長いですが共有します。間違いや補足などがあればコメントにて教えてください。
開発環境
- PC: MacBook Air (11-inch, Early 2014)
- OS: macOS Mojave 10.14.3
- IDE: Android Studio 3.3.2
license-tools-pluginとは
クックパッドさんが開発したGradleの神プラグインです。プロジェクトで使用しているライブラリのライセンスをYAMLで管理、HTMLもしくはJSONに一覧を出力してくれます。ライセンスの追記漏れもチェックしてくれる優れものです。GitHubのリポジトリは こちら です。
導入
Gradleのプラグインなので、プロジェクトルート直下の
build.gradleに追記します。記事執筆時の最新バージョンは1.7.0でした。build.gradle[root]buildscript { ... dependencies { classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.cookpad.android.licensetools:license-tools-plugin:1.7.0' // 追加 } }アプリ内で使用する為に、app直下の
build.gradleの冒頭に追記をします。build.gradle[app]apply plugin: 'com.cookpad.android.licensetools' // 追加動作する為に
JDK8以上が必須とのことなので、こちらもbuild.gradleに互換性を追記しておきます。JDK8はAndroid Studio 3.3.2であれば、ビルトインとしてあらかじめ用意されているはずです。build.gradle[app]android { ... // 追加 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }全ての追記が終わったら
Sync Projectを行い、エラーが出ていなければOKです。
使い方
ライブラリの依存関係を確認
ライブラリの依存関係を確認します。今回はデモの為に、Androidアプリの開発でよく使うものを追加しました。
build.gradle[app]dependencies { // Kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // Android Support Library implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' // Square implementation 'com.squareup.moshi:moshi:1.8.0' implementation 'com.squareup.moshi:moshi-kotlin:1.8.0' implementation 'com.squareup:otto:1.3.8' implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.squareup.retrofit2:retrofit:2.5.0' // ReactiveX implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxjava:2.2.7' }ライセンス情報の作成
依存関係の記述が終わったら、Android Studioのサイドバーにある
Gradle > [project-name] > :app > Tasks > verificationから、checkLicensesタスクを実行します。コンソールにライセンス情報の一覧が出力されるので、app直下にlicenses.ymlを作成して内容をコピペします。licenses.yml- artifact: android.arch.core:common:+ name: Android Arch-Common copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://developer.android.com/topic/libraries/architecture/index.html - artifact: com.squareup.okhttp3:okhttp:+ name: OkHttp copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: org.jetbrains.kotlin:kotlin-stdlib:+ name: org.jetbrains.kotlin:kotlin-stdlib copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://kotlinlang.org/ - artifact: com.squareup.okio:okio:+ name: Okio copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: com.android.support:support-annotations:+ name: Android Support Library Annotations copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html - artifact: com.android.support:design:+ name: Material Components for Android copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html - artifact: com.android.support:support-core-utils:+ name: Android Support Library core utils copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html - artifact: com.android.support:interpolator:+ name: Android Support Library Interpolators copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html # 以下、長いので省略ライセンス情報の編集
その後
licenses.yamlの情報を編集していきます。具体的には#COPYRIGHT_HOLDER#や#LICENSE#と言ったプレースホルダ部分を編集します。例えばlicenses.yaml- artifact: com.android.support:appcompat-v7:+ name: Android AppCompat Library v7 copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: com.squareup.retrofit2:retrofit:+ name: Retrofit copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: io.reactivex.rxjava2:rxjava:+ name: RxJava copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE#のような一覧が出力された場合は
licenses.yaml- artifact: com.android.support:appcompat-v7:+ name: Android AppCompat Library v7 copyrightHolder: The Android Open Source Project license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://developer.android.com/topic/libraries/support-library/ - artifact: com.squareup.retrofit2:retrofit:+ name: Retrofit copyrightHolder: Square, Inc. license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://github.com/square/retrofit/ - artifact: io.reactivex.rxjava2:rxjava:+ name: RxJava copyrightHolder: RxJava Contributors license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://github.com/reactivex/rxjava/となります。基本的にGitHubの情報を元に調べ編集していきます。Android Open Source Project(AOSP)は ここ にライセンスに関する内容が記載されています。全ての情報の編集が終わったら、再度
checkLicensesタスクを実行し、エラーが発生しなければOKです。ライセンス情報の表示
最後に
Gradle > [project-name] > :app > Tasks > otherから、generateLicensePageタスクを実行し、出力されたHTMLファイルをアプリ内のWebViewに読み込ませます。MainActivity.ktoverride fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView.loadUrl("file:///android_asset/licenses.html") }
ハマったこと
checkLicensesタスクが失敗する
checkLicensesタスクを実行した際にmissing libraries in licenses.ymlというエラーで失敗してしまう。初回実行時にはlicenses.yamlが存在しないので、コンソールに表示されたライセンスの情報一覧をコピペして作成すればいいのですが、2回目以降であれば不足している情報がコンソールに出力されていますので、YAMLファイルに追記します。また、使用しているライブラリが、内部的に別のライブラリに依存している場合(例えばappcompat-v7は内部的にsupport-annotationsやsupport-compatに依存している)、その関係性も監視対象になります。そういったライブラリはlicenses.yaml- artifact: com.android.support:appcompat-v7:+ name: Android AppCompat Library v7 copyrightHolder: The Android Open Source Project license: The Apache License, Version 2.0 - artifact: com.android.support:support-annotations:+ skip: true - artifact: com.android.support:support-compat:+ skip: trueと記述することで、一覧から除外することができます。また、グループ一式で除外する場合は
licenses.yaml- artifact: com.android.support:+:+ skip: trueの様にワイルドカードで指定したり、
build.gradle[app]licenseTools { ignoredGroups = ['com.android.support'] }の様に記述することで可能となります。
generateLicensePageタスクが失敗する
コンソールでエラーの内容を確認すると
checkLicensesタスクでコケていることがほとんどかと思います。 checkLicensesタスクが失敗する を参考に解決します。必要な項目が不足している
licenses.yamlに必要な項目が不足している場合にも、プラグインのタスクが失敗します。必須項目は以下の通りです。
- artifact
- name
- copyrightHolder/ author/ authors/ noticeのうちどれか1つ
オプションとして付与できる項目は
- year
- skip
- forceGenerate
などがあります。詳しくは公式の GitHubリポジトリ を参照してください。
最終的な着地点
ここまでのハマった箇所を踏まえて、デモ用のアプリとしてGitHubに demo-licenses をアップしておきます。参考になるかわかりませんが、躓いていらっしゃる方の助けに少しでもなれたらなと思います。表示させないライセンスについてですが、最終的には
ignoredGroupsとskipを併用することで解決しました。ignoredGroupsは、名前の通り使用しているライブラリのgroupIdのみにしか対応しておらずartifactIdでは除外できませんでした。groupId+artifactIdでもプラグインの監視対象から除外できれば良いのになぁ。
まとめ
結果的に調べたりなんなりで作業自体のコストはかかってしまいましたが、1から全て自前で実装するよりは遥かに良いです。今後、同様な実装が発生した場合にはこの記事を参考にスピーディに対応したいですね。また、先ほどの
groupId+artifactIdで除外するPull Requestなどを本家に出してみたいと思っています。ライセンスの表示は、Androidアプリを開発していく上で必要なことではありますが、開発が進むにつれて管理が億劫になったり、時間を割くことが難しいかもしれません。そんな痒いところに手が届くようなライブラリを開発してくださったクックパッド様には感謝しかないです
どうでも良い話
ライセンスの綴りとして license と licence があります。大きな違いとして、イギリスでは動詞の場合に『license』名詞の場合に『licence』を使い分けますが、アメリカでは特に区別をせず日常的に『license』が使われるみたいです。結構タイプミスするので気をつけたいです。
参考文献
- 投稿日:2019-03-07T02:16:52+09:00
【Android】画像をカルーセル表示するときにハマった話
RecyclerViewを使うと簡単に画像をカルーセル表示することができます。
ですが、ImageViewの扱いでハマった部分があったので、その際に調査した内容をまとめました。TL;DR
ImageViewの
AdjustViewBoundsにtrueをセットすることで、ImageViewのサイズを画像サイズに合わせて調整することができます。前提条件
カルーセル、画像のサイズおよび表示方法に関する条件は以下の通りです。
- カルーセルの高さは固定とする
- カルーセルに表示する各画像のサイズはすべて異なる可能性がある
- 各画像の高さはカルーセルの高さと一致するように拡大縮小する
- 各画像のアスペクト比は維持する
- 各画像はクロップしない
ハマった内容
以前作成したScaleTypeと表示画像の対応表を参考に、
ScaleTypeは一旦FIT_CENTER(デフォルト値のためxml上の指定なし)、またlayout_widthはwrap_content、layout_heightは100dpとしました。RecyclerViewの各要素に表示する画面のxmlは以下の通りです。
item_main.xml<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="100dp" />正直どの
ScaleTypeも今回の条件を満たさないと感じつつも、layout_widthをwrap_contentにすることで、FIT_CENTERであれば画像の高さをカルーセルの高さと一致させ、それに合わせて画像全体が含まれるようにアスペクト比を維持しながら幅を調整してくれるのでは?というわずかな希望を抱きながら試してみました。結果は以下のようになりました。案の定、希望通りの表示になりませんでした。
これを見て、画像が小さいときの結果(左)は納得できるのですが、画像が大きいときの結果(右)に関しては何で左右に余白ができるのかが理解できませんでした。解決方法
冒頭でも触れましたが、ImageViewの
AdjustViewBoundsをtrueにすることで解決できました。これは文字通り「Viewの境界を調整する」ための属性です。
公式ドキュメントはこちらにあります。
AdjustViewBoundsをtrueにすると、結果は以下のようになりました。上が先ほどの結果(デフォルト値はfalse)で、下が今回の結果です。
これで、ImageViewのサイズを画像サイズに合わせて調整することができました。AdjustViewBoundsをセットしたときの内部処理
結果としてはこれで問題ないのですが、画像が大きいときの結果に関してはこれだけでは理解ができなかったので、ImageViewの処理を追ってみました。
ImageView.javaのソースコードはこちらにあります。Androidにおいて、Viewが表示されるまでの大まかな流れは以下の通りになります。今回の肝となるのは1.の
onMeasure()です。
- サイズを決める(
onMeasure())- 場所を決める(
onLayout())- 描画する(
onDraw())これに関してはこちらの記事を参考にさせていただきました。
では、ここからはImageViewのonMeasure()の処理を追っていきます。最初は各変数を宣言しているだけだったので割愛します。
以下の処理が1つ目のポイントになります。ImageView.java#LL.1088-1089final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
widthMeasureSpecとheightMeasureSpecはonMeasure()の引数で、そこからMeasureSpecのModeを取得しています。
ただ、widthMeasureSpecとheightMeasureSpecがどのように決定されるのかは正直よく分かりませんでした。
MeasureSpecは親Viewから子Viewに対して課される制約を表しており、以下の3種類のModeがあります。公式ドキュメントはこちらにあります。
MeasureSpec.Mode 制約条件 UNSPECIFIED 親Viewによって子Viewのサイズが決定されない EXACTLY 親Viewによって子Viewの正確なサイズが決定される AT_MOST 親Viewによって子Viewの最大のサイズが決定される したがって、
widthSpecModeはMeasureSpec.UNSPECIFIED、heightSpecModeはMeasureSpec.EXACTLYとなります。
そして、以下の処理が2つ目のポイントになります。
AdjustViewBoundsがtrueの場合のみ以下の処理が行われます。ImageView.java#LL.1104-1109if (mAdjustViewBounds) { resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; desiredAspect = (float) w / (float) h; }
resizeWidthとresizeHeightは幅・高さのリサイズを行うか否かを制御する変数です。
desiredAspectは画像のアスペクト比(wは幅、hは高さ)を表す変数です。今回の場合、
widthSpecModeはMeasureSpec.UNSPECIFIED、heightSpecModeはMeasureSpec.EXACTLYなので、AdjustViewBoundsがtrueのときはresizeWidthのみがtrueとなります。
そして、以下の処理が3つ目のポイントになります。
長いので詳細は省略しますが、おおむね以下のような処理が行われています。
AdjustViewBoundsがtrueのとき
ImageViewのサイズを画像サイズに合わせて調整するAdjustViewBoundsがfalseのとき
ImageViewの幅を画像の幅と一致するよう調整するImageView.java#LL.1120-1190int widthSize; int heightSize; if (resizeWidth || resizeHeight) { // Get the max possible width given our constraints widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec); // Get the max possible height given our constraints heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec); if (desiredAspect != 0.0f) { // See what our actual aspect ratio is final float actualAspect = (float)(widthSize - pleft - pright) / (heightSize - ptop - pbottom); if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { boolean done = false; // Try adjusting width to be proportional to height if (resizeWidth) { int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright; // Allow the width to outgrow its original estimate if height is fixed. if (!resizeHeight && !sCompatAdjustViewBounds) { widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); } if (newWidth <= widthSize) { widthSize = newWidth; done = true; } } // Try adjusting height to be proportional to width if (!done && resizeHeight) { ... } } } } else { w += pleft + pright; h += ptop + pbottom; w = Math.max(w, getSuggestedMinimumWidth()); h = Math.max(h, getSuggestedMinimumHeight()); widthSize = resolveSizeAndState(w, widthMeasureSpec, 0); heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); } setMeasuredDimension(widthSize, heightSize);
ここで、else句の最後にあるresolveSizeAndState()がMeasureSpec.UNSPECIFIEDのときは引数に指定した画像サイズそのものを返していたので、ImageViewの幅が画像の幅と同じサイズで表示されるということが分かりました。スッキリ。
View.javaのソースコードはこちらにあります。View#LL.23463-23483public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }サンプルアプリ
上記の画像キャプチャを撮影したアプリです。
GitHub - AdjustViewBoundsChecker


