Привязка данных 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);
Надеюсь, это поможет.