Java 在 Android 中使用 Observable
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/42983101/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Observable in Android
提问by Kaushik NP
I want to implement a Navigation View
with many fragments that depend totally on a value defined in the MainActivity
. I know that variables in MainActivity can be accessed using method defined in MainActivity from other Fragments to get the value, but the catch here is that the value of the variable in MainActivity may change(which runs on an AsyncThread). Now, I either change the code such that my Fragments update their valuebased on some event in the fragment itselfor use SharedPreference
. But I don't want to use SharedPreferences, neither have to check for change in the value unnecessarily many times.
我想实现一个Navigation View
包含许多完全依赖于MainActivity
. 我知道可以使用 MainActivity 中定义的方法从其他 Fragment访问 MainActivity 中的变量以获取 value,但这里的问题是 MainActivity 中变量的值可能会更改(在AsyncThread上运行)。现在,我要么更改代码,以便我的片段根据片段本身中的某个事件更新它们的值,要么使用SharedPreference
. 但我不想使用 SharedPreferences,也不需要多次检查值的变化。
I know in RxJS, we use Observablethat runs Asynchronously and works in a fashion similar to a conveyor belt. A bit of googling through the official docs : Observableconfirmed my suspicion of something similar being available in Android, but couldn't find any proper Tutorial or explanation on how to implement it. So, am looking for a simple code snippet that might give clarity to how Observable works in Android and if it is Async and if its based similar to RxJS implementation of it. (No, I don't want RxJS implementation)
我知道在 RxJS 中,我们使用异步运行的Observable并以类似于传送带的方式工作。谷歌搜索官方文档:Observable证实了我对 Android 中可用的类似内容的怀疑,但找不到任何正确的教程或关于如何实现它的解释。所以,我正在寻找一个简单的代码片段,它可以清楚地说明 Observable 在 Android 中的工作方式,以及它是否是异步的,以及它是否基于类似于它的 RxJS 实现。(不,我不想要 RxJS 实现)
Test Case:
测试用例:
MainActivity : int a, b (need observable for both variables)
Frag1 : int a1 , b1, a1changed(),b1changed()
Frag2 : int a2 , b2, a2Changed(), b2changed()
MainActivity contains integers whose value when changed should reflect in corresponding integers across the Fragments and calls separate function for each Fragment on the change being noticed.
MainActivity 包含整数,其值在更改时应反映在 Fragment 中的相应整数中,并在注意到更改时为每个 Fragment 调用单独的函数。
回答by Benjamin Mesing
Reactive is not part of Android but you are probably looking for this library: https://github.com/JakeWharton/RxBinding
Reactive 不是 Android 的一部分,但您可能正在寻找这个库:https: //github.com/JakeWharton/RxBinding
The landing page is missing an introductory example, so you have to look at the javadoc. This post should give you a good start: How to create an Observable from OnClick Event Android?Here is the code sample from Matt to get you started
登录页面缺少介绍性示例,因此您必须查看 javadoc。这篇文章应该给你一个良好的开端:How to create an Observable from OnClick Event Android? 这是来自 Matt 的代码示例,可帮助您入门
RxView.clicks(myButton)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
/* do something */
}
});
回答by Ga?tan M.
Here is a simple example with an Activity
and a single Fragment
but it will be the same with other fragments.
这是一个简单的例子,带有一个Activity
和一个,Fragment
但它与其他片段相同。
First you need to create a class standing for the value you want to observe, in your case it's a simple int
so create a class containing this int
and that extends Observable
(it implements Serializable
to simplify exchange between activity and fragment):
首先,您需要创建一个代表要观察的值的类,在您的情况下,它很简单,int
因此创建一个包含 thisint
和 that extends的类Observable
(它实现Serializable
以简化活动和片段之间的交换):
...
import java.util.Observable;
public class ObservableInteger extends Observable implements Serializable {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
this.setChanged();
this.notifyObservers(value);
}
}
Then use this observable int in an activity (activity layout contains a Button
and a FrameLayout
used to display a Fragment
):
然后在活动中使用这个 observable int(活动布局包含 aButton
和 aFrameLayout
用于显示 a Fragment
):
public class MainActivity extends Activity {
private ObservableInteger a;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create observable int
a = new ObservableInteger();
// Set a default value to it
a.setValue(0);
// Create frag1
Frag1 frag1 = new Frag1();
Bundle args = new Bundle();
// Put observable int (That why ObservableInteger implements Serializable)
args.putSerializable(Frag1.PARAM, a);
frag1.setArguments(args);
// Add frag1 on screen
getFragmentManager().beginTransaction().add(R.id.container, frag1).commit();
// Add a button to change value of a dynamically
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Set a new value in a
a.setValue(a.getValue() + 1);
}
});
}
}
Finally, create a Fragment
that listen a value change:
最后,创建一个Fragment
监听值变化的:
...
import java.util.Observer;
public class Frag1 extends Fragment {
public static final String PARAM = "param";
private ObservableInteger a1;
private Observer a1Changed = new Observer() {
@Override
public void update(Observable o, Object newValue) {
// a1 changed! (aka a changed)
// newValue is the observable int value (it's the same as a1.getValue())
Log.d(Frag1.class.getSimpleName(), "a1 has changed, new value:"+ (int) newValue);
}
};
public Frag1() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
// Get ObservableInteger created in activity
a1 = (ObservableInteger) getArguments().getSerializable(PARAM);
// Add listener for value change
a1.addObserver(a1Changed);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
I try to name my variables the same as yours, I hope it will help you.
我尝试将我的变量命名为与您相同的名称,希望对您有所帮助。
回答by Think Twice Code Once
There is an good example about using Observable of Android (java.util.Observable)here:https://andhradroid.wordpress.com/2012/04/05/object-observer-pattern-in-android/
这里有一个关于使用 Android Observable (java.util.Observable)的好例子:https : //andhradroid.wordpress.com/2012/04/05/object-observer-pattern-in-android/
And another example about using Observer pattern in Java:http://www.journaldev.com/1739/observer-design-pattern-in-java.
另一个关于在 Java 中使用观察者模式的例子:http : //www.journaldev.com/1739/observer-design-pattern-in-java。
Generally, there are two ways:
一般有两种方式:
- Use java.util.Observable.
- Design your own Observable(more flexible, help us understand more deeply).
- 使用java.util.Observable。
- 设计自己的Observable(更灵活,帮助我们更深入的理解)。
I like the second way more, for example: (Sorry, I want to make sure it works so I make a complete example)
我更喜欢第二种方式,例如:(对不起,我想确保它有效,所以我做了一个完整的例子)
The Observable:
可观察的:
public interface MyObservable {
void addObserver(MyObserver myObserver);
void removeObserver(MyObserver myObserver);
void notifyObserversAboutA();
void notifyObserversAboutB();
}
The Observer:
观察员:
public interface MyObserver {
void onAChange(int newValue);
void onBChange(int newValue);
}
The MainActivity:
主要活动:
public class MainActivity extends AppCompatActivity implements MyObservable {
private List<MyObserver> myObservers;
private int a, b;
private EditText etA;
private EditText etB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myObservers = new ArrayList<>();
ViewPager vpContent = (ViewPager) findViewById(R.id.activity_main_vp_content);
etA = (EditText) findViewById(R.id.et_a);
etB = (EditText) findViewById(R.id.et_b);
Button btnOk = (Button) findViewById(R.id.btn_ok);
//add fragments to viewpager
List<Fragment> fragments = new ArrayList<>();
Fragment1 fragment1 = new Fragment1();
addObserver(fragment1);
Fragment2 fragment2 = new Fragment2();
addObserver(fragment2);
fragments.add(fragment1);
fragments.add(fragment2);
MyFragmentPagerAdapter fragmentPagerAdapter
= new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments);
vpContent.setAdapter(fragmentPagerAdapter);
btnOk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String a = etA.getText().toString().trim();
String b = etB.getText().toString().trim();
if (!a.equals("") && !b.equals("")) {
setA(Integer.parseInt(a));
setB(Integer.parseInt(b));
}
}
});
}
private void setA(int value) {
a = value;
notifyObserversAboutA();
}
private void setB(int value) {
b = value;
notifyObserversAboutB();
}
@Override
public void addObserver(MyObserver myObserver) {
myObservers.add(myObserver);
}
@Override
public void removeObserver(MyObserver myObserver) {
myObservers.remove(myObserver);
}
@Override
public void notifyObserversAboutA() {
for (MyObserver observer : myObservers) {
observer.onAChange(this.a);
}
}
@Override
public void notifyObserversAboutB() {
for (MyObserver observer : myObservers) {
observer.onBChange(this.b);
}
}
}
The Fragment1:
片段1:
public class Fragment1 extends Fragment implements MyObserver {
private TextView tvA;
private TextView tvB;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_basic, container, false);
tvA = (TextView) contentView.findViewById(R.id.tv_a);
tvB = (TextView) contentView.findViewById(R.id.tv_b);
return contentView;
}
@Override
public void onAChange(int newValue) {
tvA.setText(String.valueOf("New value of a: " + newValue));
}
@Override
public void onBChange(int newValue) {
tvB.setText(String.valueOf("New value of b: " + newValue));
}
}
The Fragment2:
片段2:
public class Fragment2 extends Fragment implements MyObserver {
private TextView tvA;
private TextView tvB;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_basic, container, false);
tvA = (TextView) contentView.findViewById(R.id.tv_a);
tvB = (TextView) contentView.findViewById(R.id.tv_b);
return contentView;
}
@Override
public void onAChange(int newValue) {
tvA.setText(String.valueOf("New value of a: " + newValue));
}
@Override
public void onBChange(int newValue) {
tvB.setText(String.valueOf("New value of b: " + newValue));
}
}
The Adapter:
适配器:
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public MyFragmentPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
}
The main layout activity_main.xml:
主要布局activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="codeonce.thinktwice.testobserverpattern.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/linearLayout">
<EditText
android:id="@+id/et_a"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:inputType="number"
android:hint="Type value for a"/>
<EditText
android:id="@+id/et_b"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:inputType="number"
android:hint="Type value for b"/>
<Button
android:id="@+id/btn_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_gravity="center_horizontal"
android:text="OK"/>
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="@+id/activity_main_vp_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/linearLayout"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true">
</android.support.v4.view.ViewPager>
</RelativeLayout>
The fragment layout fragment_basic.xml :
片段布局 fragment_basic.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:layout_marginTop="20dp"
android:id="@+id/tv_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Value of a will appear here"
android:textSize="20sp"/>
<TextView
android:id="@+id/tv_b"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Value of b will appear here"
android:textSize="20sp"/>
</LinearLayout>
回答by Benjamin Mesing
Android now offers ViewModels with LiveData for your purpose. A view model is bound to an object with a lifecycle (Fragment, Activity) and lives as long as this object. In your case you would create a view model bound to the actvity which is accessible by all fragments.
Android 现在为您提供带有 LiveData 的 ViewModel。视图模型绑定到具有生命周期的对象(Fragment、Activity),并且与该对象的生命周期一样长。在您的情况下,您将创建一个绑定到所有片段都可以访问的活动的视图模型。
From the Android documentation:
It's very common that two or more fragments in an activity need to communicate with each other. Imagine a common case of master-detail fragments, where you have a fragment in which the user selects an item from a list and another fragment that displays the contents of the selected item. This case is never trivial as both fragments need to define some interface description, and the owner activity must bind the two together. In addition, both fragments must handle the scenario where the other fragment is not yet created or visible.
This common pain point can be addressed by using ViewModel objects. These fragments can share a ViewModel using their activity scope to handle this communication
一个活动中的两个或多个片段需要相互通信是很常见的。想象一下主从片段的常见情况,其中有一个片段,其中用户从列表中选择一个项目,另一个片段显示所选项目的内容。这种情况绝非易事,因为两个片段都需要定义一些接口描述,并且所有者活动必须将两者绑定在一起。此外,两个片段都必须处理另一个片段尚未创建或不可见的情况。
这个常见的痛点可以通过使用 ViewModel 对象来解决。这些片段可以使用其活动范围共享一个 ViewModel 来处理此通信
To use a view model for sharing data between fragments you need to:
要使用视图模型在片段之间共享数据,您需要:
- create a view model which inherits from ViewModel() and contains MutableLiveData-fields
- access the view model from the fragments by calling ViewModelProviders.of(activity).get(YourViewModel::class.java)
- change values of the view model by calling setValue for the MutableLiveData-fields
- register on changes of the MutableLiveData-fields by calling .observe()
- 创建一个从 ViewModel() 继承并包含 MutableLiveData 字段的视图模型
- 通过调用 ViewModelProviders.of(activity).get(YourViewModel::class.java) 从片段访问视图模型
- 通过为 MutableLiveData 字段调用 setValue 来更改视图模型的值
- 通过调用 .observe() 注册 MutableLiveData 字段的更改
Here is the central code snippet from the Android documentationon how to use ViewModels for a master-detail-view:
以下是Android 文档中有关如何将 ViewModel 用于主从视图的中心代码片段:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}