RoboMVVM 小费计算器






4.92/5 (8投票s)
使用 RoboMVVM(一款用于 Android 的 MVVM 框架)实现的小费计算器。
引言
RoboMVVM 是一个开源的 MVVM 框架,适用于 Android。MVVM 模式利用 数据绑定 来保持任意组件的任意数据同步。熟悉 .NET 世界的人可以体会到 MVVM 模式在简化 UI 应用程序的构建、测试和重构方面的能力。
RoboMVVM 将为您提供快速设置视图和数据模型之间绑定的工具,从而节省您宝贵的时间。
这是关于在其应用程序中使用此库的系列教程的第一篇。
安装
由于 RoboMVVM 处于快速开发阶段,我们目前不提供预编译的存储库。这在不久的将来可能会改变。在此之前,只需从 Github 这里克隆存储库,并在 Android Studio 中引用它。
我们不在 Eclipse 中开发 RoboMVVM。但是,您应该可以通过引用源代码在 Eclipse 中手动设置项目。RoboMVVM 是纯粹的 Java 代码,没有任何魔法。因此,在不同的环境中设置它应该像复制源文件并创建一个新项目一样简单。
TipCalc
TipCalc 的灵感来自于 MCCMCross(一款 C# 的 MVVM 库)的类似教程。您可以在 这里找到它。 TipCalc 允许您根据小计金额和 0% 到 100% 之间的慷慨度来计算小费。小费的计算公式为:小费 = 小计 * 慷慨度 / 100。
本教程的源代码可以在 Github 存储库 这里找到。请务必克隆存储库并进行尝试,以便在学习本教程的过程中更好地理解 RoboMVVM。
设置您的 ViewModel
ViewModel 由属性和操作组成。我们的模型包含三个属性:小计金额、慷慨度值和计算出的小费。ViewModel 必须继承自 ViewModel 类。ViewModel 还使用 @SetLayout 属性,该属性设置要为此 ViewModel 使用的布局 XML。
您可以 在这里查看此类完整的代码。
@SetLayout(R.layout.tipcalc_layout)
public class TipCalcViewModel extends ViewModel {
public TipCalcViewModel(Context context) {
super(context);
}
}
我们首先在 TipCalcViewModel 类中定义这些属性。
public float getSubTotal() {
return subTotal;
}
public void setSubTotal(float subTotal) {
this.subTotal = subTotal;
raisePropertyChangeEvent("subTotal");
recalculate();
}
public float getGenerosity() {
return generosity;
}
public void setGenerosity(float generosity) {
this.generosity = generosity;
raisePropertyChangeEvent("generosity");
recalculate();
}
public float getTip() {
return tip;
}
public void setTip(float tip) {
this.tip = tip;
raisePropertyChangeEvent("tip");
}
属性由 setter 和 getter 定义。例如,要定义 subTotal 属性,我们创建 getter/setter 对 float getSubTotal() 和 void setSubtotal(float subTotal)。
属性的 setter 包含一个 raisePropertyChangeEvent 调用,该调用会通知属性已更改。每当设置 subtotal 或 generosity 属性时,我们还会调用 recalculate 函数,该函数会设置 tip 的值。recalculate 函数仅根据 subTotal 和 generosity 值设置 tip 的值:
private void recalculate() {
setTip(getSubTotal() * getGenerosity() / 100);
}
创建您的视图
视图的创建方式与在布局 XML 中通常创建视图的方式相同。此布局称为 tipcalc_layout.xml,并通过 @SetLayout 属性从 TipCalcViewModel 引用。
布局基本上由一个用于小计金额的 EditText、一个用于慷慨度的 SeekBar 和一个用于最终计算出的小费的 TextView 组成。我们还有一个 TextView 用于显示 SeekBar 设置的慷慨度值。
相关视图具有我们将用于设置绑定的 ID。最终视图如下所示:
此布局的 XML 非常简单,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:text="Subtotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<EditText
android:id="@+id/subtotal_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="12" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Generosity"
android:layout_marginTop="20dp"
android:layout_marginBottom="12dp"
android:layout_weight="20"/>
<TextView
android:id="@+id/generosity_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10"
android:layout_marginTop="20dp"
android:layout_marginBottom="12dp"
android:layout_weight="0.5"/>
</LinearLayout>
<SeekBar
android:id="@+id/generosity_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:max="10000"
android:progress="1000"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tip"
android:layout_marginTop="20dp"
android:layout_marginBottom="12dp"
android:layout_weight="20"/>
<TextView
android:id="@+id/tip_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:layout_marginTop="20dp"
android:layout_marginBottom="12dp"
android:layout_weight="0.5"/>
</LinearLayout>
</LinearLayout>
将 ViewModel 绑定到视图
绑定由 bindProperty 调用组成,这些调用将 ViewModel 属性绑定到 View 属性。它需要 ViewModel 属性名称、View ID 和 View 属性名称。它还可以选择性地接受 ValueConverter 来转换 ViewModel 和 View 之间的值,以及指定绑定方向的 BindMode。
当未提及 BindMode 时,绑定模式始终被视为 BindMode.SOURCE_TO_TARGET,它反映了 ViewModel 对 View 的更改,反之亦然。您还可以使用 BindMode.TARGET_TO_SOURCE 和 BindMode.BIDIRECTIONAL。
在我们的例子中,我们需要为 EditText 和 SeekBar 使用双向绑定,为 TextViews 使用源到目标绑定。还需要使用 ValueConverters,因为 EditText 的 text 属性是 String,而 Seekbar 的 progress 属性是 int。我们需要将它们转换为 float。此外,我们需要将慷慨度和小费的 float 值转换为各自 TextView 所期望的 string 值。
绑定代码 必须在 ViewModel 基类中提供的已重写的 bind() 函数中编写。如下所示:
@Override
protected void bind() {
bindProperty("subTotal", R.id.subtotal_edit_text, "text", new ValueConverter() {
@Override
public Object convertToTarget(Object value) {
return value.toString();
}
@Override
public Object convertToSource(Object value) {
try {
return Float.parseFloat((String) value);
} catch(NumberFormatException e) {
return 0;
}
}
}, BindMode.BIDIRECTIONAL);
bindProperty("generosity", R.id.generosity_seek_bar, "progress", new ValueConverter() {
@Override
public Object convertToTarget(Object value) {
float floatVal = (Float) value;
int ret = (int)(floatVal * 100);
return ret;
}
@Override
public Object convertToSource(Object value) {
int intVal = (Integer) value;
float ret = ((float)intVal) / 100f;
return ret;
}
}, BindMode.BIDIRECTIONAL);
bindProperty("generosity", R.id.generosity_text_view, "text",
new TypedValueConverter(Float.class, String.class), BindMode.SOURCE_TO_TARGET);
bindProperty("tip", R.id.tip_text_view, "text", new TypedValueConverter(Float.class, String.class), BindMode.SOURCE_TO_TARGET);
}
提供了一个 TypedValueConverter 类,该类尝试在任何两种类型之间进行转换。它可以转换可以相互转换的任何类型。它还可以通过调用 toString() 函数将任何类型转换为 string。这对于我们将 TextView 绑定所需的 float -> string 转换非常有用。
设置 Activity
业务逻辑和视图绑定都由 ViewModel 处理。因此,Activity 类可以很简单。我们只需要通过调用 setContentView 将与 ViewModel 对应的视图分配给 Activity 的根即可。代码如下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new TipCalcViewModel(this).createView());
}
}
结论
希望我向您展示了使用 RoboMVVM 创建 UI 应用程序并将其绑定到数据的便捷性。请继续关注更多教程,并关注存储库以了解 RoboMVVM 的最新发展。