- 投稿日:2019-05-02T20:16:02+09:00
Spring×Kotlin×MySQL×Mybatis×ThymeleafでサンプルWebアプリ作ってみた
実務では基本的にJavaでWebアプリを作ることが多いのですが
Kotlin使ったらどうなのかと思いちょっと勉強してみました。DBはMySQL, ビルドにGradleを使用し、
IDEはIntelliJ Community Editionを使用します。
(ちなみにEclipse or STSにKotlinプラグインを入れて動かすことも可能ですが、IntelliJの方が便利な面が多そうなのであまりおすすめできないです。IntelliJはCommunity Editionであれば無料で使えます)出来上がったものがこちら
プロジェクトを作成するまでの準備
Spring Initializrで雛形作成
以下のサイトにアクセス
https://start.spring.io/今回は以下の設定でプロジェクトを作成しました。
項目 設定値 Project Gradle Language Kotlin Spring Boot 2.1.4 Packaging Jar ※組み込みTomcatを使用する場合はJarで良いみたいです Java Version 8 Dependencies DevTools, Web, Thymeleaf, MySQL, JDBC, MyBatis その他の設定はお好みで。
ダウンロードが終わったら展開し、IntelliJにインポートしましょう。DB
以下のDBを作成します。
-- DB作成 CREATE DATABASE sample; USE sample; -- テーブル作成 CREATE TABLE item( id INTEGER PRIMARY KEY AUTO_INCREMENT, name VARCHAR(10), price INTEGER, type INTEGER ); -- データ挿入 INSERT INTO item(name, price, type) VALUES ('Apple', 150, 1), ('Onion', 60, 2), ('Cherry', 300, 1), ('Carrot', 70, 2), ('Banana', 80, 1) ;実装
ディレクトリ構成は以下のようになります。
ディレクトリ構成src ├── main │ ├── kotlin │ │ └── com │ │ └── example │ │ └── sample │ │ ├── SampleApplication.kt // デフォルトのまま │ │ ├── ServletInitializer.kt // デフォルトのまま │ │ ├── controller │ │ │ └── SampleController.kt │ │ ├── mapper │ │ │ └── SampleMapper.kt │ │ ├── model │ │ │ └── SampleModel.kt │ │ └── service │ │ └── SampleService.kt │ └── resources │ ├── application.yml │ ├── messages.properties │ ├── mybatis │ │ └── mapper │ │ └── SampleMapper.xml │ ├── static │ │ ├── css │ │ │ └── style.css │ │ └── js │ └── templates │ └── index.html └── test // デフォルトのまま(今回触らないので省略)ControllerからServiceを呼んで
その中でMapperを使ってDBにアクセスし、Modelを返し、Viewに表示するという
よくあるパターンで作っていきます。設定ファイル
とりあえずapplication.propertiesをymlにして以下のように設定する。
(ymlにしたのは私の好みです)application.ymlspring: datasource: url: # TODO Set connection string ex. jdbc:mysql://localhost:3306/sample username: # TODO Set username password: # TODO Set password driverClassName: com.mysql.jdbc.Driver mybatis: mapper-locations: classpath*:/mybatis/mapper/*.xml type-aliases-package: com.example.sample.model configuration: map-underscore-to-camel-case: true default-fetch-size: 100 default-statement-timeout: 30 cache-enabled: falseポイントとしては
- datasourceを設定する。
- 「mapper-locations」にmybatisのMapper(.xml)の場所を設定する。
- 「type-aliases-package」にMapperで扱うModelクラスを配置する場所を設定する。
くらいかと思います。その他の設定はよく使いそうなものをとりあえず書いてます。
Model
テーブルに対応したModelを作成します。
SampleModel.ktpackage com.example.sample.model data class SampleModel(var id: Int, var name: String, var price: Int, var type: Int)JavaだとGetter、SetterやらConstructorなどいっぱい書かないといけないのが
Kotlinだとなんと一行で書けちゃいます。上記の場合だと、
id(Int型), name(String型), price(Int型), type(Int型)というフィールドを持った
SampleModelというクラスができます。もちろんGetter, Setter, Constructorも込みです。また、Modelのようにデータを格納するためだけのクラスには
data修飾子を付けておくのがKotlinでは慣習のようです。
これを付けておくとtoStringやcopyなどの関数を自動的に実装してくれるみたいです。便利ですね。Mapper
次にMapperです。
Mapperアノテーションを付与したMapperインターフェースを作成します。SampleMapper.ktpackage com.example.sample.mapper import org.apache.ibatis.annotations.Mapper import com.example.sample.model.SampleModel @Mapper interface SampleMapper { // 抽象メソッドで定義する fun find(): List<SampleModel> }Kotlinではfunキーワードでメソッド、関数を定義できます。
ここではSampleModelのリストを返すfind抽象メソッドを定義しています。
Mybatisを使うとこれらの抽象メソッドが自動で実装されます。
次にこのMapperインターフェースに対応したSQLが書かれたxmlファイルを作成します。SampleMapper.xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.sample.mapper.SampleMapper"> <select id="find" resultType="SampleModel"> SELECT id, name, price, type FROM item </select> </mapper>namespaceに対象のMapperインターフェースのパス、
idにメソッド名、resultTypeに戻り値の型を設定します。
今回はapplication.ymlにtype-aliases-packageを設定しているので
resultTypeは完全修飾クラス名ではなくクラス名だけで動くようになっています。Service
次にServiceです。
SampleService.ktpackage com.example.sample.service import com.example.sample.mapper.SampleMapper import com.example.sample.model.SampleModel import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service @Transactional class SampleService(private val mapper: SampleMapper) { fun find() : List<SampleModel> = mapper.find() }
constructorキーワードの前に@Autowiredを付与することで
SampleMapperをインジェクトしています。
上記のようにコンストラクタに値を渡すだけでインジェクトできるみたいです。
こちらもJavaより簡単に書けて楽そうですね。Controller
次にControllerです。
SampleController.ktpackage com.example.sample.controller import com.example.sample.service.SampleService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class SampleController { @Autowired lateinit var service: SampleService @GetMapping("/") fun index(model: Model): String { val modelList = service.find() model.addAttribute("modelList", modelList) return "index" } }こちらもSampleServiceをインジェクトしています。
Serviceでは省略しましたが、本来KotlinではAutowiredするときにlateinitキーワードをつける必要があります。
ですが、もっと簡単に以下のように書くこともできます。@Controller class SampleController(private val service: SampleService){ @GetMapping("/") fun index(model: Model): String { // 以下3行とも同じように動く // model.addAttribute("modelList", service.find()) // model.set("modelList", service.find()) model["modelList"] = service.find() return "index" } }View
Controllerで設定した値を表示します。
index.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="../static/css/style.css" th:href="@{/css/style.css}"> <title>Kotlin Spring Sample</title> </head> <body> <h1>Model List</h1> <table> <tr> <th th:text="#{title.id}">No</th> <th th:text="#{title.name}">Name</th> <th th:text="#{title.price}">Price</th> <th th:text="#{title.type}">Type</th> <th th:text="#{title.note}">Note</th> </tr> <th:block th:each="model : ${modelList}"> <tr> <td th:text="${model.id}"></td> <td th:text="${model.name}"></td> <td th:text="${model.price}"></td> <td th:text="${model.type}"></td> <!--/* model.toString()が実行される */--> <td th:text="${model}"></td> </tr> </th:block> </table> </body> </html>実行結果
ハマったところ
STSで頑張ろうとしてハマる
私はIntelliJをそこまで使い込んだことがなかったので、最初STSを使おうとしていました。
STSにKotlin Pluginを入れ、Spring Initializrで作ったプロジェクトを入れて動かそうとすると
…動かない。
GithubやQiitaで上がっているサンプルコード等をImportしてもそのままでは動かず…。
どうやらKotlinでは継承元やオーバーライド元のクラスや関数にopenという修飾子をつけるようで
参考にしていたコードには軒並みopen修飾子が付いておらずエラーになっていました。
IntelliJを使うと自動的にopen修飾子をつけるプラグインが動くようです。(STSでもできるのかもしれないですが、設定めんどくs...)
他にも色々イマイチなところが多かったので、最初から大人しくIntelliJ使っとけばよかった…。IntelliJでGradleが動かない…
これは最後までよくわからなかったのですが
Spring initializrで作成したプロジェクトをインポートしても
「No such property: GradleVersion for class: JetGradlePlugin」
みたいなエラーが出て動かせなかったです。
色々調べてみたら、gradle/wrapper/gradle-wrapper.propertiesで設定している
gradleのバージョンを下げると動く、みたいな記事を見つけたので
その通りにしたらとりあえず動きました。gradle-wrapper.propertiesdistributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists # 変更前 # distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip # 変更後 distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/distsMybatisの設定周りで混乱する
これはKotlinに移行したからとか関係なく完全に自分の知識不足なのですが、
Mybatisの設定を調査するのにとても時間がかかりました。
application.ymlにはSpringの設定しか書けないという先入観があり、
Configurationアノテーションを使おうとしたり、SqlSessionTemplateクラスを使おうとしたり
色んな手法で迷いまくったあげく、最終的にはapplication.ymlに全て記載するのに落ち着きました。mybatis-spring-boot-starterを使うと以下のようにapplication.ymlに記載するだけで動くみたいです。
application.ymlspring: datasource: url: # TODO Set connection string ex. jdbc:mysql://localhost:3306/sample username: # TODO Set username password: # TODO Set password driverClassName: com.mysql.jdbc.Driver # 以下Mybatisの設定 mybatis: mapper-locations: classpath*:/mybatis/mapper/*.xml type-aliases-package: com.example.sample.model configuration: map-underscore-to-camel-case: true default-fetch-size: 100 default-statement-timeout: 30 cacheEnabled: falsebuild.gradle(抜粋)dependencies { // (中略) こんな感じに設定しておく(Spring InitializrでMybatisを入れると追加されているはず) implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.0.1' }参考文献等
https://qiita.com/kazuki43zoo/items/ea79e206d7c2e990e478
https://www.shookuro.com/entry/2017/11/23/203318
https://qiita.com/k5n/items/18adb5c3503a54e96c22
https://stackoverflow.com/questions/53600179/errorno-such-property-gradleversion-for-class-jetgradleplugin
- 投稿日:2019-05-02T17:46:15+09:00
MotionLayoutでMaterialDesignのTextFieldもどきを実装する
はじめに
昨年のGoogleI/O 2018でConstraintLayout2.0が紹介されましたが、その中でMotionLayoutという新たなAndroidのアニメーションの仕組みが公開されました。
今回は、そのMotionLayoutを使用して、MaterialDesignのTextFieldっぽいものを実装してみたので、それについて実装方法などを書き連ねたいと思います。
※またMotionLayoutはまだalpha版なので、今回は、constraint-layout:2.0.0-alpha4
での実装を紹介しますが、新たなバージョンがリリースされた場合、実装が変わる可能性もあるので、ご了承ください。作ったもの
こちらのような項目がリストのように表示されていて、画面遷移させることなく閲覧モードから入力モードにすることができます。それぞれのモード間の遷移時のアニメーションでMotionLayoutを使って実装しています。
挙動としては、
1. 右のテキスト部分をクリックした場合、入力モードになる
2. フォーカスが外れたら、閲覧モードに戻る
(フォーカスは別のテキストFieldをクリックするか、下のボタンを押下すると外れる)ソースコードはこちらに上げています。参考にされたい方はこちらをご覧ください。
https://github.com/youmitsu/MotionLayoutMaterialTextFieldMotionLayoutの概要
MotionLayoutについては、多くの方が仕組みについての記事を公開しているので、ここでは省略します。
基本的なステップとしては、以下となります。
- アニメーションさせる前のレイアウトをMotionLayoutを親として実装
- Sceneのxmlを作成
- 1のattributeに2のSceneファイルを指定
実装方法
1. ConstraintLayout2.0のインストール
まずは、ConstraintLayout2.0をインストールします。
AndroidXと、SupportLibraryの2つのArtifactsがあるので、自身のプロジェクトに応じて、使い分けましょう。リリースノートはこちら
https://androidstudio.googleblog.com/2019/04/constraintlayout-200-alpha-4.htmlAndroidXの場合
dependencies { ... implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha4' }SupportLibraryの場合
dependencies { ... implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha4' }2. 初期状態のviewを作成
まずは、こちらのクリックされる前のレイアウトを作っていきます。
xmlは以下になります。MotionLayoutはConstraintLayoutの子クラスなので、基本的にはConstraintLayoutでレイアウトを作るのと同じ要領で実装します。
※サンプルリポジトリでは、Databindingを使って入力データをバインディングしているので、
<layout>
と<data>
タグが入っていますが、無くても動きます。layout_custom_motion_edittext.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="data" type="jp.co.youmitsu.myapplication.CustomMotionEditTextLayout"/> </data> <androidx.constraintlayout.motion.widget.MotionLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/motion_layout" > <TextView android:id="@+id/nickname_title" android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="start|center_vertical" android:layout_marginStart="10dp" android:textColor="#808080" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/nickname_value" android:text="@{data.title}" /> <TextView android:id="@+id/nickname_value" android:layout_width="0dp" android:layout_height="match_parent" android:gravity="end|center_vertical" android:textSize="15sp" android:layout_marginEnd="10dp" android:ellipsize="end" android:singleLine="true" android:maxEms="15" android:text="@{data.value}" android:clickable="true" android:focusable="true" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/nickname_title"/> <androidx.appcompat.widget.AppCompatEditText android:id="@+id/edit_text" android:layout_height="wrap_content" android:layout_width="0dp" android:alpha="0" android:visibility="invisible" android:textSize="15sp" android:inputType="text" android:text="@{data.value}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.motion.widget.MotionLayout> </layout>3. アニメーション前後のConstraintの状態とTransitionを表すMotionSceneファイルを作成
次に、タップしてアニメーションし始める時と終わった時のレイアウトのConstraintSetとTransitionをMotionSceneという形で定義します。
MotionSceneファイルはxmlで記述するので、res/xml配下に新たなファイルを作成します。xmlファイルは以下です。
@id/startがアニメーションする前のConstraintSet。@id/endがアニメーションした後のConstraintSetを表しています。
今回の場合、start時のConstraintと上記2で定義したMotionLayout内の要素のConstraintは一致することになります。
@id/start
のConstraintSetでのレイアウト:
layout_custom_motion_edittext_scene.xml<?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/end" motion:duration="200"> <OnClick motion:targetId="@id/nickname_value" motion:clickAction="transitionToStart|transitionToEnd"/> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/nickname_title" android:layout_width="0dp" android:layout_height="wrap_content" android:textSize="@dimen/text_size_normal" android:layout_marginStart="5dp" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toStartOf="@id/nickname_value"> <CustomAttribute motion:attributeName="textSize" motion:customFloatValue="15" /> <CustomAttribute motion:attributeName="textColor" motion:customColorValue="#808080" /> </Constraint> <Constraint android:id="@+id/nickname_value" android:layout_width="0dp" android:layout_height="match_parent" android:textSize="15sp" android:layout_marginEnd="10dp" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toEndOf="@id/nickname_title"/> <Constraint android:id="@+id/edit_text" android:layout_height="wrap_content" android:layout_width="0dp" android:alpha="0" android:visibility="invisible" android:textSize="15sp" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent"/> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@id/nickname_title" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="5dp" motion:layout_constraintBottom_toTopOf="@id/edit_text" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toEndOf="parent"> <CustomAttribute motion:attributeName="textSize" motion:customFloatValue="12" /> <CustomAttribute motion:attributeName="textColor" motion:customColorValue="@color/colorAccent" /> </Constraint> <Constraint android:id="@+id/nickname_value" android:layout_width="wrap_content" android:layout_height="0dp" android:textSize="15sp" android:alpha="0" android:visibility="invisible" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent"/> <Constraint android:id="@id/edit_text" android:layout_height="wrap_content" android:layout_width="0dp" android:alpha="1" android:visibility="visible" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent"/> </ConstraintSet> </MotionScene>4. MotionLayoutのattributeにlayoutDescriptionとして3のSceneファイルを設定
アニメーションする対象のview(MotionLayout)とMotionSceneファイルを作成できたら、それぞれを結びつけるために、MotionLayoutのlayoutDescription属性を設定します。こうすることで、画像のようなアニメーションが実現できます。
layout_custom_motion_edittext.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="data" type="jp.co.youmitsu.myapplication.CustomMotionEditTextLayout"/> </data> <androidx.constraintlayout.motion.widget.MotionLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/motion_layout" app:layoutDescription="@xml/layout_custom_motion_edittext_scene" // これを追加 > ... </androidx.constraintlayout.motion.widget.MotionLayout> </layout>まとめ
今回は、MotionLayoutを使って、MaterialDesignのTextFieldの拡張っぽいものを実装してみました。アニメーションに関するコードをほとんどxmlだけで完結させることができるので、とても使いやすそうだなという印象です。
これからも色々機能追加があるみたいなので、楽しみです。
最後まで読んでいただきありがとうございました。参考にさせていただいたもの
- 投稿日:2019-05-02T17:46:15+09:00
【Android】MotionLayoutでMaterialDesignのTextFieldもどきを実装する
はじめに
昨年のGoogleI/O 2018でConstraintLayout2.0が紹介されましたが、その中でMotionLayoutという新たなAndroidのアニメーションの仕組みが公開されました。
今回は、そのMotionLayoutを使用して、MaterialDesignのTextFieldっぽいものを実装してみたので、それについて実装方法などを書き連ねたいと思います。
※またMotionLayoutはまだalpha版なので、今回は、constraint-layout:2.0.0-alpha4
での実装を紹介しますが、新たなバージョンがリリースされた場合、実装が変わる可能性もあるので、ご了承ください。作ったもの
こちらのような項目がリストのように表示されていて、画面遷移させることなく閲覧モードから入力モードにすることができます。それぞれのモード間の遷移時のアニメーションでMotionLayoutを使って実装しています。
挙動としては、
1. 右のテキスト部分をクリックした場合、入力モードになる
2. フォーカスが外れたら、閲覧モードに戻る
(フォーカスは別のテキストFieldをクリックするか、下のボタンを押下すると外れる)ソースコードはこちらに上げています。参考にされたい方はこちらをご覧ください。
https://github.com/youmitsu/MotionLayoutMaterialTextFieldMotionLayoutの概要
MotionLayoutについては、多くの方が仕組みについての記事を公開しているので、ここでは省略します。
基本的なステップとしては、以下となります。
- アニメーションさせる前のレイアウトをMotionLayoutを親として実装
- Sceneのxmlを作成
- 1のattributeに2のSceneファイルを指定
実装方法
1. ConstraintLayout2.0のインストール
まずは、ConstraintLayout2.0をインストールします。
AndroidXと、SupportLibraryの2つのArtifactsがあるので、自身のプロジェクトに応じて、使い分けましょう。リリースノートはこちら
https://androidstudio.googleblog.com/2019/04/constraintlayout-200-alpha-4.htmlAndroidXの場合
dependencies { ... implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha4' }SupportLibraryの場合
dependencies { ... implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha4' }2. 初期状態のviewを作成
まずは、こちらのクリックされる前のレイアウトを作っていきます。
xmlは以下になります。MotionLayoutはConstraintLayoutの子クラスなので、基本的にはConstraintLayoutでレイアウトを作るのと同じ要領で実装します。
※サンプルリポジトリでは、Databindingを使って入力データをバインディングしているので、
<layout>
と<data>
タグが入っていますが、無くても動きます。layout_custom_motion_edittext.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="data" type="jp.co.youmitsu.myapplication.CustomMotionEditTextLayout"/> </data> <androidx.constraintlayout.motion.widget.MotionLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/motion_layout" > <TextView android:id="@+id/nickname_title" android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="start|center_vertical" android:layout_marginStart="10dp" android:textColor="#808080" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/nickname_value" android:text="@{data.title}" /> <TextView android:id="@+id/nickname_value" android:layout_width="0dp" android:layout_height="match_parent" android:gravity="end|center_vertical" android:textSize="15sp" android:layout_marginEnd="10dp" android:ellipsize="end" android:singleLine="true" android:maxEms="15" android:text="@{data.value}" android:clickable="true" android:focusable="true" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/nickname_title"/> <androidx.appcompat.widget.AppCompatEditText android:id="@+id/edit_text" android:layout_height="wrap_content" android:layout_width="0dp" android:alpha="0" android:visibility="invisible" android:textSize="15sp" android:inputType="text" android:text="@{data.value}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.motion.widget.MotionLayout> </layout>3. アニメーション前後のConstraintの状態とTransitionを表すMotionSceneファイルを作成
次に、タップしてアニメーションし始める時と終わった時のレイアウトのConstraintSetとTransitionをMotionSceneという形で定義します。
MotionSceneファイルはxmlで記述するので、res/xml配下に新たなファイルを作成します。xmlファイルは以下です。
@id/startがアニメーションする前のConstraintSet。@id/endがアニメーションした後のConstraintSetを表しています。
今回の場合、start時のConstraintと上記2で定義したMotionLayout内の要素のConstraintは一致することになります。
@id/start
のConstraintSetでのレイアウト:
layout_custom_motion_edittext_scene.xml<?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <Transition motion:constraintSetStart="@id/start" motion:constraintSetEnd="@id/end" motion:duration="200"> <OnClick motion:targetId="@id/nickname_value" motion:clickAction="transitionToStart|transitionToEnd"/> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/nickname_title" android:layout_width="0dp" android:layout_height="wrap_content" android:textSize="@dimen/text_size_normal" android:layout_marginStart="5dp" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toStartOf="@id/nickname_value"> <CustomAttribute motion:attributeName="textSize" motion:customFloatValue="15" /> <CustomAttribute motion:attributeName="textColor" motion:customColorValue="#808080" /> </Constraint> <Constraint android:id="@+id/nickname_value" android:layout_width="0dp" android:layout_height="match_parent" android:textSize="15sp" android:layout_marginEnd="10dp" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toEndOf="@id/nickname_title"/> <Constraint android:id="@+id/edit_text" android:layout_height="wrap_content" android:layout_width="0dp" android:alpha="0" android:visibility="invisible" android:textSize="15sp" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent"/> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@id/nickname_title" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="5dp" motion:layout_constraintBottom_toTopOf="@id/edit_text" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toEndOf="parent"> <CustomAttribute motion:attributeName="textSize" motion:customFloatValue="12" /> <CustomAttribute motion:attributeName="textColor" motion:customColorValue="@color/colorAccent" /> </Constraint> <Constraint android:id="@+id/nickname_value" android:layout_width="wrap_content" android:layout_height="0dp" android:textSize="15sp" android:alpha="0" android:visibility="invisible" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent"/> <Constraint android:id="@id/edit_text" android:layout_height="wrap_content" android:layout_width="0dp" android:alpha="1" android:visibility="visible" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent"/> </ConstraintSet> </MotionScene>4. MotionLayoutのattributeにlayoutDescriptionとして3のSceneファイルを設定
アニメーションする対象のview(MotionLayout)とMotionSceneファイルを作成できたら、それぞれを結びつけるために、MotionLayoutのlayoutDescription属性を設定します。こうすることで、画像のようなアニメーションが実現できます。
layout_custom_motion_edittext.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="data" type="jp.co.youmitsu.myapplication.CustomMotionEditTextLayout"/> </data> <androidx.constraintlayout.motion.widget.MotionLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/motion_layout" app:layoutDescription="@xml/layout_custom_motion_edittext_scene" // これを追加 > ... </androidx.constraintlayout.motion.widget.MotionLayout> </layout>まとめ
今回は、MotionLayoutを使って、MaterialDesignのTextFieldの拡張っぽいものを実装してみました。アニメーションに関するコードをほとんどxmlだけで完結させることができるので、とても使いやすそうだなという印象です。
これからも色々機能追加があるみたいなので、楽しみです。
最後まで読んでいただきありがとうございました。参考にさせていただいたもの
- 投稿日:2019-05-02T16:04:31+09:00
Forge式BlockStateを利用してItemのJsonModelを削減する方法
概要
例えばmetadataをもつItemを追加するにはそのmetadataの個数(今回は4つ)だけのjsonファイルが必要だったが、それを2個で済ませる(同じModelを使いまわす)方法
環境
Minecraft-1.10.2
Minecraft-1.11.2
Minecraft-1.12.2(*試していないだけでおそらく他バージョンでも可)
追加するJSONファイル
(以下、modidはそのItemを追加するmodのID)
まずassets/modid/models/block/に以下のJSONファイルを作る
このときitemではなくblockのModelの位置に追加するのを間違えないようにitem_generated.json{ "parent": "item/generated" }
これは普通の板状に表示されるItemの場合で、ツール等ちゃんと持たせたい場合はgeneratedをhandheldに。
その他のmodel(自作も含む)の場合も同じようにassets/modid/models/block以下に置いてModelを指定すれば使える。
次にassets/modid/blockstates/に以下のJSONファイルを作る
(variantsには追加するmeta数だけ書き加える)sample_item.json{ "forge_marker": 1, "defaults": { "model": "modid:item_generated", "transform": "forge:default-item", "uvlock": true }, "variants": { "meta0": [{ "textures": { "layer0": "modid:items/sample_item_0" } }], "meta1": [{ "textures": { "layer0": "modid:items/sample_item_1" } }], "meta2": [{ "textures": { "layer0": "modid:items/sample_item_2" } }], "meta3": [{ "textures": { "layer0": "modid:items/sample_item_3" } }] } }
テクスチャ指定はlayer0, layer1と数字が上がるごとに上に重ねて表示される(何枚いけるのかは不明)。
Modelの登録(kotlin)
次は先ほど追加したModelの登録。
ClientProxy.ktfor (i in 0 until 4) { ModelLoader.setCustomModelResourceLocation(sampleItem, i, ModelResourceLocation(ResourceLocation(modid, "sample_item"), "meta$i")) }javaの場合も同じように
ClientProxy.javafor (int i = 0; i < 4; i++) { ModelLoader.setCustomModelResourceLocation(sampleItem, i, new ModelResourceLocation(new ResourceLocation(modid, "sample_item"), "meta" + i)); }最後に
おそらくModelResourceLocationの第2引数を弄ればBlockと同じように、
sample_item.json{ "forge_marker": 1, "defaults": { "model": "modid:item_generated", "transform": "forge:default-item", "uvlock": true }, "variants": { "meta": { "0":{ }, "1":{ }, "2":{ }, "3":{ } } }のような形にもできると思う
けどめんどくさいからやめた。
- 投稿日:2019-05-02T11:43:55+09:00
UnityのスマホネイティブプラグインをKotlin/Nativeで共通化する
Unityのスマホネイティブプラグインを言語統一できないか?
昨今のスマホネイティブプラグインの言語の選択肢としてはJava, Kotlin, Objective-c, Swiftがあげられるかと思います。
たまに自分も趣味などでプラグインを使うことがあるのですが同じような処理を別で書かないといけないのが手間だと思っていました。
そこで昨年くらいからスマホのネイティブ界隈で話題になっていたKotlin/Nativeに目をつけてUnityで実行させてみたという記事です。そもそもKotlin/Nativeとは…?
まとめている記事もありましたので参照させて頂きます。基本的にはスマホネイティブの共通化できるロジックをKotlinで書いて共通化させようぜ!ってことなのですがAndroidでは.jarとしても吐き出せますし、iOSは.frameworkとして吐き出せるのでネイティブにとって扱いやすいものになっています。
さて、今回検証に使用したリポジトリです。動かして見たい人は是非ご活用ください。
実行した結果
Android iOS 文字列をOS毎に変えるという処理ですが呼び出すメソッドは1つにしてあります。
準備
Kotlin/Native自体の作成方法は既にわかりやすい記事がありますのでそちらを参照させていただきます。
Kotlin/Nativeチュートリアル Android, iOS編
自分はここを参考にさせて頂きました。そのため今回上記の記事をベースに進めます。Androidのネイティブが自分はよくわからなかったので、最初は基本的にコピペで作って必要な箇所を変えていきました。
今回、上記記事の「Common moduleの解説」の章まで出来たら一旦は大丈夫です。
上記の記事ほぼそのままですがソースコードを載せておきます。共通
common.ktpackage com.sample.mizotake.kotlinnativeforunity expect fun platformName(): String public class common { public fun createApplicationScreenMessage(): String { return "Call Kotlin Native on ${platformName()}" } }Android
actual.ktpackage com.sample.mizotake.kotlinnativeforunity actual fun platformName(): String { return "Android" }iOS
actual.ktpackage com.sample.mizotake.kotlinnativeforunity import platform.UIKit.UIDevice actual fun platformName(): String { return UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion }共通処理にUnityで呼び出すクラスとメソッドを定義します。OS毎に変える処理はexpect actual処理でinterfaceのように切り出して呼べるようです。
Unityへの導入
Android
自分もよく把握できていませんがGradle Syncをするとbuildというディレクトリができて
プロジェクト名 + android.jar
ができていました。
もし出来ていない場合は
右端にGradleというタブがあるのでそこからbuildの項目を見るとbuildの詳細一覧があるのでandroidJarをダブルクリックすれば走り出してjarができるかと思います。吐き出されたjarをUnityのPlugin/Androidに放り入れるだけです。
これでUnityへの導入は完了です。iOS
こちらは先ほどのKotlin/Nativeチュートリアル Android, iOS編の「iOSアプリ」の章にあるbuild.gradleの追記だけ行いましょう。
/common/build.gradle... task packForXCode(type: Sync) { final File frameworkDir = new File(buildDir, "xcode-frameworks") final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG' inputs.property "mode", mode dependsOn kotlin.targets.iOS.compilations.main.linkTaskName("FRAMEWORK", mode) from { kotlin.targets.iOS.compilations.main.getBinary("FRAMEWORK", mode).parentFile } into frameworkDir doLast { new File(frameworkDir, 'gradlew').with { text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n" setExecutable(true) } } } tasks.build.dependsOn packForXCodeここの部分ですね。これを追記して
./gradlew build
することで
main.frameworkができます。これをUnityのPlugin/iOSに放り込めばframeworkの導入は大丈夫ですが、iOSの場合もう一手間必要です。
externの実装がないとC#では呼び出せませんそのためPlugin/iOSフォルダにcommon.mm#import <main/main.h> extern "C" { const char* createApplicationScreenMessage() { NSString *message = [[Maincommon alloc] init].createApplicationScreenMessage; return strdup([message UTF8String]); } }を追加しましょう。これを追加することで先ほど作ったKotlinで書いたコードのframeworkを参照できます。
ここでcommonというkotlinファイルを作ったがMaincommonって何だろう?ってなると思います。どうやらframeworkに吐き出す時に変換されているようです。
それを確認するにはAndroidStudioでframeworkのHeaderを見ると一番下の行に自分で実装した処理が追記されていると思います。
これを参考にしてObjective-c++でインターフェースを定義する必要があります。
ちなみにSwiftだとMaincommonという変換名ではなくcommonで呼び出せそうですがSwiftを使うために手間をかけるよりObjective-c++を書いた方が早いと自分は思うのでこのまま進めます。C#で呼び出す
事前準備は終わりました。
UnityではuGUIのTextにネイティブで呼び出した文字列を表示させます。CallKotlinNative.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Runtime.InteropServices; public class CallKotlinNative : MonoBehaviour { #if UNITY_IOS [DllImport("__Internal")] private static extern string createApplicationScreenMessage(); #endif private Text viewableText; void Start() { var pluginMessage = ""; #if UNITY_ANDROID using (var plugin = new AndroidJavaObject("com.sample.mizotake.kotlinnativeforunity.common")) { pluginMessage = plugin.Call<string>("createApplicationScreenMessage"); Debug.Log(pluginMessage); } #elif UNITY_IOS pluginMessage = createApplicationScreenMessage(); #endif viewableText = GetComponent<Text>(); viewableText.text = pluginMessage; } }C#側は普通にネイティブプラグインを呼び出すだけですね。
これを実機ビルドまたはシミュレータービルドすることで動作の確認ができると思います。終わりに
Kotlin/NativeのUnityProjectへの導入は手間が必要かと思っていましたが思った以上に簡単でした。ただ、iOSの導入のexternだけどうにかならなかいとAndroidProject内に.mm入れてみて.frameworkだけ吐き出して更新させるなどをしようと思いましたがうまく行きませんでした…Androidネイティブのディレクトリ構成やtaskのカスタムに詳しければどうにかできるのかな?と思っています。
Kotlin/Nativeを使えば基本的に言語はKotlinひとつに統一できますし、共通処理やOS依存処理も問題ないのではない気がしています。個人的にKotlinでUIKitなどもimportして使えることに驚きました。
何にせよ扱う言語は少ないに限ると思っていますのでKotlin/Nativeは良いものだと思います。ただ現在betaなので書き方や吐き出し方が変わる可能性は高いです。