Android ObservableBoolean 不会每次都触发 View.setVisibility
Android ObservableBoolean not triggering View.setVisibility every time
我有一个旨在控制 Activity
或 Fragment
状态的 ViewModel。此 ViewModel 包含 4 个 ObservableBoolean
,它们在我的布局中用于判断哪个视图必须可见或不可见:
public class ContentLoaderViewModel extends BaseObservable {
public static final int LOADING_LAYOUT = 0;
public static final int CONTENT_LAYOUT = 1;
public static final int NO_DATA_LAYOUT = 2;
public static final int ERROR_LAYOUT = 3;
public final ObservableBoolean loading = new ObservableBoolean();
public final ObservableBoolean loaded = new ObservableBoolean();
public final ObservableBoolean noDataFound = new ObservableBoolean();
public final ObservableBoolean hasErrors = new ObservableBoolean();
@Bindable
public int displayedLayout;
public ContentLoaderViewModel() {
setDisplayedLayout(LOADING_LAYOUT);
}
public ContentLoaderViewModel(int displayedLayout) {
setDisplayedLayout(displayedLayout);
}
public int getDisplayedLayout() {
return displayedLayout;
}
public void setDisplayedLayout(int displayedLayout) {
this.displayedLayout = displayedLayout;
notifyPropertyChanged(BR.displayedLayout);
loading.set(displayedLayout == LOADING_LAYOUT);
loaded.set(displayedLayout == CONTENT_LAYOUT);
noDataFound.set(displayedLayout == NO_DATA_LAYOUT);
hasErrors.set(displayedLayout == ERROR_LAYOUT);
}
}
我在我的一个片段中使用它。此片段旨在提供一个简单的界面来显示列表。一旦创建了适配器,它就会显示一个 RecyclerView。适配器是异步创建的,然后 RecyclerView 被初始化。所以一开始 Fragment 处于 "loading" 的状态,当它接收到它的适配器时,它会变为 "content" 的状态。这是它的样子:
XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="app.viewmodels.ContentLoaderViewModel" />
<variable
name="noDataText"
type="String" />
<variable
name="bottomButtonText"
type="String" />
<variable
name="showBottomButton"
type="boolean" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@{bottomButtonText}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/bottomButton"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:visibility="@{showBottomButton ? View.VISIBLE : View.GONE}"/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}"
tools:layout_constraintTop_creator="1"
tools:layout_constraintRight_creator="1"
tools:layout_constraintBottom_creator="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="@+id/textView"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@+id/textView"
android:id="@+id/progressBar2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{noDataText}"
android:visibility="@{viewModel.noDataFound ? View.VISIBLE : View.GONE}"
android:id="@+id/textView"
tools:layout_constraintTop_creator="1"
tools:layout_constraintRight_creator="1"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="16dp"
tools:layout_constraintLeft_creator="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/progressBar2" />
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:visibility="@{viewModel.loaded ? View.VISIBLE : View.GONE}"
app:layout_constraintBottom_toTopOf="@+id/bottomButton"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<include
layout="@layout/error_layout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:isVisible="@{viewModel.hasErrors}"/>
</android.support.constraint.ConstraintLayout>
</layout>
JAVA
@FragmentWithArgs
public class BaseListFragment extends BaseFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
inflateBinding(inflater, container);
init(binding);
return binding.getRoot();
}
protected int getLayoutId() {
return R.layout.fragment_base_list;
}
protected void inflateBinding(LayoutInflater inflater, ViewGroup container) {
binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
onAttachToBinding(binding);
}
@Override
protected void init(final FragmentBaseListBinding binding) {
selectedItemPosition = -1;
restoreState();
initBinding(binding);
refresh();
}
private void initBinding(FragmentBaseListBinding binding) {
binding.setViewModel(new ContentLoaderViewModel());
binding.setNoDataText(noDataText);
binding.setBottomButtonText(bottomButtonText);
binding.setShowBottomButton(showBottomButton);
}
public void refresh() {
binding.getViewModel().setDisplayedLayout(ContentLoaderViewModel.LOADING_LAYOUT);
Observable<RecyclerView.Adapter> observableAdapter = Observable.fromCallable(new Callable<RecyclerView.Adapter>() {
@Override
public RecyclerView.Adapter call() throws Exception {
return hostAction.getAdapterAsync();
}
});
Observer<RecyclerView.Adapter> observerAdapter = new Observer<RecyclerView.Adapter>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
binding.getViewModel().setDisplayedLayout(ContentLoaderViewModel.ERROR_LAYOUT);
}
@Override
public void onNext(RecyclerView.Adapter adapter) {
initializeList(adapter);
}
};
adapterSubscription = observableAdapter
.subscribeOn((Schedulers.newThread()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observerAdapter);
}
protected void initializeList(RecyclerView.Adapter adapter) {
this.adapter = adapter;
binding.list.setLayoutManager(hostAction.getLayoutManager());
binding.list.setAdapter(getFinalAdapter());
//Select first item by default
if(selectedItemPosition >= 0 && selectedItemPosition < adapter.getItemCount())
onItemClick(binding.list, null, selectedItemPosition, 0, null);
//Set item decoration (simple line under each items)
if(!hideDecorationItem)
binding.list.addItemDecoration(new SimpleItemDecoration(getContext(), 0));
binding.getViewModel().setDisplayedLayout(adapter.getItemCount() > 0
? ContentLoaderViewModel.CONTENT_LAYOUT
: ContentLoaderViewModel.NO_DATA_LAYOUT
);
Log.d(TAG, "initializeList: " + "viewModel.displayLayout[" + binding.getViewModel().getDisplayedLayout() + "]," +
" recyclerView.visibility[" + binding.list.getVisibility() + "], adapter.count[" + adapter.getItemCount() + "]");
binding.executePendingBindings();
binding.invalidateAll();
Log.d(TAG, "initializeList: (after pendingBinding)" + "viewModel.displayLayout[" + binding.getViewModel().getDisplayedLayout() + "]," +
" recyclerView.visibility[" + binding.list.getVisibility() + "], adapter.count[" + adapter.getItemCount() + "]");
}
protected RecyclerView.Adapter getFinalAdapter() {
return getWrapAdapter(adapter);
}
@NonNull
private WrapAdapter getWrapAdapter(RecyclerView.Adapter adapter) {
WrapAdapter wrapAdapter = new WrapAdapter(adapter);
wrapAdapter.setOnItemClickListener(binding.list, BaseListFragment.this);
return wrapAdapter;
}
}
而且我很确定这是数据绑定的原因,因为
initializeList()
中的 2 条日志显示适配器有项目:
D/BaseListFragment: initializeList: viewModel.displayLayout[1], recyclerView.visibility[8], adapter.count[27]
D/BaseListFragment: initializeList: (after pendingBinding)viewModel.displayLayout[1], recyclerView.visibility[0], adapter.count[27]
(这里奇怪的是,在执行挂起绑定并使所有无效后,可见性设置为 0 -> View.VISIBLE
但 RecyclerView
仍然没有显示)
- 当我从
RecyclerView
中删除行 android:visibility="@{viewModel.loaded ? View.VISIBLE : View.GONE}"
时,它们没有问题,并且 RecyclerView
始终可见。
知道这里发生了什么吗?
更新
WrapAdapter 是来自外部库的 class:https://github.com/eyeem/RecyclerViewTools/blob/master/library/src/main/java/com/eyeem/recyclerviewtools/adapter/WrapAdapter.java
将可见性从 GONE
更改为 INVISIBLE
帮我解决了这个问题。
我刚刚将 android:visibility="@{viewModel.loaded ? View.VISIBLE : View.GONE}"
更改为 android:visibility="@{viewModel.loaded ? View.VISIBLE : View.INVISIBLE}"
。
我不需要 RecyclerView
成为 GONE
所以现在没问题,但我仍然想知道问题是什么。
如果有人了解 GONE
无法正常工作而 INVISIBLE
正常的原因,请发表评论。
我有一个旨在控制 Activity
或 Fragment
状态的 ViewModel。此 ViewModel 包含 4 个 ObservableBoolean
,它们在我的布局中用于判断哪个视图必须可见或不可见:
public class ContentLoaderViewModel extends BaseObservable {
public static final int LOADING_LAYOUT = 0;
public static final int CONTENT_LAYOUT = 1;
public static final int NO_DATA_LAYOUT = 2;
public static final int ERROR_LAYOUT = 3;
public final ObservableBoolean loading = new ObservableBoolean();
public final ObservableBoolean loaded = new ObservableBoolean();
public final ObservableBoolean noDataFound = new ObservableBoolean();
public final ObservableBoolean hasErrors = new ObservableBoolean();
@Bindable
public int displayedLayout;
public ContentLoaderViewModel() {
setDisplayedLayout(LOADING_LAYOUT);
}
public ContentLoaderViewModel(int displayedLayout) {
setDisplayedLayout(displayedLayout);
}
public int getDisplayedLayout() {
return displayedLayout;
}
public void setDisplayedLayout(int displayedLayout) {
this.displayedLayout = displayedLayout;
notifyPropertyChanged(BR.displayedLayout);
loading.set(displayedLayout == LOADING_LAYOUT);
loaded.set(displayedLayout == CONTENT_LAYOUT);
noDataFound.set(displayedLayout == NO_DATA_LAYOUT);
hasErrors.set(displayedLayout == ERROR_LAYOUT);
}
}
我在我的一个片段中使用它。此片段旨在提供一个简单的界面来显示列表。一旦创建了适配器,它就会显示一个 RecyclerView。适配器是异步创建的,然后 RecyclerView 被初始化。所以一开始 Fragment 处于 "loading" 的状态,当它接收到它的适配器时,它会变为 "content" 的状态。这是它的样子:
XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="app.viewmodels.ContentLoaderViewModel" />
<variable
name="noDataText"
type="String" />
<variable
name="bottomButtonText"
type="String" />
<variable
name="showBottomButton"
type="boolean" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@{bottomButtonText}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/bottomButton"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:visibility="@{showBottomButton ? View.VISIBLE : View.GONE}"/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}"
tools:layout_constraintTop_creator="1"
tools:layout_constraintRight_creator="1"
tools:layout_constraintBottom_creator="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="@+id/textView"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@+id/textView"
android:id="@+id/progressBar2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{noDataText}"
android:visibility="@{viewModel.noDataFound ? View.VISIBLE : View.GONE}"
android:id="@+id/textView"
tools:layout_constraintTop_creator="1"
tools:layout_constraintRight_creator="1"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="16dp"
tools:layout_constraintLeft_creator="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/progressBar2" />
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:visibility="@{viewModel.loaded ? View.VISIBLE : View.GONE}"
app:layout_constraintBottom_toTopOf="@+id/bottomButton"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<include
layout="@layout/error_layout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:isVisible="@{viewModel.hasErrors}"/>
</android.support.constraint.ConstraintLayout>
</layout>
JAVA
@FragmentWithArgs
public class BaseListFragment extends BaseFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
inflateBinding(inflater, container);
init(binding);
return binding.getRoot();
}
protected int getLayoutId() {
return R.layout.fragment_base_list;
}
protected void inflateBinding(LayoutInflater inflater, ViewGroup container) {
binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
onAttachToBinding(binding);
}
@Override
protected void init(final FragmentBaseListBinding binding) {
selectedItemPosition = -1;
restoreState();
initBinding(binding);
refresh();
}
private void initBinding(FragmentBaseListBinding binding) {
binding.setViewModel(new ContentLoaderViewModel());
binding.setNoDataText(noDataText);
binding.setBottomButtonText(bottomButtonText);
binding.setShowBottomButton(showBottomButton);
}
public void refresh() {
binding.getViewModel().setDisplayedLayout(ContentLoaderViewModel.LOADING_LAYOUT);
Observable<RecyclerView.Adapter> observableAdapter = Observable.fromCallable(new Callable<RecyclerView.Adapter>() {
@Override
public RecyclerView.Adapter call() throws Exception {
return hostAction.getAdapterAsync();
}
});
Observer<RecyclerView.Adapter> observerAdapter = new Observer<RecyclerView.Adapter>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
binding.getViewModel().setDisplayedLayout(ContentLoaderViewModel.ERROR_LAYOUT);
}
@Override
public void onNext(RecyclerView.Adapter adapter) {
initializeList(adapter);
}
};
adapterSubscription = observableAdapter
.subscribeOn((Schedulers.newThread()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observerAdapter);
}
protected void initializeList(RecyclerView.Adapter adapter) {
this.adapter = adapter;
binding.list.setLayoutManager(hostAction.getLayoutManager());
binding.list.setAdapter(getFinalAdapter());
//Select first item by default
if(selectedItemPosition >= 0 && selectedItemPosition < adapter.getItemCount())
onItemClick(binding.list, null, selectedItemPosition, 0, null);
//Set item decoration (simple line under each items)
if(!hideDecorationItem)
binding.list.addItemDecoration(new SimpleItemDecoration(getContext(), 0));
binding.getViewModel().setDisplayedLayout(adapter.getItemCount() > 0
? ContentLoaderViewModel.CONTENT_LAYOUT
: ContentLoaderViewModel.NO_DATA_LAYOUT
);
Log.d(TAG, "initializeList: " + "viewModel.displayLayout[" + binding.getViewModel().getDisplayedLayout() + "]," +
" recyclerView.visibility[" + binding.list.getVisibility() + "], adapter.count[" + adapter.getItemCount() + "]");
binding.executePendingBindings();
binding.invalidateAll();
Log.d(TAG, "initializeList: (after pendingBinding)" + "viewModel.displayLayout[" + binding.getViewModel().getDisplayedLayout() + "]," +
" recyclerView.visibility[" + binding.list.getVisibility() + "], adapter.count[" + adapter.getItemCount() + "]");
}
protected RecyclerView.Adapter getFinalAdapter() {
return getWrapAdapter(adapter);
}
@NonNull
private WrapAdapter getWrapAdapter(RecyclerView.Adapter adapter) {
WrapAdapter wrapAdapter = new WrapAdapter(adapter);
wrapAdapter.setOnItemClickListener(binding.list, BaseListFragment.this);
return wrapAdapter;
}
}
而且我很确定这是数据绑定的原因,因为
initializeList()
中的 2 条日志显示适配器有项目:
D/BaseListFragment: initializeList: viewModel.displayLayout[1], recyclerView.visibility[8], adapter.count[27]
D/BaseListFragment: initializeList: (after pendingBinding)viewModel.displayLayout[1], recyclerView.visibility[0], adapter.count[27]
(这里奇怪的是,在执行挂起绑定并使所有无效后,可见性设置为 0 -> View.VISIBLE
但 RecyclerView
仍然没有显示)
- 当我从
RecyclerView
中删除行android:visibility="@{viewModel.loaded ? View.VISIBLE : View.GONE}"
时,它们没有问题,并且RecyclerView
始终可见。
知道这里发生了什么吗?
更新
WrapAdapter 是来自外部库的 class:https://github.com/eyeem/RecyclerViewTools/blob/master/library/src/main/java/com/eyeem/recyclerviewtools/adapter/WrapAdapter.java
将可见性从 GONE
更改为 INVISIBLE
帮我解决了这个问题。
我刚刚将 android:visibility="@{viewModel.loaded ? View.VISIBLE : View.GONE}"
更改为 android:visibility="@{viewModel.loaded ? View.VISIBLE : View.INVISIBLE}"
。
我不需要 RecyclerView
成为 GONE
所以现在没问题,但我仍然想知道问题是什么。
如果有人了解 GONE
无法正常工作而 INVISIBLE
正常的原因,请发表评论。