zoukankan      html  css  js  c++  java
  • MVVM 实战之计算器

    MVVM 实战之计算器

     

     

    前些日子,一直在学习基于 RxAndroid + Retrofit + DataBinding 技术组合的 MVVM 解决方案。初识这些知识,深深被它们的巧妙构思和方便快捷所吸引,心中颇为激动。但是,“纸上得来终觉浅,绝知此事要躬行”,学习完以后心里还是没有谱,于是,决定自己动手做一个基于这些技术和框架的小应用。

    既然是对新技术学习和掌握的练习,因此,摊子不宜铺的太大。经过思量,最终决定使用 DataBinding 技术构建一个小的 MVVM 应用。MVVM 就是 Model-View-ViewModel 的缩写,与 MVC 模式相比,把其中的 Control 更换为 ViewModel 了。MVVM 的特点:ModelView 之间完全没有直接的联系,但是,通过 ViewModelModel 的变化可以反映在 View 上,对 View 操作呢,又可以影响到 Model

    平时在编写 Android 应用时,大家都在深受 findViewById 的折磨。DataBinding 还有个好处,就是完全不需要使用 findViewById 来获取控件(当然,需要在布局文件中给控件设置 id 属性)。有了 DataBinding 的支持,在数据变化后,也不需使用代码来改变控件的显示了。这样,我们的代码就清爽多了。

    Model


    MVVM 中,Model 的变化可以直接反映到 View 上,而不需要通过代码进行设置。这样,就不能用普通的 Java 类型的变量了。Android 专门为这种变量定义了新的变量类型:ObservableXXX

    注意:ObservableXXX 是在 android.databinding 包下

    变量定义如下:

    /** 被操作数 */
    public ObservableField<String> firstNum = new ObservableField<>("0");
    /** 上一次结果 */
    public ObservableField<String> secondNum = new ObservableField<>("");
    /** 当前结果 */
    public ObservableField<String> resNum = new ObservableField<>("");
    

      

    变量的定义位置应该在 ViewModel 中,后方会有完整代码。

    View

    布局文件


    DataBinding 的布局特点是把正常布局包裹在 layout 节点中,layout 布局中的第一个子直接子元素必须是 data 节点。因为,计算器布局的特点非常符合网格布局的特点,因此,我们选择 GridLayout 控件作为 layout 布局中的第二个直接子元素。布局内容如下:

      1 <?xml version="1.0" encoding="utf-8"?>
      2 <layout xmlns:android="http://schemas.android.com/apk/res/android"
      3         xmlns:tools="http://schemas.android.com/tools"
      4         xmlns:app="http://schemas.android.com/apk/res-auto">
      5     <data>
      6         <variable
      7             name="cal"
      8             type="com.ch.wchhuangya.android.pandora.vm.CalculatorVM"/>
      9     </data>
     10 
     11     <LinearLayout
     12                   android:layout_width="match_parent"
     13                   android:layout_height="match_parent"
     14                   android:orientation="vertical">
     15 
     16         <LinearLayout
     17             android:layout_width="match_parent"
     18             android:layout_height="0dp"
     19             android:layout_marginBottom="10dp"
     20             android:layout_weight="2"
     21             android:gravity="bottom"
     22             android:orientation="vertical"
     23             >
     24 
     25             <TextView
     26                 android:id="@+id/cal_top_num"
     27                 android:layout_width="match_parent"
     28                 android:layout_height="wrap_content"
     29                 android:gravity="right"
     30                 android:maxLines="1"
     31                 android:paddingRight="10dp"
     32                 android:text="@{cal.secondNum}"
     33                 android:textColor="#555"
     34                 android:textSize="35sp"
     35                 tools:text="16"
     36                 />
     37 
     38             <TextView
     39                 android:id="@+id/cal_bottom_num"
     40                 android:layout_width="match_parent"
     41                 android:layout_height="wrap_content"
     42                 android:gravity="right"
     43                 android:maxLines="1"
     44                 android:paddingRight="10dp"
     45                 android:text="@{cal.firstNum}"
     46                 android:textColor="#222"
     47                 android:textSize="45sp"
     48                 tools:text="+ 3234234"
     49                 />
     50 
     51             <TextView
     52                 android:id="@+id/cal_res"
     53                 android:layout_width="match_parent"
     54                 android:layout_height="wrap_content"
     55                 android:gravity="right"
     56                 android:maxLines="1"
     57                 android:paddingRight="10dp"
     58                 android:text="@{cal.resNum}"
     59                 android:textColor="#888"
     60                 android:textSize="30sp"
     61                 tools:text="= 3234250"
     62                 />
     63 
     64         </LinearLayout>
     65 
     66         <android.support.v7.widget.GridLayout
     67             android:layout_width="match_parent"
     68             android:layout_height="0dp"
     69             android:layout_weight="3"
     70             app:columnCount="4"
     71             app:orientation="horizontal"
     72             app:rowCount="5"
     73             >
     74 
     75             <Button
     76                 android:id="@+id/cal_clear"
     77                 android:layout_marginLeft="5dp"
     78                 android:layout_marginRight="5dp"
     79                 app:layout_rowWeight="1"
     80                 android:text="clear"
     81                 android:onClick="@{() -> cal.clear()}"
     82                 />
     83 
     84             <Button
     85                 android:id="@+id/cal_del"
     86                 android:layout_marginRight="5dp"
     87                 app:layout_rowWeight="1"
     88                 android:text="del"
     89                 android:onClick="@{() -> cal.del()}"
     90                 />
     91 
     92             <Button
     93                 android:id="@+id/cal_divide"
     94                 android:layout_marginRight="5dp"
     95                 app:layout_rowWeight="1"
     96                 android:text="÷"
     97                 android:onClick="@{cal::operatorClick}"
     98                 />
     99 
    100             <Button
    101                 android:id="@+id/cal_multiply"
    102                 app:layout_rowWeight="1"
    103                 android:text="×"
    104                 android:onClick="@{cal::operatorClick}"
    105                 />
    106 
    107             <Button
    108                 android:id="@+id/cal_7"
    109                 android:layout_marginLeft="5dp"
    110                 app:layout_rowWeight="1"
    111                 android:text="7"
    112                 android:onClick="@{cal::numClick}"
    113                 />
    114 
    115             <Button
    116                 android:id="@+id/cal_8"
    117                 app:layout_rowWeight="1"
    118                 android:text="8"
    119                 android:onClick="@{cal::numClick}"
    120                 />
    121 
    122             <Button
    123                 android:id="@+id/cal_9"
    124                 app:layout_rowWeight="1"
    125                 android:text="9"
    126                 android:onClick="@{cal::numClick}"
    127                 />
    128 
    129             <Button
    130                 android:id="@+id/cal_minus"
    131                 app:layout_rowWeight="1"
    132                 android:text="-"
    133                 android:onClick="@{cal::operatorClick}"
    134                 />
    135 
    136             <Button
    137                 android:id="@+id/cal_4"
    138                 android:layout_marginLeft="5dp"
    139                 app:layout_rowWeight="1"
    140                 android:text="4"
    141                 android:onClick="@{cal::numClick}"
    142                 />
    143 
    144             <Button
    145                 android:id="@+id/cal_5"
    146                 app:layout_rowWeight="1"
    147                 android:text="5"
    148                 android:onClick="@{cal::numClick}"
    149                 />
    150 
    151             <Button
    152                 android:id="@+id/cal_6"
    153                 app:layout_rowWeight="1"
    154                 android:text="6"
    155                 android:onClick="@{cal::numClick}"
    156                 />
    157 
    158             <Button
    159                 android:id="@+id/cal_add"
    160                 app:layout_rowWeight="1"
    161                 android:text="+"
    162                 android:onClick="@{cal::operatorClick}"
    163                 />
    164 
    165             <Button
    166                 android:id="@+id/cal_1"
    167                 android:layout_marginLeft="5dp"
    168                 app:layout_rowWeight="1"
    169                 android:text="1"
    170                 android:onClick="@{cal::numClick}"
    171                 />
    172 
    173             <Button
    174                 android:id="@+id/cal_2"
    175                 app:layout_rowWeight="1"
    176                 android:text="2"
    177                 android:onClick="@{cal::numClick}"
    178                 />
    179 
    180             <Button
    181                 android:id="@+id/cal_3"
    182                 app:layout_rowWeight="1"
    183                 android:text="3"
    184                 android:onClick="@{cal::numClick}"
    185                 />
    186 
    187             <Button
    188                 android:id="@+id/cal_equals"
    189                 app:layout_rowSpan="2"
    190                 app:layout_rowWeight="1"
    191                 app:layout_gravity="fill_vertical"
    192                 android:text="="
    193                 android:onClick="@{() -> cal.equalsClick()}"
    194                 />
    195 
    196             <Button
    197                 android:id="@+id/cal_12"
    198                 android:layout_marginLeft="5dp"
    199                 app:layout_rowWeight="1"
    200                 android:text="%"
    201                 android:onClick="@{() -> cal.percentClick()}"
    202                 />
    203 
    204             <Button
    205                 android:id="@+id/cal_zero"
    206                 app:layout_rowWeight="1"
    207                 android:text="0"
    208                 android:onClick="@{cal::numClick}"
    209                 />
    210 
    211             <Button
    212                 android:id="@+id/cal_dot"
    213                 app:layout_rowWeight="1"
    214                 android:text="."
    215                 android:onClick="@{() -> cal.dotClick()}"
    216                 />
    217 
    218         </android.support.v7.widget.GridLayout>
    219 
    220     </LinearLayout>
    221 </layout>
    布局文件

     

    布局内容比较简单,下面,只说一些重点:

    1. DataBinding 的布局中,如果需要使用 tools 标签,它的声明必须放在 layout 节点上。否则,布局预览中没有效果

    2. data 节点中申明的是布局文件各元素需要使用到的对象,也可以为对象定义别名

    3. 布局文件中的控件如果要使用 data 中定义的对象,值的类似于:@{View.VISIBLE} 。控件的属性值中,不仅可以使用对象,还能使用对象的方法

    Fragment


    MVVM 中,ActivityFragment 的作用只是用于控件的初始化,包括控件属性(如颜色)等的设置。因此,它的代码灰常简单,具体如下: 

     1 package com.ch.wchhuangya.android.pandora.view.activity.calculator;
     2 
     3 import android.databinding.DataBindingUtil;
     4 import android.os.Bundle;
     5 import android.support.annotation.Nullable;
     6 import android.support.v4.app.Fragment;
     7 import android.view.LayoutInflater;
     8 import android.view.View;
     9 import android.view.ViewGroup;
    10 
    11 import com.ch.wchhuangya.android.pandora.R;
    12 import com.ch.wchhuangya.android.pandora.databinding.CalculatorBinding;
    13 import com.ch.wchhuangya.android.pandora.vm.CalculatorVM;
    14 
    15 /**
    16  * Created by wchya on 2016-12-07 16:17
    17  */
    18 
    19 public class CalculatorFragment extends Fragment {
    20 
    21     private CalculatorBinding mBinding;
    22     private CalculatorVM mCalVM;
    23 
    24     @Nullable
    25     @Override
    26     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    27         mBinding = DataBindingUtil.inflate(inflater, R.layout.calculator, container, false);
    28         mCalVM = new CalculatorVM(getContext());
    29         mBinding.setCal(mCalVM);
    30         return mBinding.getRoot();
    31     }
    32 
    33     @Override
    34     public void onDestroy() {
    35         super.onDestroy();
    36         mCalVM.reset();
    37     }
    38 }
    Fragment

    该类中,只有两个方法。

    onCreateView 方法用于返回视图,返回的方法与平时使用的 Fragment 略有不同。平时用 View.inflate 方法获取视图并返回,在 DataBinding 下,使用 DataBindingUtil.inflate 方法返回 ViewBinding 对象,然后给该对象对应的布局文件中的变量赋值。

    onDestory() 方法中调用了两个释放资源的方法,这两个方法是在 ViewModel 中声明的。

    ViewModel


    MVVM 中,ViewModel 是重头,它用于处理所有非 UI 的业务逻辑。对于计算器来说,业务逻辑就是数字、符号的输入,数字运算等。具体内容如下:

      1 package com.ch.wchhuangya.android.pandora.vm;
      2 
      3 import android.content.Context;
      4 import android.databinding.ObservableField;
      5 import android.view.View;
      6 import android.widget.Button;
      7 
      8 /**
      9  * Created by wchya on 2016-12-07 16:17
     10  */
     11 
     12 public class CalculatorVM extends BaseVM {
     13 
     14     /** 用于定义操作符后的空格显示 */
     15     public static final String EMPTY_STR = " ";
     16     /** 用于定义结果数字前的显示 */
     17     public static final String EQUALS_EMPTY_STR = "= ";
     18 
     19     /** 被操作数 */
     20     public ObservableField<String> firstNum = new ObservableField<>("0");
     21     /** 上一次结果 */
     22     public ObservableField<String> secondNum = new ObservableField<>("");
     23     /** 当前结果 */
     24     public ObservableField<String> resNum = new ObservableField<>("");
     25 
     26     /** 被操作数的数值 */
     27     double fNum;
     28     /** 上一次结果的数值 */
     29     double sNum;
     30     /** 当前结果的数值 */
     31     double rNum;
     32     /** 标识当前是否为初始状态 */
     33     boolean initState = true;
     34     /** 当前运算符 */
     35     CalOperator mCurOperator;
     36     /** 前一运算符 */
     37     CalOperator mPreOperator;
     38 
     39     /** 运算符枚举 */
     40     enum CalOperator {
     41         ADD("+"),
     42         MINUS("-"),
     43         MULTIPLY("×"),
     44         DIVIDE("÷");
     45 
     46         private String value;
     47 
     48         CalOperator(String value) {
     49             this.value = value;
     50         }
     51 
     52         /** 根据运算符字符串获取运算符枚举 */
     53         public static CalOperator getOperator(String value) {
     54             CalOperator otor = null;
     55             for (CalOperator operator : CalOperator.values()) {
     56                 if (operator.value.equals(value))
     57                     otor = operator;
     58             }
     59             return otor;
     60         }
     61     }
     62 
     63     public CalculatorVM(Context context) {
     64         mContext = context;
     65     }
     66 
     67     /**
     68      * 数字点击处理 
     69      * 当数字变化时,先变化 firstNum,然后计算结果
     70      */
     71     public void numClick(View view) {
     72         String btnVal = ((Button) view).getText().toString();
     73 
     74         if (btnVal.equals("0")) { // 当前点击 0 按钮
     75             if (firstNum.get().equals("0")) // 当前显示的为 0
     76                 return;
     77         }
     78 
     79         String originalVal = firstNum.get();
     80         boolean firstIsDigit = Character.isDigit(originalVal.charAt(0));
     81 
     82         if (isInitState()) { // 初始状态(既刚打开页面或点击了 Clear 之后)
     83             handleFirstNum(btnVal, Double.parseDouble(btnVal));
     84             handleResNum(EQUALS_EMPTY_STR + btnVal, Double.parseDouble(btnVal));
     85         } else {
     86             if (firstIsDigit) { // 首位是数字,直接在数字后添加
     87                 String changedVal = originalVal + btnVal;
     88                 handleFirstNum(changedVal, Double.parseDouble(changedVal));
     89                 handleResNum(EQUALS_EMPTY_STR + String.valueOf(fNum), Double.parseDouble(changedVal));
     90             } else { // 首位是运算符,计算结果后显示
     91 
     92                 if (originalVal.length() == 3 && Double.parseDouble(originalVal.substring(2)) == 0L) // 被操作数是 运算符 + 空格 + 0
     93                     handleFirstNum(mCurOperator.value + EMPTY_STR, Double.parseDouble(btnVal));
     94                 else
     95                     handleFirstNum(originalVal + btnVal, Double.parseDouble((originalVal + btnVal).substring(2)));
     96 
     97                 cal();
     98             }
     99         }
    100         adjustNums();
    101         setInitState(false);
    102     }
    103 
    104     /** 退格键事件 */
    105     public void del() {
    106         String first = firstNum.get();
    107         if (secondNum.get().length() > 0) { // 正在计算
    108 
    109             if (first.length() <= 3) { // firstNum 是运算符,把 secondNum 的值赋值给 firstNum,secondNum 清空
    110                 handleFirstNum(sNum + "", sNum);
    111                 handleResNum(EQUALS_EMPTY_STR + secondNum.get(), sNum);
    112                 handleSecondNum("", 0L);
    113                 mCurOperator = null;
    114             } else { // 把最后一个数字删除,重新计算
    115                 String changedVal = first.substring(0, first.length() - 1);
    116                 handleFirstNum(changedVal, Double.parseDouble(changedVal.substring(2)));
    117                 cal();
    118             }
    119         } else { // 没有计算
    120 
    121             if ((first.startsWith("-") && first.length() == 2) || first.length() == 1) { // 只有一位数字
    122                 setInitState(true);
    123                 handleFirstNum("0", 0L);
    124                 handleResNum("", 0L);
    125             } else {
    126                 String changedFirst = first.substring(0, firstNum.get().length() - 1);
    127                 handleFirstNum(changedFirst, Double.parseDouble(changedFirst));
    128                 handleResNum(EQUALS_EMPTY_STR + fNum, fNum);
    129             }
    130         }
    131         adjustNums();
    132     }
    133 
    134     /** 运算符点击处理 */
    135     public void operatorClick(View view) {
    136         String btnVal = ((Button) view).getText().toString();
    137 
    138         // 如果当前有运算符,并且运算符后有数字,把当前运算符赋值给前一运算符
    139         if (mCurOperator != null && firstNum.get().length() >= 3)
    140             mPreOperator = mCurOperator;
    141 
    142         mCurOperator = CalOperator.getOperator(btnVal);
    143 
    144         if (secondNum.get().equals("")) { // 1. 没有 secondNum,把 firstNum 赋值给 secondNum,然后把运算符赋值给 firstNum
    145 
    146             handleSecondNum(firstNum.get(), Double.parseDouble(firstNum.get()));
    147             handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
    148         } else { // 2. 有 secondNum
    149             if (firstNum.get().length() == 2) { // 2.1 只有运算符时,只改变运算符显示,其它不变
    150 
    151                 firstNum.set(mCurOperator.value + EMPTY_STR);
    152             } else { // 2.2 既有运算符,又有 firstNum 和 secondNum 时,计算结果
    153 
    154                 if (mPreOperator != null) {
    155                     mPreOperator = null;
    156 
    157                     handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
    158                     handleSecondNum(rNum + "", rNum);
    159                 } else {
    160                     cal();
    161                     handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
    162                 }
    163             }
    164         }
    165         setInitState(false);
    166         adjustNums();
    167     }
    168 
    169     /**
    170      * 点的事件处理
    171      * 1. 只能有一个点
    172      * 2. 输入点后,firstNum 的值不变,只改变显示
    173      */
    174     public void dotClick() {
    175         if (firstNum.get().contains("."))
    176             return;
    177         else {
    178             setInitState(false);
    179             String val = firstNum.get();
    180 
    181             if (!Character.isDigit(val.charAt(0)) && val.length() == 2) {
    182                 handleFirstNum(val + "0.", fNum);
    183             } else
    184                 handleFirstNum(val + ".", fNum);
    185         }
    186     }
    187 
    188     /**
    189      * 百分号的事件处理
    190      * 1. 初始状态或刚刚经过 clear 操作时,点击无反应
    191      * 2. 当 firstNum 为运算符时,点击无反应
    192      * 3. 其余情况,点击后将 firstNum 乘以 0.01
    193      */
    194     public void percentClick() {
    195         String originalVal = firstNum.get();
    196         if (isInitState())
    197             return;
    198         else if (originalVal.length() == 1 && !Character.isDigit(originalVal.charAt(0)))
    199                 return;
    200         else {
    201             fNum = fNum * 0.01;
    202             if (mCurOperator != null) {
    203                 handleFirstNum(mCurOperator.value + " " + fNum, fNum);
    204                 cal();
    205             } else {
    206                 handleFirstNum(String.valueOf(fNum), fNum);
    207                 handleResNum(String.valueOf(fNum), fNum);
    208             }
    209         }
    210     }
    211 
    212     /**
    213      * 等号事件处理
    214      * 1. 只有 firstNum,不作任何处理
    215      * 2. 有 secondNum 时,把 secondNum 和 firstNum 的值进行运算,然后把值赋值给 firstNum,清空 secondNum,
    216      */
    217     public void equalsClick() {
    218         if (!secondNum.get().equals("")) {
    219             cal();
    220             handleFirstNum(String.valueOf(rNum), rNum);
    221             handleSecondNum("", 0L);
    222         }
    223         adjustNums();
    224     }
    225 
    226     /** 计算结果 */
    227     private void cal() {
    228         switch (mCurOperator) {
    229             case ADD:
    230                 rNum = sNum + fNum;
    231                 handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
    232                 break;
    233             case MINUS:
    234                 rNum = sNum - fNum;
    235                 handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
    236                 break;
    237             case MULTIPLY:
    238                 rNum = sNum * fNum;
    239                 handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
    240                 break;
    241             case DIVIDE:
    242                 if (fNum == 0L) {
    243                     rNum = 0L;
    244                     handleResNum("= ∞", rNum);
    245                 } else {
    246                     rNum = sNum / fNum;
    247                     handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
    248                 }
    249                 break;
    250         }
    251         adjustNums();
    252     }
    253 
    254     /**
    255      * 调整结果,主要将最后无用的 .0 去掉
    256      */
    257     private void adjustNums() {
    258         String ffNum = firstNum.get();
    259         String ssNum = secondNum.get();
    260         String rrNum = resNum.get();
    261         if (ffNum.endsWith(".0")) {
    262             firstNum.set(ffNum.substring(0, ffNum.length() - 2));
    263         }
    264         if (ssNum.endsWith(".0")) {
    265             secondNum.set(ssNum.substring(0, ssNum.length() - 2));
    266         }
    267         if (rrNum.endsWith(".0"))
    268             resNum.set(rrNum.substring(0, rrNum.length() - 2));
    269     }
    270 
    271     /** 将计算器恢复到初始状态 */
    272     public void clear() {
    273         setInitState(true);
    274 
    275         handleFirstNum("0", 0L);
    276 
    277         handleSecondNum("", 0L);
    278 
    279         handleResNum("", 0L);
    280 
    281         mCurOperator = null;
    282     }
    283 
    284     /** 处理被操作数的显示和值 */
    285     private void handleFirstNum(String values, double val) {
    286         firstNum.set(values);
    287         fNum = val;
    288     }
    289 
    290     /** 处理上次结果的显示和值 */
    291     private void handleSecondNum(String values, double val) {
    292         secondNum.set(values);
    293         sNum = val;
    294     }
    295 
    296     /** 处理本次结果的显示和值 */
    297     private void handleResNum(String values, double val) {
    298         resNum.set(values);
    299         rNum = val;
    300     }
    301 
    302     public boolean isInitState() {
    303         return initState;
    304     }
    305 
    306     public void setInitState(boolean initState) {
    307         this.initState = initState;
    308     }
    309 
    310     @Override
    311     public void reset() {
    312         // 释放其它资源
    313         mContext = null;
    314 
    315         // 取掉观察者的注册
    316         unsubscribe();
    317     }
    318 }
    ViewModel

    要注意的是:ObservableXXX 变量值的获取方法为—— variable.get(),设置方法为:variable.set(xxx)

    该类有一个父类:BaseVM, 它用于定义一些通用的变量和子类必须实现的抽象方法。内容如下:

     1 package com.ch.wchhuangya.android.pandora.vm;
     2 
     3 import android.content.Context;
     4 import android.support.v4.app.Fragment;
     5 import android.support.v7.app.AppCompatActivity;
     6 
     7 import java.util.ArrayList;
     8 import java.util.List;
     9 
    10 import rx.Subscription;
    11 
    12 /**
    13  * Created by wchya on 2016-11-27 20:32
    14  */
    15 
    16 public abstract class BaseVM {
    17 
    18     /** VM 模式中,View 引用的持有 */
    19     protected AppCompatActivity mActivity;
    20     /** VM 模式中,View 引用的持有 */
    21     protected Fragment mFragment;
    22     /** VM 模式中,上下文引用的持有 */
    23     protected Context mContext;
    24     /** 所有用到的观察者 */
    25     protected List<Subscription> mSubscriptions = new ArrayList<>();
    26 
    27     /** 释放持有的资源引用 */
    28     public abstract void reset();
    29 
    30     /** 将所有注册的观察者反注册掉 */
    31     public void unsubscribe() {
    32         for (Subscription subscription : mSubscriptions) {
    33             if (subscription != null && subscription.isUnsubscribed())
    34                 subscription.unsubscribe();
    35         }
    36     }
    37 }
    BaseVM

    最终效果如下:

     


    计算器

     

    结束语


    本文只是借助计算器这个小应用,把所学的 DataBindingMVVM 的知识使用在实际当中。文中主要使用了 Google 官方 DataBinding 的一些特性,比如为控件设置属性值,为控件绑定事件等。如果读者对这一块内容还不了解,请在官网上查找相关文档进行学习,地址:https://developer.android.com/topic/libraries/data-binding/index.html

    笔者在学习时,对官方文档进行了翻译,如果大家对英文文档比较抗拒,可以尝试看一下我的翻译。因为本人能力有限,难免出现错误,欢迎大家用评论的方式告知于我,翻译文档的地址:http://www.cnblogs.com/wchhuangya/p/6031934.html

    该应用只是实现了计算器的基本功能,功能不够完善,而且,还有一些缺陷。已知的缺陷有:1. 双精度位数的处理;2. 特别大、特别小数字的显示及处理;这些缺陷只是计算器算法处理上的缺陷,与本文的主题无关,有兴趣的朋友可以将其修改、完善。记着,改好后记得告诉我哦!

    路漫漫其修远兮,吾将上下而求索。此话与诸君共勉之!

  • 相关阅读:
    频率计数器简介
    电力时钟厂家简介
    linux mail命令详解
    /etc/postfix下 main.cf 配置文件详解
    linux tar
    Linux find命令详解
    Python with
    Python 多进程概述
    python paramiko模块简介
    python 全局变量和局部变量
  • 原文地址:https://www.cnblogs.com/wchhuangya/p/6156168.html
Copyright © 2011-2022 走看看