HOME
  Security
   Software
    Hardware
  
FPGA
  CPU
   Android
    Raspberry Pi
  
nLite
  Xcode
   etc.
    ALL
  
LINK
BACK
 

2020/09/30

AACの記事から1年経ってデータ バインディングの俺的カンニング帳を作る AACの記事から1年経ってデータ バインディングの俺的カンニング帳を作る

(Android開発 データ バインディング 虎の巻 Android Studio)

Tags: [Android開発]




● 2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!

2019/10/02
Kotlin大嫌い人間が Kotlin言語を必死に勉強する
Kotlin大嫌い人間が Kotlin言語を必死に勉強する

  行末にセミコロン;の無い言語は大嫌い

2019/03/01
2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!
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  "</>
 <>&#32;ABC&#32;</>


● 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形式のライブラリを Androidプロジェクトに組み込む方法

  Androidプロジェクトに AAR/JAR形式のライブラリを組み込む方法のバリエーション

Android JNI NDKの C言語側で R.rawや Assetのファイルを FileDescriptor経由で直接読み込む方法
Android JNI NDKの C言語側で R.rawや Assetのファイルを FileDescriptor経由で直接読み込む方法

  Android JNIの C言語で FileDescriptor経由でダイレクトに rawリソースを読み込む、メモリ受け渡しやダミーファイル作成が不要

Kotlin大嫌い人間が Kotlin言語を必死に勉強する
Kotlin大嫌い人間が Kotlin言語を必死に勉強する

  行末にセミコロン;の無い言語は大嫌い

2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!
2019年になったから Android Architecture Componentsで開発しようじゃないか!今から始めても遅くない!

  Androidの開発で AACって何? DataBinding、LiveData、ViewModel、LifecycleObserverについて解説

Androidアプリ作成に必須の多端末に対応するデザイン方法について解説する dipを極める
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/