Привязка данных Android с пользовательским представлением

В руководстве по привязке данных Android обсуждаются значения привязки внутри действия или фрагмента, но есть ли способ выполнить привязку данных с помощью пользовательского представления?

Я хотел бы сделать что-то вроде:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.mypath.MyCustomView
        android:id="@+id/my_view"
        android:layout_width="match_parent"
        android:layout_height="40dp"/>

</LinearLayout>

с my_custom_view.xml:

<layout>

<data>
    <variable
        name="myViewModel"
        type="com.mypath.MyViewModelObject" />
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{myViewModel.myText}" />

</LinearLayout>

</layout>

Хотя представляется возможным сделать это, установив пользовательские атрибуты в настраиваемом представлении, это быстро станет громоздким, если будет много значений для привязки.

Есть ли хороший способ выполнить то, что я пытаюсь сделать?

9 ответов

Решение

В вашем пользовательском представлении, раздувайте макет, как обычно, и предоставьте установщик для атрибута, который вы хотите установить:

private MyCustomViewBinding mBinding;
public MyCustomView(...) {
    ...
    LayoutInflater inflater = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mBinding = MyCustomViewBinding.inflate(inflater);
}

public void setMyViewModel(MyViewModelObject obj) {
    mBinding.setMyViewModel(obj);
}

Затем в макете вы используете его в:

<layout xmlns...>
    <data>
        <variable
            name="myViewModel"
            type="com.mypath.MyViewModelObject" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.mypath.MyCustomView
            android:id="@+id/my_view"
            app:myViewModel="@{myViewModel}"
            android:layout_width="match_parent"
            android:layout_height="40dp"/>

    </LinearLayout>
</layout>

Выше был создан атрибут автоматической привязки для app:myViewModel, поскольку существует установщик с именем setMyViewModel.

Связывание данных работает даже при слиянии, только родительский элемент должен быть "this" и прикреплен к родительскому значению true.

binding = DataBindingUtil.inflate(inflater, R.layout.view_toolbar, this, true)

Во-первых, не делайте этого, если это пользовательское представление уже <include> в другом макете, например, активности и т. д. Вы просто получите исключение из-за того, что тег является неожиданным значением. Привязка данных уже запустила привязку, так что все готово.

Вы пытались использовать onFinishInflate запустить связку? (Пример Котлина)

override fun onFinishInflate() {
    super.onFinishInflate()
    this.dataBinding = MyCustomBinding.bind(this)
}

Имейте в виду, что если вы используете связывание в своем представлении, оно не сможет быть создано программно, по крайней мере, оно будет очень сложным, чтобы поддерживать оба, даже если вы можете.

Следуя решению, представленному Джорджем, графический редактор в Android Studio больше не мог отображать пользовательский вид. Причина в том, что в следующем коде на самом деле нет представления.

public MyCustomView(...) {
    ...
    LayoutInflater inflater = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mBinding = MyCustomViewBinding.inflate(inflater);
}

Я предполагаю, что привязка обрабатывает инфляцию, однако графический редактор не понравился.

В моем конкретном случае использования я хотел связать одно поле, а не всю модель представления. Я придумал (входящий котлин):

class LikeButton @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

    val layout: ConstraintLayout = LayoutInflater.from(context).inflate(R.layout.like_button, this, true) as ConstraintLayout

    var numberOfLikes: Int = 0
      set(value) {
          field = value
          layout.number_of_likes_tv.text = numberOfLikes.toString()
      }
}

Кнопка "Мне нравится" состоит из изображения и текстового представления. Текстовое представление содержит количество лайков, которые я хочу установить с помощью привязки данных.

Используя установщик для numberOfLikes в качестве атрибута в следующем XML-файле, привязка данных автоматически устанавливает связь:

<views.LikeButton
  android:id="@+id/like_btn"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:numberOfLikes="@{story.numberOfLikes}" />

Дополнительная информация: https://medium.com/google-developers/android-data-binding-custom-setters-55a25a7aea47

Сегодня я хочу использовать dataBinding в своем классе Custom View. Но я не знаю, как создать привязку данных к моему классу. поэтому я ищу ответ на Stackru. Сначала я пытаюсь ответить:

LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
BottomBarItemCustomViewBinding binding = BottomBarItemCustomViewBinding.inflate(inflater);

но я обнаружил, что это не работает для моего кода

поэтому я меняю другой метод:

LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
BottomBarItemCustomViewBinding binding = DataBindingUtil.inflate(inflater, R.layout.bottom_bar_item_custom_view, this, true);

У меня это работает.

полный код: bottom_bar_item_custom_view.xml

<data>

    <variable
        name="contentText"
        type="String" />

    <variable
        name="iconResource"
        type="int" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center">

    <ImageView
        android:id="@+id/bottomBarItemIconIv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginTop="2dp"
        android:src="@{iconResource}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/bottomBarItemContentTv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="@{contentText}"
        android:textColor="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bottomBarItemIconIv" />


</androidx.constraintlayout.widget.ConstraintLayout>

BottomBarItemCustomView.java

public class BottomBarItemCustomView extends ConstraintLayout {

public BottomBarItemCustomView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
    //use dataBinding on custom view.
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    BottomBarItemCustomViewBinding binding = DataBindingUtil.inflate(inflater, R.layout.bottom_bar_item_custom_view, this, true);

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BottomBarItemCustomView);
    int iconResourceId = typedArray.getResourceId(R.styleable.BottomBarItemCustomView_bottomBarIconResource, R.drawable.my_account_icon);
    binding.setIconResource(iconResourceId);

    String contentString = typedArray.getString(R.styleable.BottomBarItemCustomView_bottomBarContentText);
    if (contentString != null) {
        binding.setContentText(contentString);
    }

    typedArray.recycle();
}

надеюсь пригодится вам!

В Kotlin мы можем напрямую использовать ViewBinding:

      class BenefitView(context: Context, attrs: AttributeSet) : ConstraintLayout(context, attrs) {

    init {
        val binding = BenefitViewBinding.inflate(LayoutInflater.from(context), this, true)
        val attributes = context.obtainStyledAttributes(attrs, R.styleable.BenefitView)
        binding.image.setImageDrawable(attributes.getDrawable(R.styleable.BenefitView_image))
        binding.caption.text = attributes.getString(R.styleable.BenefitView_text)
        attributes.recycle()

    }
}

Я столкнулся с той же проблемой, когда пытался добавить дочерние представления в LinearLayout внутри моего хост-фрагмента (вложенный фрагмент UI/UX)

вот мое решение

var binding: LayoutAddGatewayBinding? = null
binding = DataBindingUtil.inflate(layoutInflater, R.layout.layout_add_gateway,
            mBinding?.root as ViewGroup?, false)
binding?.lifecycleOwner=this
val nameLiveData = MutableLiveData<String>()
nameLiveData.value="INTIAL VALUE"
binding?.text=nameLiveData

Вот mBinding это дочерний фрагмент ViewDataBinding объект и я использовал nameLiveData для двусторонней привязки данных

Исходя из того, что вы делаете, я пытался сделать MyCustomBinding.bind(this) и это больше не работает для меня. Привязка уже состоялась, но вместо этого мы ее находим.

private MyCustomBinding binding;

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();

    binding = DataBindingUtil.getBinding(this); //got the binding!
    MyViewModelObject model = MyViewModelObject.get("myText");

    binding.setVariable(BR.myViewModel, model); 
}

Здесь уже есть несколько хороших ответов, но я хотел предложить то, что я считаю самым простым.

Создайте свой собственный элемент управления с тегами макета, окружающими его, как и любой другой макет. Смотрите следующую панель инструментов, например. это используется в каждом из классов деятельности

<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">

<data>
    <variable name="YACustomPrefs" type="com.appstudio35.yourappstudio.models.YACustomPreference" />
</data>

<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/YATheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:popupTheme="@style/YATheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

</android.support.design.widget.CoordinatorLayout>

Теперь этот пользовательский макет является дочерним элементом каждого действия. Вы просто рассматриваете это как таковое в настройке привязки onCreate.

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    binding.yaCustomPrefs = YACustomPreference.getInstance(this)
    binding.toolbarMain?.yaCustomPrefs = YACustomPreference.getInstance(this)
    binding.navHeader?.yaCustomPrefs = YACustomPreference.getInstance(this)

    binding.activity = this
    binding.iBindingRecyclerView = this
    binding.navHeader?.activity = this

    //local pointer for notify txt badge
    txtNotificationCountBadge = txtNotificationCount

    //setup notify if returned from background so we can refresh the drawer items
    AppLifeCycleTracker.getInstance().addAppToForegroundListener(this)

    setupFilterableCategories()
    setupNavigationDrawer()
}

Обратите внимание, что я устанавливаю контент для детей одновременно с родителем, и все это делается с помощью доступа к точечной нотации. Пока файлы окружены тегами макета и вы назвали их, это легко сделать.

Теперь, если у пользовательского класса есть своя собственная привязка кода, тогда он может легко сделать свою собственную привязку в своем onCreate или конструкторе, но вы получите картину. Если у вас есть свой собственный класс, просто добавьте следующее в конструктор, чтобы он соответствовал названному классу привязки. Он соответствует соглашению об именах файлов макета в паскале, поэтому его легко найти и автоматически заполнить.

    LayoutInflater inflater = (LayoutInflater)
    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBinding = NameOfCustomControlBinding.inflate(inflater);

Надеюсь, это поможет.

Другие вопросы по тегам