iOS側のUIの特徴と見比べる

Android側でのUI実装のヒント
Android個人開発LT
2022/04/12
Fumiya	Sakai
自己紹介
・Fumiya	Sakai
・Freelance	App	Engineer
アカウント:
・Twitter:	https://twitter.com/fumiyasac

・Facebook:	https://www.facebook.com/fumiya.sakai.37

・Github:	https://github.com/fumiyasac	

・Qiita:	https://qiita.com/fumiyasac@github
発表者:
・Born	on	September	21,	1984
これまでの歩み:
Web	Designer
2008	~	2010
Web	Engineer
2012	~	2016
App	Engineer
2017	~	Now
iOS	/	Android	/	sometimes	Flutter
iOSのUI実装本を執筆しています!
書籍に掲載したサンプルのバージョンアップや続編等に現在着手中です。
少しの工夫で実現できるTIPS集やライブラリ表現の活用集をはじめとした、iOSア
プリ開発の中でも特にUI実装やUIKitを利用した画面の中で特徴を与える様な表現
という題材に焦点を当てた書籍となっております。
現在は電子書籍版のみとなります。	こちらは全て¥1,000となっております。
https://just1factory.booth.pm/
概要:
https://book-tech.com/
価格:
📖 	Booth
📖 	Book	Tech
UI実装であると嬉しいレシピブックの最新情報
UI実装であると嬉しいレシピブックVol.3として昨年10月に商業化しました!
New
これまでの同人誌として頒布したものに加えて、Vol.1及びVol.2に頒布したものの
中で書籍に載せきれなかったものや表現や動きが特徴的でユーザーにもほんの少し
遊び心を与える様なUI実装を紹介したものをVol.3としています。
概要:
これからの構想:
こちらで購入可能です:
Amazon	/	Google	Play	/	Apple	Books	/	KINOKUNIYA	/	Rakuten	BOOKS	etc..
🏊 	iOS:	SwiftUIを利用したUI実装や動画関連の実装
🏊 	Android:	Jetpack	Composeの基本やその他気になるUI表現の考察
https://www.slideshare.net/fumiyasakai37/android-240434551
2.	少しずつキャッチアップしていくAndroidアプリ開発の補足と振り返り
https://www.slideshare.net/fumiyasakai37/android-245108194
3.	最近の業務やAndroid関連のインプットと振り返り
今回のスライドにつきまして
過去にAndroid関連の登壇内容の中でUI実装関連部分を集めたものになります
参考:	比較的軽めのタスクをこなしながらキャッチアップ期間を過ごした時の記録

https://note.com/fumiyasac/n/nec1c3c80f12d
https://www.slideshare.net/fumiyasakai37/kotlinvol27-android
1.	Kotlin愛好会Vol27資料:	少しずつキャッチアップしていくAndroidアプリ開発	(改訂版)
今回は以前の登壇やその他の機会でも過去にお話した内容のダイジェスト版になります!
iOS/Androidのデザインガイドラインを理解する
デザインの統一感を意識していく際にもMaterialDesignの事を知る
iOS側もデザインをMaterialDesignに寄せていく決断をした際もガイドラインと合わせて不自然にならないように注意。
iOS:	Human	Interface	Guideline Android:	Material	Design
一見すると同じ様な見た目にも見えるかもしれないが、細かな点を注意深く見ていくと実装で配慮すべき点が見えてくる。
ActivityやFragmentのライフサイクルを理解する
画面に関する実装をする場合には押さえておきたい重要な部分
Activity Fragment
できるだけ見通しをよくす
るためにシンプルにする。
Activity:
Fragment:
画面レイアウトに関する処
理はできるだけこちらへ。
どんな感じの構成になっているかの一例:
Intentによる画面遷移処理など画面クラス全体に関わるもの
Activity:
Fragment:
RecyclerViewの処理やメニュー部分など画面表示全般
※	ActivityとFragmentが1対1対応が基本型
①
GUIを利用したレイアウトの作成について(1)
AutoLayout(iOS)	&	ConstraintLayout(Android)を見てみる
10
24
24
10
CenterX:280
CenterY:200
相対的な位置関係の調節を
コードまたはGUIで実装
<androidx.constraintlayout.widget.ConstraintLayout

				android:layout_width="match_parent"

				android:layout_height="wrap_content">

				<View	

								android:layout_width="0dp"

								android:layout_height="wrap_content"

								app:layout_constraintTop_toTopOf="10dp"

								app:layout_constraintBottom_toBottomOf="10dp"

								app:layout_constraintStart_toStartOf="24dp"

								app:layout_constraintEnd_toEndOf="24dp"	/>
contentView.addSubview(childView)

childView.snp.makeConstraints	{	make	in

					make.top.equalToSuperview().offset(10.0)

					make.right.equalToSuperview().offset(24.0)

					make.left.equalToSuperview().offset(24.0)

					make.bottom.equalToSuperview().offset(10.0)

}
contentView.addSubview(childView)

childView.snp.makeConstraints	{	make	in

					make.centerX.equalToSuperview()

					make.centerY.equalToSuperview()

					make.height.equalTo(200.0)

					make.width.equalTo(280.0)

}
<LinearLayout

				android:layout_width="match_parent"

				android:layout_height="wrap_content"

				android:gravity="center_horizontal"

				android:orientation="vertical">

				<View

								android:layout_width="280dp"

								android:layout_height="200dp"	/>
iOS:	AutoLayout	(with	SnapKit)
Android:	ConstraintLayout	&	Gravity
①	相対配置
②	中央寄せ
①
②
②
GUIを利用したレイアウトの作成について(2)
UIStackView(iOS)	&	LinearLayout(Android)を見てみる
UIStackViewの内部にXibで切り出したりコードで
作成したView要素のクラスを追加する。
iOS:	UIStackView
match_parent/wrap_contentで親との位置関係を
指定しorientationで横・縦方向の指定をする。
Android:	LinearLayout
レイアウトを構築する上で
も活用機会が多いView要素
Horizontal
Vertical
<LinearLayout

				android:layout_width="match_parent"

				android:layout_height="wrap_content"

				android:orientation=“vertical">

この中に各種要素やConstraintLayoutを配置

</LinearLayout>
contentsStackView.addArrangedSubview(contentView1)

contentsStackView.addArrangedSubview(contentView2)
コードでの実装例:
contentView2
contentView1
縦に長い可変の画面要素の配置や等間隔での要素
の整列時に用いる場面など結構な頻度で活用する
ConstraintLayoutとも上手に併用しながら実装
iOSとAndroidでのUI表現の方針の違いを知る(1)
縦方向だけではなく横方向へのスクロールも考慮する様な形の画面
Androidにおける実装方針例
RecyclerViewの入れ子構造	&	適用するLayoutManagerをSection毎に変更
Section:	0	(Category)
Section:	1	(Article)
Section:	2	(Featured)
Horizontal	Scroll
Horizontal	Scroll
Grid	Layout	Pattern
val	gridlayoutManager	=	GridLayoutManager(recyclerView.context,	4)

gridlayoutManager.spanSizeLookup	=	object	:	GridLayoutManager.SpanSizeLookup()	{

				override	fun	getSpanSize(position:	Int):	Int	{

								if	(categoriesCount	<	position)	{

												return	4

								}	else	{

												return	1

								}

				}

}

recyclerView.layoutManager	=	gridlayoutManager
val	linearLayoutManager	=

				LinearLayoutManager(recyclerView.context,	LinearLayoutManager.HORIZONTAL,	false)

recyclerView.layoutManager	=	linearLayoutManager
※iOSではUICollectionViewを利用する
-	格子状に並べる表現におけるLayoutManager設定例
-	水平方向へのスクロール表現におけるLayoutManager設定例
iOSとAndroidでのUI表現の方針の違いを知る(2)
Pinterestの様に配置するサムネイル画像のアスペクト比に合わせる
Androidにおける実装方針例
適用するLayoutManagerをStaggeredGridLayoutManagerに変更
Waterfall	Grid
val	staggeredGridLayoutManager

	=	StaggeredGridLayoutManager(2,	StaggeredGridLayoutManager.VERTICAL)

recyclerView.layoutManager	=	gridlayoutManager
※iOSではUICollectionViewを利用する
-	一見すると難しそうなレイアウトに見えるが実はこれだけ
写真アスペクト比
を考慮する形のレ
イアウト表現
参考:	StaggeredGridLayoutManager

https://developer.android.com/reference/androidx/
recyclerview/widget/StaggeredGridLayoutManager
一方でiOSでは自前で実装するのはなかなか大変な実装
参考:	UICollectionViewCompositionalLayoutを利用した例

https://qiita.com/fromkk/items/475eb761fa3352829f52
従来通りのUICollectionViewの実装だとレイアウト属性を調整する形になる
iOSとAndroidでのUI表現の方針の違いを知る(3)
配置したヘッダー画像がスクロールに合わせて伸縮する様なレイアウト
AndroidではCoodinatorLayoutを活用して実装する
①	実際の実装事例紹介

https://guides.codepath.com/android/handling-scrolls-with-
coordinatorlayout
一方でiOSでは自前で実装するかライブラリを活用する
ライブラリを利用しないで独自実装を進める方針:
基本はUIScrollView等を利用してスクロールの変化量をキャッチ
してヘッダー画像に付与しているAutoLayout制約を変更する形

(UINavigationBarの変化も考慮すると結構大変な処理)
例)	ParallaxHeader:

https://github.com/romansorochak/ParallaxHeader
Streachy	Header
②	CoodinatorLayoutに関する解説

https://techblog.yahoo.co.jp/android/androidcoordinatorlayout/
iOSとAndroidでのUI表現の方針の違いを知る(4)
iOSのUITabBarControllerの様な下タブで選択画面を切り替える形の表現
≡	タイトル
Bottom	Navigation
ViewPager
Toolbar	Title
スクリーン
Androidにおける実装方針例 ※iOSではUITabBarControllerを利用する
下タブを切り替え
るとタイトル表示
が変更される
下タブを切り替え
るとスクリーン表
示が変更される
ViewPager	&	BottomNavigation
BottomNavigationの使いどころを整理する

https://qiita.com/konifar/items/d64ada9c8ce069e6d138
BottomNavigationViewとNavigationを一緒に使う

https://qiita.com/superman9387/items/d373c8bddfe2243a49a7
ViewPagerとPagerAdapterでスワイプビューを作ってみる

https://qiita.com/shira-jun/items/1c40b087b38a370c6d18
BottomNavigation(下タブ)	+	Navigationを試してみる

https://qiita.com/orimomo/items/313ce4e273fa741dddf2
Navigation	+	BottomNavigationViewでFragmentの状態を残す

https://bit.ly/3mOV5Rh
Androidでは標準で準備されている表現例(1)
DrawerMenuはAndroidだと標準で用意されているがiOSは別途作成が必要
AndroidではDrawerMenuが標準で実装されている
公式ドキュメントでも実装方針や方法が示されている。

https://developer.android.com/guide/navigation/navigation-ui
一方でiOSでは自前で実装するかライブラリを活用する
ライブラリを利用しないで独自実装を進める方針:
-	ContainerViewとGestureRecognizerを利用して実現する
iOSアプリ内でDrawerMenuに近しい挙動を実現する例:

https://qiita.com/fumiyasac@github/items/eb5b17ab90f5aa27b793
ライブラリを利用して進める方針:
例)	SideMenu:

https://github.com/jonkykong/SideMenu
-	よく利用されるものなので著名なライブラリも存在する
Androidでは標準で準備されている表現例(2)
Tab付SwipeViewもAndroidでは実装方法が紹介されているがiOSは別途作成が必要
Tab	No.1 Tab	No.2 Tab Tab	No.1 Tab	No.2 Tab
AndroidではTab付SwipeViewの事例がすでにある
公式のドキュメントでも実装方針や方法が示されている。

https://developer.android.com/guide/navigation/navigation-
swipe-view

一方でiOSでは自前で実装するかライブラリを活用する
ライブラリを利用しないで独自実装を進める方針:
-	タブ表示部分:	UIScrollView	or	UICollectionView
例)	Parchment:

https://github.com/rechsteiner/Parchment
-	コンテンツ表示部分:	UIPageViewController
ライブラリを利用して進める方針:
-	よく利用されるものなので著名なライブラリも存在する
Swipable	Page	Contents
Component単位でAndroidでは標準である表現例(1)
例1.	FAB(Floating	Action	Button)の実装
公式ガイド&リファレンス
iOS(UIKit想定時)で類似した表現をするためのライブラリ例
+ +
Floating	Action	Button
Settings
Taking	Picture
Add	Articles
Guide:

https://developer.android.com/guide/topics/ui/floating-
action-button?hl=ja
Reference:

https://developer.android.com/reference/com/google/android/
material/floatingactionbutton/FloatingActionButton?hl=ja
Library)	Floaty:

https://github.com/kciter/Floaty
大まかな部分は再現できそうではあるが、付随するアニメー
ション表現等にもこだわると意外に大変な予感も。
補足:
Component単位でAndroidでは標準である表現例(2)
例2.	Bottom	Sheetの実装
Bottom	Sheet
実装時の参考資料
BottomSheetBehaviorを使う:

https://qiita.com/napplecomputer/items/5b3d1225533a59488ac3
[Android]	BottomSheetの実装:

https://qiita.com/kurramkurram/items/32b5b208c2ddb6ff4f51
Library)	PanModal:

https://github.com/slackhq/PanModal

Library)	FloatingPanel:

https://github.com/scenee/FloatingPanel
iOS(UIKit想定時)で類似した表現をするためのライブラリ例
こちらも大まかな部分は再現できるが、指の動きに伴った状
態変化等を考慮するとかなり難易度が上がるケースも。
補足:
iOSではSemiModal
やHalfModalの名
称で呼ばれる。
Component単位でAndroidでは標準である表現例(3)
例3.	Toastの実装
Toast
公式ガイド&リファレンス
iOS(UIKit想定時)で類似した表現をするためのライブラリ例
Guide:

https://developer.android.com/guide/topics/ui/notifiers/
toasts?hl=ja
Reference:

https://developer.android.com/reference/android/widget/
Toast?hl=ja
Library)	Toast-Swift:

https://github.com/scalessec/Toast-Swift
iOSアプリで類似の表現をとるものはあまり多くはないが稀
に見かける程度。
補足:
その他(1):	iOSと形は違うがポイントは似ている例
一覧画面でのページネーションの処理におけるiOS/Androidでの処理
OnScrollListener
スクロールの最下部へ到
達した際に次のデータを
読み込む処理を実行。
Paging3を理解するための資料集:
iOS(UIKit想定時):
実践	Paging	3:

https://speakerdeck.com/ticktaku77/shi-jian-paging-3
Flow	APIとPaging3を考える際のポイント例:

https://github.com/zivkesten/Paging-with-Flow-
sample-app
スクロールの変化や該当部分の出現をキャッチする:
-	UIScrollViewDelegate
-	UITableViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView)
func tableView(_ tableView: UITableView, willDisplay
cell: UITableViewCell, forRowAt indexPath: IndexPath)
binding.recyclerView.apply	{

//	…	途中省略	…

//	Scrollイベントをキャッチするためのイベントリスナーを定義する

				addOnScrollListener(object	:	RecyclerView.OnScrollListener()	{

								override	fun	onScrollStateChanged(recyclerView:	RecyclerView,	newState:	Int)	{

												super.onScrollStateChanged(recyclerView,	newState)

			

			//	canScrollVertically(1)でスクロールの最下端に到達したかの判定をする

												if	(!recyclerView.canScrollVertically(1))	{

																(binding.recyclerView.adapter	as?	ImagePhotoGalleryAdapter)?.let	{	adapter	->

																				adapter.courseCommentListBindingModel.let	{	bindingModel	->

																								if	(bindingModel.imagePhotoGalleryBindingModelList.isNotEmpty()	&&

																												bindingModel.imagePhotoGalleryBindingModelList.last().pageInfo.hasNextPage	&&

																												!adapter.shouldShowPagingProgress

																								)	{

																												viewModel.fetchNextCursor(bindingModel)

																								}

																				}

																}

												}

								}

				})

}
その他(1):	iOSと形は違うがポイントは似ている例
RecyclerView内部に定義するイベントリスナーとの連携部分のコード例
現在の画面表示状態を考慮した

次ページデータの取得処理実行
スクロール状態変化時に実行される処理を記述
押下時の処理を外部から渡す
iOSでの●●●Delegateでの処理を

EventListenerで置き換えて考える
iOS/Android間でのほぼ同じ振る舞い
を考えていく際のヒントにもなる部分
その他(2):	簡単に見えて実は落とし穴があるかも
テキストの中にURLがあった場合に押下して該当ページへ進む&コピー等も可能
スマートフォン対応Webサイト等でよくありがちなテキスト表現
autoLinkの設定だけでは不十分な理由とその例

https://android.gcreate.jp/482/
URLが文字列の中にある場合は正規表現で抽出&Spannableで押下可能な形にする
上記の方法では、区切り文字問題やChromeカスタムタブ非対応等の特徴が問題になった。
その他アイデア)	MarkDownに対応しているのであればそれを活用していく感じでも良さそうです。
android:autoLink=“all"	or	autoLinkMaskの設定では不十分であった。
https://xxx.jp?id=y
○
△
※	基本的には平文のテキストの中にURLがある様なケースを想定
URLと文字配置の位置関係によっては正しくならないケースが発生する
今回のケースではこちらの方針を採用
※	コピー等を可能にする設定

android:textIsSelectable="true"
その他(2):	簡単に見えて実は落とし穴があるかも
まずはURL文字列を抽出するためのExtensionを作成する
fun	TextView.setUrlClickable(onClickLinkListener:	OnClickLinkListener)	{

				val	spannableString	=	SpannableString(this.text)

				var	start	=	0

				var	end	=	0

				val	pattern	=

								Pattern.compile("(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]")

				val	matcher	=	pattern.matcher(this.text)

				while	(matcher.find())	{

								start	=	matcher.start()

								end	=	matcher.end()

								spannableString.setSpan(

												LinkClickableSpan(

																url	=	matcher.group(0)	?:	"",

																onClickLinkListener	=	object	:	OnClickLinkListener	{

																				override	fun	onClick(url:	String)	{	onClickLinkListener.onClick(url)	}

																}

												),	

												start,	end,

												Spanned.SPAN_INCLUSIVE_INCLUSIVE

								)

				}

				this.text	=	spannableString

				this.movementMethod	=	LinkMovementMethod.getInstance()

}
抽出URL部分を押下可能にする
URLを抽出するための正規表現
押下時の処理を外部から渡す
class	LinkClickableSpan(

				private	val	url:	String,	private	val	onClickLinkListener:	OnClickLinkListener

)	:	ClickableSpan()	{

				override	fun	onClick(widget:	View)	{ onClickLinkListener.onClick(url)	}

}
その他(2):	簡単に見えて実は落とし穴があるかも
次にActivity/Fragment/ViewHolderで反映させる処理&クリックイベント紐付け
binding.bindingModel	=	bindingModel

binding.executePendingBindings()

binding.descriptionTextView.setUrlClickable(

				onClickLinkListener	=	object	:	OnClickLinkListener	{

								override	fun	onClick(url:	String)	{	//	リンク先のページを開く等の処理	}

				}

)
①	ライブラリGroupieを利用したViewHolder
fun	bind(

				bindingModel:	XXXBindingModel,

				onClickLinkListener:	OnClickLinkListener?

)	{

				binding.bindingModel	=	bindingModel

				binding.executePendingBindings()

				binding.descriptionTextView.setUrlClickable(

								onClickLinkListener	=	object	:	OnClickLinkListener	{

												override	fun	onClick(url:	String)	{

																onClickLinkListener?.onClick(url)

												}

								}

				)

}
override	fun	bind(

				viewBinding:	ViewXXXBinding,

				position:	Int

)	{

				viewBinding.apply	{

								bindingModel	=	xxxBindingModel

								viewBinding.executePendingBindings()

								descriptionTextView.setUrlClickable(

												onClickLinkListener	=	object	:	OnClickLinkListener	{

																override	fun	onClick(url:	String)	{

																				onClickLinkListener.onClick(url)

																}

												}

								)

				}

}
URLリンク用のSpannableを適用直前に
executePendingBindings()を実行する
この記載を忘れていたり順番を間違え
ていると動作しなかった
②	Android純正のViewHolder
※	Activity	/	Fragmentで利用する場合
その他(3):ネイティブ機能で類似した機能を考える
動画Player関連等デバイスで機能の考え方が異なるものは違いを理解する
詳細の記事はこちら:	https://qiita.com/fumiyasac@github/items/2f698d6c330530338826
まとめ
iOS/Android両方の視点を少しずつ持つ様にする事で表現のヒントを得やすい
1.	Material	Designにおける特徴やUI実装での基本方針を知る:
自分自身もAndroidアプリを最初に始めた際に、一番最初に着目した視点が「UI表現やそれを構成するために必要なものの名称」
を把握する所から始めました。次にそこから基本的なUI実装を実現するコードや概要を知る様に進めて行きました。
2.	iOS/Android間におけるデザインや実装方針の違いからヒントを見つける:
類似したデザインであったとしても実装時の考え方はもちろん感覚も違いますし、同じ実装ができたとしても大きく難易度が異
なる場合もある点には注意が必要ですが、違いを知ることによって実装時に難しそうなポイントを発見できると思います。
業務内ではDesignerはもちろんiOS/Androidエンジニア同士でも一緒に考える機会がとても勉強になっています。
3.	iOS/Android両方のUI実装をケアしながら開発する楽しさ:
これまでもDesignerと連携しながら開発を進めていく機会が多かった事もあり、Androidエンジニアも交えて仕様やUIを検討する
機会もありました。覚える事は最初は多いですが個人的には慣れてくると楽しくできた様に思います。
Thank	you	for	listening	!

iOS側のUIの特徴と見比べるAndroid側でのUI実装のヒント