・2020/09/30
AACの記事から1年経ってデータ バインディングの俺的カンニング帳を作る
(Android開発 データ バインディング 虎の巻 Android Studio)
Tags: [Android開発]
● 2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!
・2019/10/02
Kotlin大嫌い人間が Kotlin言語を必死に勉強する
行末にセミコロン;の無い言語は大嫌い
・2019/03/01
2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!
Androidの開発で AACって何? DataBinding、LiveData、ViewModel、LifecycleObserverについて解説
● Android DataBinding ViewBinding
ここは俺のチラシの裏だ。
Android Studio でのデータ バインディング コードのサポート
https://github.com/android/architecture-components-samples
appの下の build.gradleに黄色行を追加
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
// 今の正しい書き方
buildFeatures {
dataBinding true
}
... 略 ...
下記の書き方は「廃止」されました(ビルド時に「警告」が出る)
DSL element 'android.dataBinding.enabled' is obsolete and has been replaced with 'android.buildFeatures.dataBinding'.
dataBinding {
enabled = true
}
● View Binding
View Binding
MainActivity.kt
// activity_main.xml is ActivityMainBinding
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main)
// setSupportActionBar(findViewById(R.id.toolbar))
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
// findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
// .setAction("Action", null).show()
// }
binding.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
}
FirstFragment.kt
class FirstFragment : Fragment() {
// View Binding
// https://developer.android.com/topic/libraries/view-binding#fragments
// fragment_first.xml is FragmentFirstBinding
private var _binding: FragmentFirstBinding? = null
// This property is only valid between onCreateView and onDestroyView .
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
// return inflater.inflate(R.layout.fragment_first, container, false)
// View Binding Use view binding in fragments
_binding = FragmentFirstBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
// Avoid Memory leak
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// view.findViewById<Button>(R.id.button_first).setOnClickListener {
// findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
// }
// findViewByIdしなくても "@+id/button_first"を buttonFirstで参照できる(キャメルケース)
binding.buttonFirst.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// View Binding
binding.noBindingTextView.text = "No Binding Can setText()"
// Data Binding
// Primitive
binding.testString = "Binding Test (String)"
binding.bindingTextView1.text = "バインディングが優先で、これは無視される"
binding.testBoolean = true
binding.testInt = 12345
binding.testColorInt = 0xFF0088FF.toInt() // ARGB A=FF,R=00,G=88,B=FF
// Android Class
context?.let {
binding.testDrawable = getDrawable(it, R.drawable.ic_launcher_foreground)
}
// Resource ID
binding.testStringId = R.string.test_string_res_id
binding.testDrawableId = R.drawable.ic_launcher_foreground
binding.testColorId = R.color.test_color_res_id
binding.testDimenId = R.dimen.test_dimen_res_id
binding.testDimen2Id = R.dimen.test_dimen_0dp
// Binding variable name
binding.testVariableNameSnakeCase = 123
binding.testVariableNameCamelCase = 123
// BindingAdapter
binding.testPaddingStartFloat = 50.0f
binding.testMarginTopFloat = 50.0f
binding.testWidthFloat = 300.0f
binding.bindingTestButton.setOnClickListener {
binding.testBoolean = !binding.testBoolean
binding.testInt += 1
}
}
RecyclerView
// list_item.xml
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
RecyclerView
override fun onBindViewHolder(holder: BindingHolder, position: Int) {
item: T = items.get(position)
holder.binding.setVariable(BR.item, item);
holder.binding.executePendingBindings();
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
adapter = null
}
RecyclerViewを使う Fragment側
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.adapter = adapter
}
override fun onDestroyView() {
recyclerView.adapter = null
super.onDestroyView()
}
●レイアウトとバインディング式
レイアウトとバインディング式
<?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">
<!-- @formatter:off -->
<data>
<import type="android.view.View" />
<import type="androidx.constraintlayout.widget.ConstraintSet" />
<!-- Primitive -->
<variable name="test_int" type="int" />
<variable name="test_float" type="float" />
<variable name="test_string" type="String" />
<variable name="test_boolean" type="boolean" />
<variable name="test_string_no_set" type="String" />
<!-- Value -->
<variable name="test_color_int" type="int" />
<!-- Android Class -->
<variable name="test_drawable" type="android.graphics.drawable.Drawable" />
<!-- Resource ID -->
<variable name="test_string_id" type="int" />
<variable name="test_color_id" type="int" />
<variable name="test_drawable_id" type="int" />
<variable name="test_dimen_id" type="int" />
<variable name="test_dimen_2_id" type="int" />
<!-- BindingAdapter -->
<variable name="test_width_float" type="float" />
<variable name="test_margin_top_float" type="float" />
<variable name="test_padding_start_float" type="float" />
<!-- Binding variable name -->
<variable name="test_variable_name_snake_case" type="int" />
<variable name="testVariableNameCamelCase" type="int" />
</data>
<!-- @formatter:on -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<TextView
android:id="@+id/no_binding_text_view"
android:layout_width="300dp"
android:layout_height="32dp"
android:background="#4F0F"
android:fontFamily="@font/ipaex"
android:textStyle="bold|italic"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/binding_text_view_5"
android:layout_width="300dp"
android:layout_height="32dp"
android:background="#400F"
android:text="@{test_boolean ? `test_boolean=true is VISIBLE` : `test_boolean=false is INVISIBLE`}"
android:visibility="@{test_boolean ? View.VISIBLE : View.INVISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/binding_text_view_4" />
<!-- 文字列は `文字(Shift+@、アクサングラーヴ、0x60)で囲む、日本語等は 3バイトエラーになるので @stringリソース定義が必要 -->
Android UTF-8 パーサは最大 3バイトしか認識しないのが原因
Invalid byte 3 of 3-byte UTF-8 sequence
3バイトUTF-8シーケンスのバイト3が無効です
<!-- View.GONE -->
● include layoutで値を受け渡す方法
variable nameはキャメルケースが必須です。
と言う訳で上の例も全部「キャメルケース」が良いです。
親レイアウト
<?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:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="layoutId" type="int" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondFragment">
<include
android:id="@+id/include_1"
layout="@layout/include_test_layout"
bind:includeInt="@{12345}"
bind:includeColor="@{0xFFFF0000}"
bind:includeString="@{@string/app_name}"
app:layout_constraintTop_toTopOf="parent" />
include_test_layout.xml
小レイアウト
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:showIn="@layout/fragment_second">
<data>
<!-- Variable name must Camel Case -->
<variable name="includeInt" type="int" />
<variable name="includeColor" type="int" />
<variable name="includeString" type="String" />
<!-- Error variable name Snake Case -->
<!-- variable name="test_value" type="int" -->
</data>
<merge>
<TextView
android:id="@+id/include_text_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{includeString}"
android:background="@{includeColor}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
●バインディング アダプター @BindingAdapter
バインディング アダプター @BindingAdapter
BindingAdapters.kt
@JvmStaticが必要な書き方
package com.example.test_my_application
import android.graphics.drawable.Drawable
import android.os.Handler
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.ImageView
import androidx.annotation.DimenRes
import androidx.annotation.IdRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.databinding.BindingAdapter
object BindingAdapters {
private fun log(str: String): Int = Log.d("BindingAdapters", str)
// layout_width, layout_height Float
@BindingAdapter("android:layout_width")
@JvmStatic
fun setLayoutWidth(view: View, valueFloat: Float) {
log("BindingAdapters: setLayoutWidth value " + view.javaClass.simpleName)
val layoutParams = view.layoutParams as ViewGroup.LayoutParams
layoutParams.width = valueFloat.toInt()
view.layoutParams = layoutParams
}
// > Task :app:kaptDebugKotlin
// 警告: Binding adapter AK(android.view.View, float) already exists for android:paddingStart !
// Overriding androidx.databinding.adapters.ViewBindingAdapter#setPaddingStart
// Padding Float
@BindingAdapter("android:paddingStart_No_need_define")
@JvmStatic
fun setPaddingStart(view: View, valueFloat: Float) {
log("BindingAdapters: setPaddingStart value " + view.javaClass.simpleName)
with(view) {
setPadding(
valueFloat.toInt(),
paddingTop,
paddingEnd,
paddingBottom
)
}
}
// https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout.LayoutParams
@BindingAdapter("layout_constraintStart_toStartOf")
@JvmStatic
fun setConstraintStart(view: View, @IdRes startId: Int) {
log("BindingAdapters: setConstraintStart " + view.javaClass.simpleName)
val layoutParams = view.layoutParams as? ConstraintLayout.LayoutParams ?: return
layoutParams.startToStart = startId
view.layoutParams = layoutParams
// 他の Viewが巻き添えを食って期待通りに動かない
// https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintSet
// val constraintLayout = (view.parent as? ConstraintLayout) ?: return
// with(ConstraintSet()) {
// clone(constraintLayout)
// connect(view.id, ConstraintSet.START, startId, ConstraintSet.START)
// applyTo(constraintLayout)
// }
}
// layout_margin DimenRes ResourceId Int
@BindingAdapter("android:layout_marginTop")
@JvmStatic
fun setLayoutMarginTop(view: View, @DimenRes resId: Int) {
log("BindingAdapters: setLayoutMarginTop resId " + view.javaClass.simpleName)
val lp = view.layoutParams as? MarginLayoutParams ?: return
val marginValue = view.context.resources.getDimensionPixelSize(resId)
lp.setMargins(lp.leftMargin, marginValue, lp.rightMargin, lp.bottomMargin)
view.layoutParams = lp
}
// layout_margin Float
@BindingAdapter("android:layout_marginTop")
@JvmStatic
fun View.setMarginTopValue(valueFloat: Float) =
(layoutParams as MarginLayoutParams).apply { topMargin = valueFloat.toInt() }
@BindingAdapter("android:layout_marginStart")
@JvmStatic
fun View.setMarginStartValue(valueFloat: Float) =
(layoutParams as MarginLayoutParams).apply { leftMargin = valueFloat.toInt() }
@BindingAdapter("android:layout_marginBottom")
@JvmStatic
fun setLayoutMarginBottom(view: View, valueFloat: Float) {
log("BindingAdapters: setLayoutMarginBottom value " + view.javaClass.simpleName)
val lp = view.layoutParams as? MarginLayoutParams ?: return
lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, valueFloat.toInt())
view.layoutParams = lp
}
@BindingAdapter("imageUrl", "error")
@JvmStatic
fun loadImage(view: ImageView, url: String, errorDrawable: Drawable) {
// Picasso.get().load(url).error(error).into(view)
view.setImageDrawable(errorDrawable)
// TODO: Fetch URL
Handler().also {
it.postDelayed(
{
view.setImageResource(R.drawable.ic_launcher_background)
}, 3000
)
}
}
}
BindingAdapters.kt
@JvmStaticが不要な書き方
package com.example.test_my_application
import android.graphics.drawable.Drawable
import android.os.Handler
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.ImageView
import androidx.annotation.DimenRes
import androidx.annotation.IdRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.databinding.BindingAdapter
private fun log(str: String): Int = Log.d("BindingAdapters", str)
// layout_width, layout_height Float
@BindingAdapter("android:layout_width")
fun setLayoutWidth(view: View, valueFloat: Float) {
log("BindingAdapters: setLayoutWidth value " + view.javaClass.simpleName)
val layoutParams = view.layoutParams as ViewGroup.LayoutParams
layoutParams.width = valueFloat.toInt()
view.layoutParams = layoutParams
}
// app:layout_constraintDimensionRatio
// "1:2" = 幅:高さ
// "W,1:2" = 幅を高さの 1/2にする = W = 1/2 * H
// "H,1:2" = 高さを幅の 2倍にする = H = 2/1 * W
@BindingAdapter("layout_constraintDimensionRatio")
fun View.setConstraintStart(valueString: String) =
(layoutParams as ConstraintLayout.LayoutParams).apply { dimensionRatio = valueString }
// ImageViewに URLで画像を取得する BindingAdapterのサンプル
@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, errorDrawable: Drawable) {
// Picasso.get().load(url).error(error).into(view)
view.setImageDrawable(errorDrawable)
// TODO: Fetch URL
Handler().also {
it.postDelayed(
{
view.setImageResource(R.drawable.ic_launcher_background)
}, 3000
)
}
}
layout_constraintDimensionRatioのカンニング帳
app:layout_constraintDimensionRatio
"1:2" = 幅:高さ
"W,1:2" = 幅を高さの 1/2にする = W = 1/2 * H
"H,1:2" = 高さを幅の 2倍にする = H = 2/1 * W
● Android DataBinding + ViewModel + LiveData
Android データ バインディング コードラボ
Data Binding in Android
Last updated Oct 9, 2020
SolutionActivity.kt
class SolutionActivity : AppCompatActivity() {
// Obtain ViewModel from ViewModelProviders
private val viewModel by lazy {
ViewModelProviders.of(this).get(SimpleViewModelSolution::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: SolutionBinding =
DataBindingUtil.setContentView(this, R.layout.solution)
binding.lifecycleOwner = this // use Fragment.viewLifecycleOwner for fragments
binding.viewmodel = viewModel
}
}
変更できる MutableLiveDataは _nameで privateで定義する。
変更できない LiveDataは name = _nameで定義して、外部からの参照用とする。
Transformations.mapで _likes(イイネの数)を LiveDataに変換します。
SimpleViewModel.kt
class SimpleViewModelSolution : ViewModel() {
private val _name = MutableLiveData("Ada")
private val _lastName = MutableLiveData("Lovelace")
private val _likes = MutableLiveData(0)
val name: LiveData<String> = _name
val lastName: LiveData<String> = _lastName
val likes: LiveData<Int> = _likes
// popularity is exposed as LiveData using a Transformation instead of a @Bindable property.
val popularity: LiveData<Popularity> = Transformations.map(_likes) {
when {
it > 9 -> Popularity.STAR
it > 4 -> Popularity.POPULAR
else -> Popularity.NORMAL
}
}
fun onLike() {
_likes.value = (_likes.value ?: 0) + 1
}
fun setName(name: String) {
_name.value = name
}
fun setLastName(lastName: String) {
_lastName.value = lastName
}
}
enum class Popularity {
NORMAL,
POPULAR,
STAR
}
BindingAdapters.kt
@BindingAdapter("app:popularityIcon")
fun popularityIcon(view: ImageView, popularity: Popularity) {
val color = getAssociatedColor(popularity, view.context)
ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))
view.setImageDrawable(getDrawablePopularity(popularity, view.context))
}
private fun getAssociatedColor(popularity: Popularity, context: Context): Int {
return when (popularity) {
Popularity.NORMAL -> context.theme.obtainStyledAttributes(
intArrayOf(android.R.attr.colorForeground)
).getColor(0, 0x000000)
Popularity.POPULAR -> ContextCompat.getColor(context, R.color.popular)
Popularity.STAR -> ContextCompat.getColor(context, R.color.star)
}
}
private fun getDrawablePopularity(popularity: Popularity, context: Context): Drawable? {
return when (popularity) {
Popularity.NORMAL -> {
ContextCompat.getDrawable(context, R.drawable.ic_person_black_96dp)
}
Popularity.POPULAR -> {
ContextCompat.getDrawable(context, R.drawable.ic_whatshot_black_96dp)
}
Popularity.STAR -> {
ContextCompat.getDrawable(context, R.drawable.ic_whatshot_black_96dp)
}
}
}
android:onClick="@{() -> viewmodel.onLike()}"
でクリックを実装。
solution.xml
<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="viewmodel"
type="com.example.android.databinding.basicsample.data.SimpleViewModelSolution"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- A simple binding between a TextView and a string observable in the ViewModel -->
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="128dp"
android:text="@{viewmodel.name}"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/name_label"/>
<!-- A simple binding between a TextView and a string observable in the ViewModel -->
<TextView
android:id="@+id/lastname"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="128dp"
android:text="@{viewmodel.lastName}"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lastname_label"/>
<!-- A custom Binding Adapter (`app:popularityIcon`) is used passing `viewmodel.popularity`
as a parameter. Because it's a @Bindable property, the ImageView is automatically updated.
-->
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:contentDescription="@string/profile_avatar_cd"
android:minWidth="48dp"
android:minHeight="48dp"
app:layout_constraintBottom_toTopOf="@+id/likes_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed"
app:popularityIcon="@{viewmodel.popularity}"/>
...
<!-- Listeners can accept lambdas so in this case the ViewModel deals with the event,
bypassing the activity. -->
<Button
android:id="@+id/like_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:onClick="@{() -> viewmodel.onLike()}"
android:text="@string/like"
...
● Fragmentで ViewModel
●書き方、その1 by viewModels()を使う方法
Fragment
HogeFragment.kt
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
class HogeFragment : Fragment() {
private val viewModel: SimpleViewModelSolution by activityViewModels()
または、
private val viewModel: SimpleViewModelSolution by viewModels()
Android KTX
拡張機能のリスト
Android KTX
dependencies {
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.5"
}
kotlinOptions {
jvmTarget = "1.8"
}
●書き方、その2 lazyを使う方法
HogeFragment.kt
class HogeFragment : Fragment() {
// Obtain ViewModel from ViewModelProviders
private val viewModel by lazy {
ViewModelProviders.of(this).get(SimpleViewModelSolution::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// View Binding Use view binding in fragments
_binding = FragmentFirstBinding.inflate(inflater, container, false)
// use Fragment.viewLifecycleOwner for fragments
binding.lifecycleOwner = viewLifecycleOwner
binding.viewmodel = viewModel
val view = binding.root
return view
}
}
●書き方、その3 lateinitを使う方法(lateinitは今風じゃない)
HogeFragment.kt
class HogeFragment : Fragment() {
private lateinit var viewModel: SimpleViewModelSolution
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// View Binding Use view binding in fragments
_binding = FragmentFirstBinding.inflate(inflater, container, false)
// Create Vide Model
viewModel = ViewModelProvider(this).get(SimpleViewModelSolution::class.java)
// use Fragment.viewLifecycleOwner for fragments
binding.lifecycleOwner = viewLifecycleOwner
binding.viewmodel = viewModel
val view = binding.root
return view
}
}
● DialogFragmentからのイベントを Activity以外で受け取りたい場合
DialogFragmentからのイベントを Fragmentで受け取る場合
ダイアログ DialogFragment
●ダイアログのホストにイベントを渡す
Activityで DialogFragmentを生成して、Activityのコールバックを呼び出す
class NoticeDialogFragment : DialogFragment() {
// Use this instance of the interface to deliver action events
internal lateinit var listener: NoticeDialogListener
/* The activity that creates an instance of this dialog fragment must
* implement this interface in order to receive event callbacks.
* Each method passes the DialogFragment in case the host needs to query it. */
interface NoticeDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
listener = context as NoticeDialogListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NoticeDialogListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle): Dialog {
return activity?.let {
// Build the dialog and set up the button click handlers
val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.dialog_fire_missiles)
.setPositiveButton(R.string.fire,
DialogInterface.OnClickListener { dialog, id ->
// Send the positive button event back to the host activity
listener.onDialogPositiveClick(this)
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
// Send the negative button event back to the host activity
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
Activity
class MainActivity : FragmentActivity(),
NoticeDialogFragment.NoticeDialogListener {
fun showNoticeDialog() {
// Create an instance of the dialog fragment and show it
val dialog = NoticeDialogFragment()
dialog.show(supportFragmentManager, "NoticeDialogFragment")
}
// The dialog fragment receives a reference to this Activity through the
// Fragment.onAttach() callback, which it uses to call the following methods
// defined by the NoticeDialogFragment.NoticeDialogListener interface
override fun onDialogPositiveClick(dialog: DialogFragment) {
// User touched the dialog's positive button
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
}
●ダイアログのホストにイベントを渡す
Fragmentで DialogFragmentを生成して、Fragmentのコールバックを呼び出す
class NoticeDialogFragment : DialogFragment() {
// Use this instance of the interface to deliver action events
internal lateinit var listener: NoticeDialogListener
/* The activity that creates an instance of this dialog fragment must
* implement this interface in order to receive event callbacks.
* Each method passes the DialogFragment in case the host needs to query it. */
interface NoticeDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
val fragment = getTargetFragment();
listener = fragment as NoticeDialogListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NoticeDialogListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle): Dialog {
return activity?.let {
// Build the dialog and set up the button click handlers
val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.dialog_fire_missiles)
.setPositiveButton(R.string.fire,
DialogInterface.OnClickListener { dialog, id ->
// Send the positive button event back to the host activity
listener.onDialogPositiveClick(this)
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
// Send the negative button event back to the host activity
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
フラグメント間でデータを渡す
Fragment
class MainFragment : Fragment(),
NoticeDialogFragment.NoticeDialogListener {
fun showNoticeDialog() {
// Create an instance of the dialog fragment and show it
val dialog = NoticeDialogFragment()
dialog.setTargetFragment(this, 12345)
dialog.show(getFragmentManager(), "NoticeDialogFragment")
// getChildFragmentManager() ?
}
// The dialog fragment receives a reference to this Activity through the
// Fragment.onAttach() callback, which it uses to call the following methods
// defined by the NoticeDialogFragment.NoticeDialogListener interface
override fun onDialogPositiveClick(dialog: DialogFragment) {
// User touched the dialog's positive button
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
}
●ダイアログのホストにイベントを渡す
Fragmentで DialogFragmentを生成して、Fragmentのコールバックを呼び出す
class NoticeDialogFragment : DialogFragment() {
// Use this instance of the interface to deliver action events
internal lateinit var listener: NoticeDialogListener
/* The activity that creates an instance of this dialog fragment must
* implement this interface in order to receive event callbacks.
* Each method passes the DialogFragment in case the host needs to query it. */
interface NoticeDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
val fragment = getParentFragment();
listener = fragment as NoticeDialogListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NoticeDialogListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle): Dialog {
return activity?.let {
// Build the dialog and set up the button click handlers
val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.dialog_fire_missiles)
.setPositiveButton(R.string.fire,
DialogInterface.OnClickListener { dialog, id ->
// Send the positive button event back to the host activity
listener.onDialogPositiveClick(this)
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
// Send the negative button event back to the host activity
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
Fragment
class MainFragment : Fragment(),
NoticeDialogFragment.NoticeDialogListener {
fun showNoticeDialog() {
// Create an instance of the dialog fragment and show it
val dialog = NoticeDialogFragment()
dialog.show(getChildFragmentManager(), "NoticeDialogFragment")
}
// The dialog fragment receives a reference to this Activity through the
// Fragment.onAttach() callback, which it uses to call the following methods
// defined by the NoticeDialogFragment.NoticeDialogListener interface
override fun onDialogPositiveClick(dialog: DialogFragment) {
// User touched the dialog's positive button
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
}
● DialogFragmentで自前のレイアウトを適用する方法
AlertDialog.Builder#setViewを使う。
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater;
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
let view = inflater.inflate(R.layout.dialog_signin, null)
builder.setView(view)
// Add action buttons
.setPositiveButton(R.string.signin,
DialogInterface.OnClickListener { dialog, id ->
// sign in the user ...
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
getDialog().cancel()
})
view.findViewById<>(R.id.hogehoge).xxxx
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
● DialogFragmentでレイアウトの大きさが言う事を聞いてくれないンゴ orz
onResumeに MATCH_PARENTやサイズ指定を仕込む(ただし DecorViewの Windowなのでサイズ指定の時は外枠のスキマ部分も含まれる)
訳が分からない時は LayoutInspectorでレイアウトの階層を追って、サイズ指定の具合を調べると解決が楽。
override fun onResume() {
super.onResume()
// 画面の大きさを取得する場合
val displayWidth = resources.displayMetrics.widthPixels
val displayHeight = resources.displayMetrics.heightPixels
// 画面の大きさからダイアログの大きさを指定する場合
val width = (displayWidth * 0.8f).toInt()
val height = (displayHeight / 1.5f).toInt()
// ダイアログの Window(android.view.Window)の大きさを直接指定する
// val width = WindowManager.LayoutParams.MATCH_PARENT
// val height = WindowManager.LayoutParams.MATCH_PARENT
dialog?.window?.setLayout(width, height)
}
● Activityや Fragmentのインスタンスを生成する時に newInstance関数でルール付けをする方法
呼び出す側(FragmentA)でインスタンスを生成する処理を記述するのではなく、呼び出される側(FragmentB)にインスタンスを生成する処理を記述しておく。
Bundleで引数を渡す場合等で便利に記述や引数の管理ができる。
呼び出し側 FragmentAが FragmentBを呼び出す場合
FragmentA {
val fragment = FragmentB.newInstance("hogehoge")
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(R.id.fragment_container, fragment)
fragmentTransaction.commit()
}
BUNDLE_STR等のタグ用の文字列定数も privateで自分の中だけの管理で済む
class FragmentB : Fragment() {
...
companion object {
private val BUNDLE_STR = "BUNDLE_STR"
private val BUNDLE_INT = "BUNDLE_INT"
fun onCreate( ... ) {
val strValue = arguments.getString(BUNDLE_STR, "default")
val intValue = arguments.getInt(BUNDLE_INT)
...
}
fun newInstance(strValue: string, ... ): FragmentB {
return FragmentB().apply {
arguments = Bundle().apply {
putString(BUNDLE_STR, strValue)
putInt(BUNDLE_INT, intValue)
}
}
}
}
}
>> SafeArgs
●カスタムフォント Android TextView
res/font/
に下記のファイルを配置した場合
ipaexg.ttf
ipaexm.ttf
<!-- "@font/ipaexg"で IPAexゴシック -->
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/ipaexg" />
<!-- "@font/ipaexm"で IPAex明朝 -->
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/ipaexm" />
res/font/
に下記のファイルを配置した場合
ipaex.xml
ipaexg.ttf
ipaexm.ttf
ipaex.xml
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<!-- normal = IPAex明朝 -->
<font
android:fontStyle="normal"
android:fontWeight="400"
android:font="@font/ipaexm" />
<!-- italic = IPAexゴシック -->
<font
android:fontStyle="italic"
android:fontWeight="400"
android:font="@font/ipaexg" />
</font-family>
...
<!-- italic = IPAexゴシックで bold 「太字」 -->
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/ipaex"
android:textStyle="bold|italic" />
<!-- normal = IPAex明朝 -->
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/ipaex"
android:textStyle="normal" />
● Android Studioのバグ修正
Android Studioの Build Outputログの日本語の文字化けを修正する。
C:\Users\xxxx\.AndroidStudio4.0\config\studio64.exe.vmoptions
studio64.exe.vmoptions
# custom Android Studio VM options, see https://developer.android.com/studio/intro/studio-config.html
-Dfile.encoding=UTF-8
-Xmx1280m
-XX:ReservedCodeCacheSize=240m
● Build Outputの日本語の文字列が文字化けする場合の対処方法。
DataBinderMapperImpl.java:9: �G���[: �V���{�����������܂���
import com.example.test_my_application.databinding.FragmentFirstBindingImpl;
^
�V���{��: �N���X FragmentFirstBindingImpl
�ꏊ: �p�b�P�[�W com.example.test_my_application.databinding
Menu > Help > Edit Custom VM Options...
-Dfile.encoding=UTF-8
DataBinderMapperImpl.java:9: エラー: シンボルを見つけられません
import com.example.test_my_application.databinding.FragmentFirstBindingImpl;
^
シンボル: クラス FragmentFirstBindingImpl
場所: パッケージ com.example.test_my_application.databinding
● warn: multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute?
Android\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values\values.xml:630: warn: multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute?.
<string name="test_string_format">Value is %s, %d</string>
を、下記の様に書き直す
<string name="test_string_format" formatted="false">Value is %s, %d</string>
<string name="test_string_format">Value is $s, $d</string>
<string name="test_string_format">Value is %1$s, %2$d</string>
● Overriding androidx.databinding.adapters.ViewBindingAdapter#setPaddingStart
ViewBindingAdapter.java
> Task :app:kaptDebugKotlin
警告: Binding adapter AK(android.view.View, float) already exists for android:paddingStart !
Overriding androidx.databinding.adapters.ViewBindingAdapter#setPaddingStart
●
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.test_my_application/com.example.test_my_application.MainActivity}: java.lang.IllegalStateException: Required DataBindingComponent is null in class FragmentFirstBindingImpl. A BindingAdapter in com.example.test_my_application.BindingAdapters is not static and requires an object to use, retrieved from the DataBindingComponent. If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static.
Caused by: java.lang.IllegalStateException: Required DataBindingComponent is null in class FragmentFirstBindingImpl. A BindingAdapter in com.example.test_my_application.BindingAdapters is not static and requires an object to use, retrieved from the DataBindingComponent. If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static.
@JvmStatic
https://github.com/android/databinding-samples/blob/master/TwoWaySample/app/src/main/java/com/example/android/databinding/twowaysample/ui/BindingAdapters.kt
* Also, keep in mind that @JvmStatic is only necessary if you define the methods inside a class or
* object. Consider moving the Binding Adapters to the top level of the file.
● You must supply a layout_width attribute .
default=wrap_content
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.test_my_application/com.example.test_my_application.MainActivity}: android.view.InflateException: Binary XML file line #138: Binary XML file line #138: You must supply a layout_width attribute.
android:layout_width="@{test_width_float, default=wrap_content}"
● conflicts with another tag that has the same ID
<TextView id='@+id/text_view_x'> conflicts with another tag that has the same ID
Invalidate Caches/Restart
● import文を勝手に*で省略しない様にする
● Buttonに Spannableが効かない、Buttonの文字列が大文字になる、Buttonの文字列が小文字にならない
textAllCaps:textAllCaps="false"
● xmlの Stringリソースに半角空白を設定したい
<>" false S P A C EEEEE "</>
<> ABC </>
● TextView
TextView.setText Html.fromHtml("<h>HOGEHOGE</h>");
● Incompatible Kotlin/Native libraries
Incompatible Kotlin/Native libraries
There is a library libraries from the Kotlin/Native 1.3.61 distribution attached to the project: stdlib
These libraries were compiled with an older Kotlin/Native compiler and can't be read in IDE.
Please edit Gradle buildfile(s) to use Kotlin Gradle plugin version 1.4.20. Then re-import the project in IDE.
Incompatible Kotlin/Native libraries
There are 32 third-party libraries attached to the project that were compiled with an older Kotlin/Native compiler and cant be read in IDE:
Gradle: com.soywiz.korlibs.klock:klock-iosarm64:klib:1.8.6
Gradle: com.soywiz.korlibs.klock:klock-iosx64:klib:1.8.6
Please edit Gradle buildfile(s) and specify library a version compatible with Kotlin/Native 1.4.20. Then re-import the project in IDE.
● CenterVerticalSpan - MetricAffectingSpan
● Android Studio is using the following JDK location when running Gradle
JAVA_HOME
Why is there more than one Daemon process on my machine?
23:00 Android Studio is using the following JDK location when running Gradle:
C:\Android\android-studio\jre
Using different JDK locations on different processes might cause Gradle to
spawn multiple daemons, for example, by executing Gradle tasks from a terminal
while using Android Studio.
More info...
Select a JDK from the File System
Do not show this warning again
●糞重い Android Studio
ビルド速度を最適化する - Android デベロッパー
ビルド キャッシュによるクリーンビルドの高速化 - Android デベロッパー
低スペック PCだと頻繁にキャッシュが崩壊します。
rem ビルド キャッシュのクリアと gradle daemonの停止(リセット)
rem ビルド キャッシュの削除
gradlew clean cleanBuildCache
gradlew --stop
Tags: [Android開発]
●関連するコンテンツ(この記事を読んだ人は、次の記事も読んでいます)
Androidのアプリ開発で AAR形式のライブラリを Androidプロジェクトに組み込む方法
Androidプロジェクトに AAR/JAR形式のライブラリを組み込む方法のバリエーション
Android JNI NDKの C言語側で R.rawや Assetのファイルを FileDescriptor経由で直接読み込む方法
Android JNIの C言語で FileDescriptor経由でダイレクトに rawリソースを読み込む、メモリ受け渡しやダミーファイル作成が不要
Kotlin大嫌い人間が Kotlin言語を必死に勉強する
行末にセミコロン;の無い言語は大嫌い
2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!
Androidの開発で AACって何? DataBinding、LiveData、ViewModel、LifecycleObserverについて解説
Androidアプリ作成に必須の多端末に対応するデザイン方法について解説する dipを極める
Androidの開発で dipって何?密度非依存ピクセル?Density-Independent Pixels?って何者?
[HOME]
|
[BACK]
リンクフリー(連絡不要、ただしトップページ以外は Web構成の変更で移動する場合があります)
Copyright (c)
2020 FREE WING,Y.Sakamoto
Powered by 猫屋敷工房 & HTML Generator
http://www.neko.ne.jp/~freewing/android/android_aac_data_binding_view_binding/