- 投稿日:2020-06-21T21:12:58+09:00
AndroidのWebView#shouldInterceptRequestでリクエストをinterceptするときに忘れがちなこと
AndroidのWebViewでHTTPレスポンスのハンドリングやタイムアウトを独自に実装したいときに、shouldInterceptRequestをオーバーライドして、独自にHTTP通信を実装することがありますよね。
サンプルなので大雑把な実装ですが、例えば以下のような感じに実装したとしましょう。
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? { val latch = CountDownLatch(1) var res: InputStream? = null val call = createOkHttpClient().newCall(Request.Builder().url(request?.url.toString()).method("POST", RequestBody.create(null, "hoge")).build()) call.enqueue(object: Callback { override fun onFailure(call: Call, e: IOException) { latch.countDown() } override fun onResponse(call: Call, response: Response) { res = response.body()?.byteStream() latch.countDown() } }) latch.await() return WebResourceResponse("text/html", "UTF-8",res) } private val cookieStore = HashMap<String, MutableList<Cookie>>() fun createOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .cookieJar(object: CookieJar { override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) { cookieStore[url.host()] = cookies } override fun loadForRequest(url: HttpUrl): MutableList<Cookie> { val cookies = cookieStore[url.host()] return cookies ?: ArrayList() } }) .build() }でも、これを使うときは要注意。
例えば、以下のようなHTMLを取得する場合を考えてみましょう。<html> <body> <script type="text/javascript"> function doPost() { document.TestForm.submit(); } </script> <h1>Test</h1> <form id="TestForm" name="TestForm" action="http://192.168.100.2:3000/hoge" method="post"> <input type="hidden" name="hoge" value="hogeVal"/> <input type="hidden" name="fuga" value="fugaVal"/> <input type="submit" value="submit"> </form> <script type="text/javascript"> doPost(); </script> </body> </html>WebViewはHTML解析機能を持っていますから、上記のHTML読み込み時に、doPost関数が呼び出され、
タグに定義されたhttp://192.168.100.2:3000/hoge
に対して、自動的にリクエストを送信します。このとき、shouldInterceptRequestでリソースの読み込みをWebViewに任せていれば(shouldInterceptRequestをオーバーライドしていなければ)、
<input>タグのhidden属性の値を自動的にPOSTしてくれますが、開発者が独自にOkHTTPなどでリクエストを実装する際は、HTMLの内容を解析して自前で付与しないといけません。(デフォルトではhidden属性の値は付与されない)以下のようなサーバを立てて、ログを見てみましょう。
{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Scotty import Control.Monad.IO.Class (liftIO) main :: IO () main = scotty 3000 $ do post "/test.html" $ file "./static/test.html" >> setHeader "Content-Type" "text/html" post "/hoge" $ do (liftIO . putStrLn $ ("Access to /hoge. Headers: " :: String)) >> headers >>= liftIO . print (liftIO . putStrLn $ ("Params: " :: String)) >> params >>= liftIO . print text "hoge success"shouldInterceptRequestをオーバーライドしない場合Access to /hoge. Headers: [("Host","192.168.100.151:3000"),("Connection","keep-alive"),("Content-Length","25"),("Cache-Control","max-age=0"),("Origin","http://192.168.100.151:3000"),("Upgrade-Insecure-Requests","1"),("Content-Type","application/x-www-form-urlencoded"),("User-Agent","Mozilla/5.0 (Linux; Android 9; Android SDK built for x86 Build/PSR1.180720.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.158 Mobile Safari/537.36"),("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"),("Referer","http://192.168.100.151:3000/test.html"),("Accept-Encoding","gzip, deflate"),("Accept-Language","ja-JP,en-US;q=0.9"),("X-Requested-With","com.badlogic.masaki.webviewjsinterfacesample")] Params: [("hoge","hogeVal"),("fuga","fugaVal")]shouldInterceptを上記実装でオーバーライドした場合Access to /hoge. Headers: [("Content-Length","4"),("Host","192.168.100.151:3000"),("Connection","Keep-Alive"),("Accept-Encoding","gzip"),("User-Agent","okhttp/3.10.0")] Params: []HTMLのhidden属性に定義されていた内容がPOSTされていませんね。これだとサーバから意図したレスポンスが返却されなくなってしまうでしょう。
その他、User-Agentなどのヘッダー情報も代わってしまいますから、shouldInterceptRequestをオーバライドするときは、この点も注意。hidden属性の値をPOSTするためには、HTMLの解析が必要になってきます。
ここら辺は既に情報が色々出回っています。HTMLの内容の取得方法は以下とか参考になりました。
https://stackoverflow.com/questions/8200945/how-to-get-html-content-from-a-webviewOkHttpで、application/x-www-form-urlencodedのPOSTパラメータを設定する方法は以下とかが
参考になりました。
https://stackoverflow.com/questions/35756264/how-do-i-post-data-using-okhttp-library-with-content-type-x-www-form-urlencoded以前の記事でも同じようなこと言いましたが、WebViewの目的はHTMLのレンダリングだから、WebAPI呼び出しに使うようなものではないですね。
- 投稿日:2020-06-21T20:37:42+09:00
[Modern Android] Jetpack Compose その2
Composeのサンプルコードを見ながら、分析しようと思います。
JetNewというComposeのサンプルを見つけました。はじめる
https://github.com/android/compose-samples/tree/master/JetNews
Readmeを読む
Jetnews is a sample news reading app, built with Jetpack Compose. The goal of the sample is to showcase the current UI capabilities of Compose.
To try out this sample app, you need to use the Canary version of Android Studio 4.0, and import the project from the Android Samples following the steps here.
Compose is not available in earlier versions of Android Studio and downloading this github repo directly and opening it will most likely result in build errors.
- Use
Row
s andColumn
s to arrange the contents of the UI- Add an
AppBar
- Use
MaterialTypography
and opacity to style the text- Use
Shape
to round the corners of the images- Use elevation to make the
Card
s stand out from the backgroundこれを読んで見るとFlutterを似ていると感じますね。
コードを見る
1
setContentにUIのComposeを使う。
package com.example.jetnews.ui import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.ui.core.setContent import com.example.jetnews.JetnewsApplication class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val appContainer = (application as JetnewsApplication).container setContent { JetnewsApp(appContainer = appContainer) } } }2
- JetnewsThemeとAppContentというCompose関数がある。
- DataとModelは今回無視します。
@Composable fun JetnewsApp(appContainer: AppContainer) { JetnewsTheme { AppContent( interestsRepository = appContainer.interestsRepository, postsRepository = appContainer.postsRepository ) } }3
JetnewsThemeはTheme.ktで宣言されている。
MaterialThemeはなんだろう。Theme.kt@Composable fun JetnewsTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit ) { MaterialTheme( colors = if (darkTheme) DarkThemeColors else LightThemeColors, typography = themeTypography, shapes = shapes, content = content ) }4
- なるほど、Material design specificationの Compose関数である。
- Color, typography , shapes, contentをパラメータで使ってる。
/** * A MaterialTheme defines the styling principles from the Material design specification. * * Material components such as [Button] and [Checkbox] use values provided here when retrieving * default values. * * It defines colors as specified in the [Material Color theme creation spec](https://material.io/design/color/the-color-system.html#color-theme-creation), * typography defined in the [Material Type Scale spec](https://material.io/design/typography/the-type-system.html#type-scale), * and shapes defined in the [Shape scheme](https://material.io/design/shape/applying-shape-to-ui.html#shape-scheme). * * All values may be set by providing this component with the [colors][ColorPalette], * [typography][Typography], and [shapes][Shapes] attributes. Use this to configure the overall * theme of elements within this MaterialTheme. * * Any values that are not set will inherit the current value from the theme, falling back to the * defaults if there is no parent MaterialTheme. This allows using a MaterialTheme at the top * of your application, and then separate MaterialTheme(s) for different screens / parts of your * UI, overriding only the parts of the theme definition that need to change. * * @sample androidx.ui.material.samples.MaterialThemeSample * * @param colors A complete definition of the Material Color theme for this hierarchy * @param typography A set of text styles to be used as this hierarchy's typography system * @param shapes A set of shapes to be used by the components in this hierarchy */ @Composable fun MaterialTheme( colors: ColorPalette = MaterialTheme.colors, typography: Typography = MaterialTheme.typography, shapes: Shapes = MaterialTheme.shapes, content: @Composable () -> Unit ) { ProvideColorPalette(colors) { val indicationFactory: @Composable () -> Indication = remember { { RippleIndication() } } Providers( IndicationAmbient provides indicationFactory, TypographyAmbient provides typography, ShapesAmbient provides shapes ) { ProvideTextStyle(value = typography.body1, children = content) } } }
- これはFlutterのMaterialAppのthemeと一緒だと思う。
https://api.flutter.dev/flutter/material/MaterialApp-class.html
5
元に戻して、AppContentのCompose関数をみてみよう
- Crossfade関数はFadeAnimationを使うための関数みたい
- FlutterのFadeTransition Widgetと似ていると思う。
@Composable private fun AppContent( postsRepository: PostsRepository, interestsRepository: InterestsRepository ) { Crossfade(JetnewsStatus.currentScreen) { screen -> Surface(color = MaterialTheme.colors.background) { when (screen) { is Screen.Home -> HomeScreen(postsRepository = postsRepository) is Screen.Interests -> InterestsScreen(interestsRepository = interestsRepository) is Screen.Article -> ArticleScreen( postId = screen.postId, postsRepository = postsRepository ) } } } }終わりに
今回はここまでです。
つづく。。。
- 投稿日:2020-06-21T19:47:23+09:00
初めてのEpoxy
入社して3ヶ月経ちそうですが、まだEpoxyを触ったことなかったので備忘録として書いていきます。
Epoxyを使うとRecyclerViewを簡単に実装できるみたいです!(中毒性があるので注意らしい)実装
Gradle
まずはEpoxyを使うためにGradleに依存関係を記述します。
build.gradledef epoxy_version = "3.11.0" implementation "com.airbnb.android:epoxy:$epoxy_version" // databinding使用するなら implementation "com.airbnb.android:epoxy-databinding:${epoxy_version}" kapt "com.airbnb.android:epoxy-processor:$epoxy_version"package-info
gradleに依存関係を記述したら、
package-info.java
ファイルをプロジェクト内に作成します。package-info.java@EpoxyDataBindingLayouts({R.layout.recyclerview_item}) package com.example.todo; import com.airbnb.epoxy.EpoxyDataBindingLayouts;
@EpoxyDataBindingLayouts
アノテーションでレイアウトファイルを指定するとModelが自動生成してくれます。
ここで一旦ビルドをかけてください。layout
過去の記事のxmlファイルをDataBindingするために
<layout>
属性で囲います。後に書くControllerからTodoTitleを受け取って表示させます。recyclerview_item.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" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="todoTitle" type="String" /> </data> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" android:foreground="?android:attr/selectableItemBackground" app:cardElevation="10dp"> <LinearLayout android:id="@+id/LinearLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:background="@android:color/white" android:orientation="horizontal"> <ImageView android:id="@+id/sampleImg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:contentDescription="@string/recycler_picture" app:srcCompat="@mipmap/ic_launcher_round" /> <TextView android:id="@+id/sampleTxt" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="@{ todoTitle }" tools:text="TODO" android:textSize="30sp" /> </LinearLayout> </androidx.cardview.widget.CardView> </layout>Controller
Controllerは引数が一つの時は
TypedEpoxyController<List<Todo>>()
を継承します。これは引数2つの場合は、
Typed2EpoxyController()
3つ、Typed3EpoxyController()
4つ、Typed4EpoxyController()
今回は、表示するリストだけをもらえればいいので、
TypedEpoxyController()
を選択しました。EpoxyController.ktclass RecyclerViewController( lifecycleOwner: LifecycleOwner, viewModel: MainViewModel ) : TypedEpoxyController<List<Todo>>() { init { viewModel.todoList.observe(lifecycleOwner, ::setData) } override fun buildModels(data: List<Todo>?) { data?.forEach { recyclerviewItem { id(modelCountBuiltSoFar) todoTitle(it.todoTitle) } } } }順を追ってControllerを見ていきましょう。
buildModels()
はsetData()
が呼ばれると実行されます。そのため今回はRoomに保存しているLiveDataが更新させるたびにsetData()
を呼ぶようにObserveを書きました。
buildModels()
内に表示させるものを書いていきます。引数でもらったdataを自動生成された拡張関数recyclerviewItemで表示させます。inline fun ModelCollector.recyclerviewItem( modelInitializer: RecyclerviewItemBindingModelBuilder.() -> Unit ) { add( RecyclerviewItemBindingModel_().apply { modelInitializer() } ) }毎回
add()
を書かずに済むのでとても便利ですね!(これが自動生成されるなんて)
id()
とは、表示される要素毎に重複しないものを設定して上げる必要があります。これを指定すると表示させる要素の差分などを判別してくれるらしい。とりあえず被らないようにmodelCountBuiltSoFar
を入れました。これは、Epoxyで表示させている要素の数です。idの付け方で迷ったらこれにしてます。Activity
あとは同じみの記述をするだけでリスト表示ができました! ViewHolderやらを書かなくて済むのはとてもいい感じです。
MainActivity.ktrecyclerViewController = RecyclerViewController(this, mainViewModel) main_recycler_view.apply { this.adapter = recyclerViewController.adapter this.layoutManager = LinearLayoutManager(this@MainActivity) }
- 投稿日:2020-06-21T16:49:38+09:00
【Android】MergeAdapterを使ってBanner + GridItemのパターンを実装する
概要
巷によくあるアプリのパターンとして、画面の上に横長のバナーやレイアウトを置いて、その下に格子状のレイアウトを置くというものがあります。
このデザインに対応するためのよくある実装として、一つのRecylerAdapterで、ViewHolderを複数用意して、item毎にViewHolderを切り替えるといったものがあります。
しかし、ロジックが複雑化するので、実装コストや後のメンテナンスコストが増えてしまう原因でもあります。今回はよくあるこのパターンを、recyclerviewのalpha版に実装されているMergeAdapterを使って実装していきます。
Sample
https://github.com/alpha2048/MergeAdapterTest
解説
MergeAdapterはrecyclerviewの1.2.0のalpha02から入っています。
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha03"実装はとても簡単です
- Banner用のAdapterを作成
- GridItem用のAdapterを作成
- MergeAdapterに上の2つをマージする
Banner用のAdapter
class BannerAdapter() : RecyclerView.Adapter<ItemBannerViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemBannerViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = ItemListBannerBinding.inflate(layoutInflater, parent, false) return ItemBannerViewHolder(binding) } override fun onBindViewHolder(holder: ItemBannerViewHolder, position: Int) { } override fun getItemCount(): Int { return 1 } } class ItemBannerViewHolder(val binding: ItemListBannerBinding) : RecyclerView.ViewHolder(binding.root)GridItem用のAdapter
class GridItemAdapter (private val viewModel: MainViewModel) : RecyclerView.Adapter<ItemGridViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemGridViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = ItemListGridItemBinding.inflate(layoutInflater, parent, false) return ItemGridViewHolder(binding) } override fun onBindViewHolder(holder: ItemGridViewHolder, position: Int) { holder.binding.image.load(viewModel.repoItems[position].owner.avatar_url) } override fun getItemCount(): Int { return viewModel.repoItems.size } } class ItemGridViewHolder(val binding: ItemListGridItemBinding) : RecyclerView.ViewHolder(binding.root)この2つをMergeAdapterにマージする
val bannerAdapter = BannerAdapter() val gridItemAdapter = GridItemAdapter(viewModel) val mergeAdapter = MergeAdapter( bannerAdapter, gridItemAdapter ) binding.recyclerView.apply { val gridLayoutManager = GridLayoutManager(context,3) gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int = if (position == 0) 3 else 1 } layoutManager = gridLayoutManager adapter = mergeAdapter }注意点として、LayoutManagerはGridLayoutManagerを使うので、バナーのように1列に表示する場合はSpanSizeの制御をきちんと入れましょう。
あとがき
簡単な実装で、性質の違うデータ、レイアウトを別々のAdapterで管理できるのはとてもよいですね!
地味にいろんなところで使えそうなので、安定版のリリースが待ち遠しいです。参考資料
- 投稿日:2020-06-21T12:15:02+09:00
Espresso入門
はじめに
AndroidでUIテストをするためには何を使うのが良いか調べて見たところ、
単一のアプリのUIテスト方法として EspressoがDeveloperGuideでも紹介されていたので、試してみました。導入〜テスト作成まで
AndroidStudioで簡単なプログラムを作成して、それに対するUIテストを書いてみました。
が、
面倒くさそう難しそうなので、書くのは大変そうですね...いい方法がないか調べていると、DeveloperGuideにもこんなページが..
Espresso テスト レコーダーで UI テストを作成する
この方法が使えるならと..試しみました。。
SampleにAndroidStudioのプロジェクト作成で一発で作成できるLoginActivityを..
プロジェクトを作成して、Espressoを実行..
アプリが起動して、IDとパスワードを入力して、ボタンをタップしてみました。
すると、以下のキャプチャのように記録されます。
(マシン性能によりそうですが、実際は結構重く、入力もすんなりできませんでした..)OKをタップするとテストクラスを出力できます。
コードはこんな風に出力されます。
テストクラスなので、実行できます。@LargeTest @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField var mActivityTestRule = ActivityTestRule(LoginActivity::class.java) @Test fun loginActivityTest() { val appCompatEditText = onView( allOf( withId(R.id.username), childAtPosition( allOf( withId(R.id.container), childAtPosition( withId(android.R.id.content), 0 ) ), 0 ), isDisplayed() ) ) appCompatEditText.perform(replaceText("test"), closeSoftKeyboard()) 以下省略「Add Assertion」ボタンを押せば、キャプチャのようにアサーションを追加できます。
(ダイアログもできるのですが、レイアウト位置が正しく設定されず手直しが必要です..)まとめ
ものによっては手直しが必要ですし、ボイラーコードになりがちなのでメンテナンステストもかかりそうですが、
(UIテスト自体コストも高いし..)
UIテストはコストが高いからだけで今まで避けてきたので意外とハードルは高くないのかなと思いました。ログインやエラーハンドリングのようなそのものはあまり変わらない部分であれば導入してみるのも良いかと思いました。
- 投稿日:2020-06-21T10:35:46+09:00
AndroidでDividerを描画する方法
これは何
アンドロイドアプリを開発している際、任意の場所に区切り線を描画したいことがあるかと思います。
区切り線の描画方法について検索しても、情報が乱立していてどれが正しいのか分からなかったのですが、おそらく正解であろう方法を見つけたのでまとめました。dividerを設定する方法
答えはこちらのデモアプリにありました。
Material Components Catalog App
以下のxml要素をdivierを描画したいところに設定するだけです。
layout.xml<!-- horizontal divider --> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="?android:attr/listDivider" /> <!-- vertical divider --> <View android:layout_width="1dp" android:layout_height="match_parent" android:background="?android:attr/listDivider" />マテリアルデザインを導入している場合は、dark themeに切り替えても自動的に色を反転してくれます。
参考リンク
- 投稿日:2020-06-21T00:13:15+09:00
Androidでカレンダーのレイアウトを全力で自作する
概要
前回は曜日の部分だけ作ったので、今回は日付の部分を作っていきたいと思います。
全力で自作
activity_main.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" xmlns:tools="http://schemas.android.com/tools"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/sunday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Sun" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/monday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mon" app:layout_constraintEnd_toEndOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/sunday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/tuesday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Tue" app:layout_constraintEnd_toEndOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/monday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/wednesday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Wed" app:layout_constraintEnd_toEndOf="@id/thursday" app:layout_constraintStart_toEndOf="@+id/tuesday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/thursday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Thu" app:layout_constraintEnd_toEndOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/wednesday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/friday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fri" app:layout_constraintEnd_toEndOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/thursday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/saturday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Sat" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/friday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/cell_1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="1" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="2" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="3" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="4" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toStartOf="@id/thursday" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="5" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="6" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="7" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_11" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="8" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cell_1" /> <TextView android:id="@+id/cell_12" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="9" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@+id/cell_2" /> <TextView android:id="@+id/cell_13" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="10" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@+id/cell_3" /> <TextView android:id="@+id/cell_14" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="11" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toStartOf="@+id/cell_15" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@+id/cell_4" /> <TextView android:id="@+id/cell_15" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="12" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@id/cell_1" /> <TextView android:id="@+id/cell_16" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="13" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@+id/cell_6" /> <TextView android:id="@+id/cell_17" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="14" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@id/cell_1" /> <TextView android:id="@+id/cell_21" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="15" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/cell_11" /> <TextView android:id="@+id/cell_22" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="16" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@+id/cell_12" /> <TextView android:id="@+id/cell_23" android:layout_width="27dp" android:layout_height="34dp" android:text="17" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@+id/cell_13" /> <TextView android:id="@+id/cell_24" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="18" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toStartOf="@+id/cell_15" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@+id/cell_14" /> <TextView android:id="@+id/cell_25" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="19" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@+id/cell_15" /> <TextView android:id="@+id/cell_26" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="20" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@+id/cell_16" /> <TextView android:id="@+id/cell_27" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="21" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@+id/cell_17" /> <TextView android:id="@+id/cell_31" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="22" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/cell_21" /> <TextView android:id="@+id/cell_32" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="23" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@+id/cell_22" /> <TextView android:id="@+id/cell_33" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@+id/cell_23" /> <TextView android:id="@+id/cell_34" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="25" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/cell_15" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@+id/cell_24" /> <TextView android:id="@+id/cell_35" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="26" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@+id/cell_25" /> <TextView android:id="@+id/cell_36" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="27" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@+id/cell_26" /> <TextView android:id="@+id/cell_37" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="28" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@+id/cell_27" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>とりあえず書いてみたが....
実行してみると、、、、微調節
なんかおかしい....真ん中の2つだけおかしいがそこだけ見ても違いがわからなかった...が下記のように修正したところ
activity_main.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" xmlns:tools="http://schemas.android.com/tools"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/sunday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Sun" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/monday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mon" app:layout_constraintEnd_toEndOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/sunday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/tuesday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Tue" app:layout_constraintEnd_toEndOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/monday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/wednesday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Wed" app:layout_constraintEnd_toEndOf="@id/thursday" app:layout_constraintStart_toEndOf="@+id/tuesday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/thursday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Thu" app:layout_constraintEnd_toEndOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/wednesday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/friday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fri" app:layout_constraintEnd_toEndOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/thursday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/saturday" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Sat" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/friday" app:layout_constraintTop_toTopOf="@+id/sunday" /> <TextView android:id="@+id/cell_1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="1" app:layout_constraintBottom_toTopOf="@id/cell_11" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="2" app:layout_constraintBottom_toTopOf="@id/cell_12" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="3" app:layout_constraintBottom_toTopOf="@id/cell_13" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="4" app:layout_constraintBottom_toTopOf="@id/cell_14" app:layout_constraintEnd_toStartOf="@id/thursday" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="5" app:layout_constraintBottom_toTopOf="@id/cell_15" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="6" app:layout_constraintBottom_toTopOf="@id/cell_16" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="7" app:layout_constraintBottom_toTopOf="@id/cell_17" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@id/sunday" /> <TextView android:id="@+id/cell_11" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="8" app:layout_constraintBottom_toBottomOf="@id/cell_21" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cell_1" /> <TextView android:id="@+id/cell_12" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="9" app:layout_constraintBottom_toBottomOf="@id/cell_22" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@+id/cell_2" /> <TextView android:id="@+id/cell_13" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="10" app:layout_constraintBottom_toBottomOf="@id/cell_23" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@+id/cell_3" /> <TextView android:id="@+id/cell_14" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="11" app:layout_constraintBottom_toBottomOf="@id/cell_24" app:layout_constraintEnd_toStartOf="@+id/cell_15" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@+id/cell_4" /> <TextView android:id="@+id/cell_15" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="12" app:layout_constraintBottom_toBottomOf="@id/cell_25" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@id/cell_5" /> <TextView android:id="@+id/cell_16" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="13" app:layout_constraintBottom_toBottomOf="@id/cell_26" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@+id/cell_6" /> <TextView android:id="@+id/cell_17" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="14" app:layout_constraintBottom_toBottomOf="@id/cell_27" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@id/cell_7" /> <TextView android:id="@+id/cell_21" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="15" app:layout_constraintBottom_toBottomOf="@id/cell_31" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/cell_11" /> <TextView android:id="@+id/cell_22" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="16" app:layout_constraintBottom_toBottomOf="@id/cell_32" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@+id/cell_12" /> <TextView android:id="@+id/cell_23" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="17" app:layout_constraintBottom_toBottomOf="@id/cell_33" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@+id/cell_13" /> <TextView android:id="@+id/cell_24" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="18" app:layout_constraintBottom_toBottomOf="@id/cell_34" app:layout_constraintEnd_toStartOf="@+id/cell_15" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@+id/cell_14" /> <TextView android:id="@+id/cell_25" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="19" app:layout_constraintBottom_toBottomOf="@id/cell_35" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@+id/cell_15" /> <TextView android:id="@+id/cell_26" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="20" app:layout_constraintBottom_toBottomOf="@id/cell_36" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@+id/cell_16" /> <TextView android:id="@+id/cell_27" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="21" app:layout_constraintBottom_toBottomOf="@id/cell_37" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@+id/cell_17" /> <TextView android:id="@+id/cell_31" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="22" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/monday" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/cell_21" /> <TextView android:id="@+id/cell_32" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="23" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/tuesday" app:layout_constraintStart_toEndOf="@+id/cell_1" app:layout_constraintTop_toBottomOf="@+id/cell_22" /> <TextView android:id="@+id/cell_33" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/wednesday" app:layout_constraintStart_toEndOf="@+id/cell_2" app:layout_constraintTop_toBottomOf="@+id/cell_23" /> <TextView android:id="@+id/cell_34" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="25" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/cell_15" app:layout_constraintStart_toEndOf="@+id/cell_3" app:layout_constraintTop_toBottomOf="@+id/cell_24" /> <TextView android:id="@+id/cell_35" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="26" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/friday" app:layout_constraintStart_toEndOf="@+id/cell_4" app:layout_constraintTop_toBottomOf="@+id/cell_25" /> <TextView android:id="@+id/cell_36" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="27" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/saturday" app:layout_constraintStart_toEndOf="@+id/cell_5" app:layout_constraintTop_toBottomOf="@+id/cell_26" /> <TextView android:id="@+id/cell_37" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="28" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/cell_6" app:layout_constraintTop_toBottomOf="@+id/cell_27" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>いい感じになりました!
TopやBottomをちゃんとした上下の要素ではなく1列目のを指定した場所があったことが問題だった模様?
ConstraintLayout便利なようで便利じゃないような...わかったようなわからないような.....次回
ほんとは枠線とかを入れてみたいが全然やり方が分からなかったので明日からはそこを少し調べていきたいと思います..!!!