MVVM 实战之计算器

android
DataBinding
MVVM
calculator

前些日子,一直在学习基于 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 布局中的第二个直接子元素。布局内容如下:

 <?xml version="1.0" encoding="utf-8"?>
<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="cal"
type="com.ch.wchhuangya.android.pandora.vm.CalculatorVM"/>
</data> <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
android:layout_weight="2"
android:gravity="bottom"
android:orientation="vertical"
> <TextView
android:id="@+id/cal_top_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:maxLines="1"
android:paddingRight="10dp"
android:text="@{cal.secondNum}"
android:textColor="#555"
android:textSize="35sp"
tools:text="16"
/> <TextView
android:id="@+id/cal_bottom_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:maxLines="1"
android:paddingRight="10dp"
android:text="@{cal.firstNum}"
android:textColor="#222"
android:textSize="45sp"
tools:text="+ 3234234"
/> <TextView
android:id="@+id/cal_res"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:maxLines="1"
android:paddingRight="10dp"
android:text="@{cal.resNum}"
android:textColor="#888"
android:textSize="30sp"
tools:text="= 3234250"
/> </LinearLayout> <android.support.v7.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
app:columnCount="4"
app:orientation="horizontal"
app:rowCount="5"
> <Button
android:id="@+id/cal_clear"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
app:layout_rowWeight="1"
android:text="clear"
android:onClick="@{() -> cal.clear()}"
/> <Button
android:id="@+id/cal_del"
android:layout_marginRight="5dp"
app:layout_rowWeight="1"
android:text="del"
android:onClick="@{() -> cal.del()}"
/> <Button
android:id="@+id/cal_divide"
android:layout_marginRight="5dp"
app:layout_rowWeight="1"
android:text="÷"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_multiply"
app:layout_rowWeight="1"
android:text="×"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_7"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="7"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_8"
app:layout_rowWeight="1"
android:text="8"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_9"
app:layout_rowWeight="1"
android:text="9"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_minus"
app:layout_rowWeight="1"
android:text="-"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_4"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="4"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_5"
app:layout_rowWeight="1"
android:text="5"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_6"
app:layout_rowWeight="1"
android:text="6"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_add"
app:layout_rowWeight="1"
android:text="+"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_1"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="1"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_2"
app:layout_rowWeight="1"
android:text="2"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_3"
app:layout_rowWeight="1"
android:text="3"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_equals"
app:layout_rowSpan="2"
app:layout_rowWeight="1"
app:layout_gravity="fill_vertical"
android:text="="
android:onClick="@{() -> cal.equalsClick()}"
/> <Button
android:id="@+id/cal_12"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="%"
android:onClick="@{() -> cal.percentClick()}"
/> <Button
android:id="@+id/cal_zero"
app:layout_rowWeight="1"
android:text="0"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_dot"
app:layout_rowWeight="1"
android:text="."
android:onClick="@{() -> cal.dotClick()}"
/> </android.support.v7.widget.GridLayout> </LinearLayout>
</layout>

布局文件

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

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

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

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

Fragment


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

 package com.ch.wchhuangya.android.pandora.view.activity.calculator;

 import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import com.ch.wchhuangya.android.pandora.R;
import com.ch.wchhuangya.android.pandora.databinding.CalculatorBinding;
import com.ch.wchhuangya.android.pandora.vm.CalculatorVM; /**
* Created by wchya on 2016-12-07 16:17
*/ public class CalculatorFragment extends Fragment { private CalculatorBinding mBinding;
private CalculatorVM mCalVM; @Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.calculator, container, false);
mCalVM = new CalculatorVM(getContext());
mBinding.setCal(mCalVM);
return mBinding.getRoot();
} @Override
public void onDestroy() {
super.onDestroy();
mCalVM.reset();
}
}

Fragment

该类中,只有两个方法。

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

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

ViewModel


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

 package com.ch.wchhuangya.android.pandora.vm;

 import android.content.Context;
import android.databinding.ObservableField;
import android.view.View;
import android.widget.Button; /**
* Created by wchya on 2016-12-07 16:17
*/ public class CalculatorVM extends BaseVM { /** 用于定义操作符后的空格显示 */
public static final String EMPTY_STR = " ";
/** 用于定义结果数字前的显示 */
public static final String EQUALS_EMPTY_STR = "= "; /** 被操作数 */
public ObservableField<String> firstNum = new ObservableField<>("0");
/** 上一次结果 */
public ObservableField<String> secondNum = new ObservableField<>("");
/** 当前结果 */
public ObservableField<String> resNum = new ObservableField<>(""); /** 被操作数的数值 */
double fNum;
/** 上一次结果的数值 */
double sNum;
/** 当前结果的数值 */
double rNum;
/** 标识当前是否为初始状态 */
boolean initState = true;
/** 当前运算符 */
CalOperator mCurOperator;
/** 前一运算符 */
CalOperator mPreOperator; /** 运算符枚举 */
enum CalOperator {
ADD("+"),
MINUS("-"),
MULTIPLY("×"),
DIVIDE("÷"); private String value; CalOperator(String value) {
this.value = value;
} /** 根据运算符字符串获取运算符枚举 */
public static CalOperator getOperator(String value) {
CalOperator otor = null;
for (CalOperator operator : CalOperator.values()) {
if (operator.value.equals(value))
otor = operator;
}
return otor;
}
} public CalculatorVM(Context context) {
mContext = context;
} /**
* 数字点击处理
* 当数字变化时,先变化 firstNum,然后计算结果
*/
public void numClick(View view) {
String btnVal = ((Button) view).getText().toString(); if (btnVal.equals("0")) { // 当前点击 0 按钮
if (firstNum.get().equals("0")) // 当前显示的为 0
return;
} String originalVal = firstNum.get();
boolean firstIsDigit = Character.isDigit(originalVal.charAt(0)); if (isInitState()) { // 初始状态(既刚打开页面或点击了 Clear 之后)
handleFirstNum(btnVal, Double.parseDouble(btnVal));
handleResNum(EQUALS_EMPTY_STR + btnVal, Double.parseDouble(btnVal));
} else {
if (firstIsDigit) { // 首位是数字,直接在数字后添加
String changedVal = originalVal + btnVal;
handleFirstNum(changedVal, Double.parseDouble(changedVal));
handleResNum(EQUALS_EMPTY_STR + String.valueOf(fNum), Double.parseDouble(changedVal));
} else { // 首位是运算符,计算结果后显示 if (originalVal.length() == 3 && Double.parseDouble(originalVal.substring(2)) == 0L) // 被操作数是 运算符 + 空格 + 0
handleFirstNum(mCurOperator.value + EMPTY_STR, Double.parseDouble(btnVal));
else
handleFirstNum(originalVal + btnVal, Double.parseDouble((originalVal + btnVal).substring(2))); cal();
}
}
adjustNums();
setInitState(false);
} /** 退格键事件 */
public void del() {
String first = firstNum.get();
if (secondNum.get().length() > 0) { // 正在计算 if (first.length() <= 3) { // firstNum 是运算符,把 secondNum 的值赋值给 firstNum,secondNum 清空
handleFirstNum(sNum + "", sNum);
handleResNum(EQUALS_EMPTY_STR + secondNum.get(), sNum);
handleSecondNum("", 0L);
mCurOperator = null;
} else { // 把最后一个数字删除,重新计算
String changedVal = first.substring(0, first.length() - 1);
handleFirstNum(changedVal, Double.parseDouble(changedVal.substring(2)));
cal();
}
} else { // 没有计算 if ((first.startsWith("-") && first.length() == 2) || first.length() == 1) { // 只有一位数字
setInitState(true);
handleFirstNum("0", 0L);
handleResNum("", 0L);
} else {
String changedFirst = first.substring(0, firstNum.get().length() - 1);
handleFirstNum(changedFirst, Double.parseDouble(changedFirst));
handleResNum(EQUALS_EMPTY_STR + fNum, fNum);
}
}
adjustNums();
} /** 运算符点击处理 */
public void operatorClick(View view) {
String btnVal = ((Button) view).getText().toString(); // 如果当前有运算符,并且运算符后有数字,把当前运算符赋值给前一运算符
if (mCurOperator != null && firstNum.get().length() >= 3)
mPreOperator = mCurOperator; mCurOperator = CalOperator.getOperator(btnVal); if (secondNum.get().equals("")) { // 1. 没有 secondNum,把 firstNum 赋值给 secondNum,然后把运算符赋值给 firstNum handleSecondNum(firstNum.get(), Double.parseDouble(firstNum.get()));
handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
} else { // 2. 有 secondNum
if (firstNum.get().length() == 2) { // 2.1 只有运算符时,只改变运算符显示,其它不变 firstNum.set(mCurOperator.value + EMPTY_STR);
} else { // 2.2 既有运算符,又有 firstNum 和 secondNum 时,计算结果 if (mPreOperator != null) {
mPreOperator = null; handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
handleSecondNum(rNum + "", rNum);
} else {
cal();
handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
}
}
}
setInitState(false);
adjustNums();
} /**
* 点的事件处理
* 1. 只能有一个点
* 2. 输入点后,firstNum 的值不变,只改变显示
*/
public void dotClick() {
if (firstNum.get().contains("."))
return;
else {
setInitState(false);
String val = firstNum.get(); if (!Character.isDigit(val.charAt(0)) && val.length() == 2) {
handleFirstNum(val + "0.", fNum);
} else
handleFirstNum(val + ".", fNum);
}
} /**
* 百分号的事件处理
* 1. 初始状态或刚刚经过 clear 操作时,点击无反应
* 2. 当 firstNum 为运算符时,点击无反应
* 3. 其余情况,点击后将 firstNum 乘以 0.01
*/
public void percentClick() {
String originalVal = firstNum.get();
if (isInitState())
return;
else if (originalVal.length() == 1 && !Character.isDigit(originalVal.charAt(0)))
return;
else {
fNum = fNum * 0.01;
if (mCurOperator != null) {
handleFirstNum(mCurOperator.value + " " + fNum, fNum);
cal();
} else {
handleFirstNum(String.valueOf(fNum), fNum);
handleResNum(String.valueOf(fNum), fNum);
}
}
} /**
* 等号事件处理
* 1. 只有 firstNum,不作任何处理
* 2. 有 secondNum 时,把 secondNum 和 firstNum 的值进行运算,然后把值赋值给 firstNum,清空 secondNum,
*/
public void equalsClick() {
if (!secondNum.get().equals("")) {
cal();
handleFirstNum(String.valueOf(rNum), rNum);
handleSecondNum("", 0L);
}
adjustNums();
} /** 计算结果 */
private void cal() {
switch (mCurOperator) {
case ADD:
rNum = sNum + fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
break;
case MINUS:
rNum = sNum - fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
break;
case MULTIPLY:
rNum = sNum * fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
break;
case DIVIDE:
if (fNum == 0L) {
rNum = 0L;
handleResNum("= ∞", rNum);
} else {
rNum = sNum / fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
}
break;
}
adjustNums();
} /**
* 调整结果,主要将最后无用的 .0 去掉
*/
private void adjustNums() {
String ffNum = firstNum.get();
String ssNum = secondNum.get();
String rrNum = resNum.get();
if (ffNum.endsWith(".0")) {
firstNum.set(ffNum.substring(0, ffNum.length() - 2));
}
if (ssNum.endsWith(".0")) {
secondNum.set(ssNum.substring(0, ssNum.length() - 2));
}
if (rrNum.endsWith(".0"))
resNum.set(rrNum.substring(0, rrNum.length() - 2));
} /** 将计算器恢复到初始状态 */
public void clear() {
setInitState(true); handleFirstNum("0", 0L); handleSecondNum("", 0L); handleResNum("", 0L); mCurOperator = null;
} /** 处理被操作数的显示和值 */
private void handleFirstNum(String values, double val) {
firstNum.set(values);
fNum = val;
} /** 处理上次结果的显示和值 */
private void handleSecondNum(String values, double val) {
secondNum.set(values);
sNum = val;
} /** 处理本次结果的显示和值 */
private void handleResNum(String values, double val) {
resNum.set(values);
rNum = val;
} public boolean isInitState() {
return initState;
} public void setInitState(boolean initState) {
this.initState = initState;
} @Override
public void reset() {
// 释放其它资源
mContext = null; // 取掉观察者的注册
unsubscribe();
}
}

ViewModel

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

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

 package com.ch.wchhuangya.android.pandora.vm;

 import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity; import java.util.ArrayList;
import java.util.List; import rx.Subscription; /**
* Created by wchya on 2016-11-27 20:32
*/ public abstract class BaseVM { /** VM 模式中,View 引用的持有 */
protected AppCompatActivity mActivity;
/** VM 模式中,View 引用的持有 */
protected Fragment mFragment;
/** VM 模式中,上下文引用的持有 */
protected Context mContext;
/** 所有用到的观察者 */
protected List<Subscription> mSubscriptions = new ArrayList<>(); /** 释放持有的资源引用 */
public abstract void reset(); /** 将所有注册的观察者反注册掉 */
public void unsubscribe() {
for (Subscription subscription : mSubscriptions) {
if (subscription != null && subscription.isUnsubscribed())
subscription.unsubscribe();
}
}
}

BaseVM

最终效果如下:

计算器

结束语


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

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

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

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

MVVM 实战之计算器的更多相关文章

  1. Jetpack MVVM 实战项目,附带源码+视频,收藏!

    从读者的反馈来看,近期大部分安卓开发已跳出舒适圈,开始尝试认识和应用 Jetpack MVVM 到实际的项目开发中. 只可惜,关于 Jetpack MVVM,网上多是 东拼西凑.人云亦云.通篇贴代码  ...

  2. C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点

    本文介绍在 MVVM 中,如何用 ViewModel 控制焦点. 这焦点设置个东西嘛,有些争论.就是到底要不要用 ViewModel 来控制视图的键盘输入焦点.这里不讨论,假设你就是要通过 VM,设置 ...

  3. C# WPF MVVM 实战 – 4 - 善用 IValueConverter

    IValueConverter,做 WPF 的都应该接触过,把值换成 Visibility .Margin 等等是最常见的例子,也有很多很好的博文解释过用法.本文只是解释一下,MVVM 中一些情景. ...

  4. Electron 实战桌面计算器应用

    前言 Electron 是一个搭建跨平台桌面应用的框架,仅仅使用 JavaScript.HTML 以及 CSS,即可快速而容易地搭建一个原生应用.这对于想要涉及其他领域的开发者来说是一个非常大的福利. ...

  5. Android 开发笔记___初级控件之实战__计算器

    功能简单,实现并不难,对于初学者可以总和了解初级控件的基本使用. 用到的知识点如下: 线性布局 LinearLayout:整体界面是从上往下的,因此需要垂直方向的linearlayout:下面每行四个 ...

  6. AvalonJS+MVVM实战部分源码

    轻量级前端MVVM框架avalon,它兼容到 IE6 (其他MVVM框架,KnockoutJS(IE6), AngularJS(IE9), EmberJS(IE8), WinJS(IE9) ),它可以 ...

  7. python小实例——tkinter实战(计算器)

    一.完美计算器实验一 import tkinter import math import tkinter.messagebox class calculator: #界面布局方法 def __init ...

  8. MVVM实战

    1.层次依赖 - (UIViewController *)createInitialViewController { self.viewModelServices = [RWTViewModelSer ...

  9. Windows Phone 十一、MVVM模式

    MVVM 模式介绍 模型-视图-视图模型 (MVVM) 是一种用来分离 UI 和非 UI 代码的应用设计模式 MVVM – 模型(Model) MVVM 中的 Model 与 MVC 中的一致,用于封 ...

随机推荐

  1. arithmetic-slices

    https://leetcode.com/problems/arithmetic-slices/ public class Solution { public int numberOfArithmet ...

  2. DX12

    CD3DX12_DESCRIPTOR_RANGE1 的baseShaderRegister 用来指定 t0 t1 b0 b1...的index t0 srv b0 constant buffer u0 ...

  3. 在Windows Server 2008 R2上安装Exchange 2013过程中遇到的一些问题

    笔者对Exchange经验非常有限, 但也正因为如此, 这里分享的东西对从没接触过Exchange的朋友会有更多的帮助吧, 至少希望如此.   1. Exchange 2013的安装需要.net fr ...

  4. 【DataStrcutre】Introduction and description of Binary Trees

    [Definitions] Here is the recursive definition of a binary tree: A binary tree is either the empty s ...

  5. [Functional Programming ADT] Adapt Redux Actions/Reducers for Use with the State ADT

    By using the State ADT to define how our application state transitions over time, we clear up the ne ...

  6. set_exception_handler 自定义异常处理

    该函数用于创建运行时期间的用户自己的异常处理方法. set_exception_handler(error_function) 参数 必需.规定未捕获的异常发生时调用的函数. 该函数必须在调用 set ...

  7. 一步一步学Spring.NET——1、Spring.NET环境准备

    Spring.NET 1.3.2下载地址:http://down.51cto.com/data/861700 下载后解压 Spring.NET-1.3.2.7z:这个里面有我们须要用到的全部东西. S ...

  8. LoadRunner测试ajaxweb程序攻略

    用loadrunner测试WEB程序的时候总是会碰到AJAX或者ActiveX实现的功能,而通常这些功能会包含很多客户端函数(一般为JavaScript).我们该如何处理?如果从功能实现的角度去考虑这 ...

  9. 使用caffe的HDF5数据完毕回归任务

    一直在研究怎样用caffe做行人检測问题.然而參考那些经典结构比方faster-rcnn等,都是自己定义的caffe层来完毕的检測任务. 这些都要求对caffe框架有一定程度的了解.近期看到了怎样用c ...

  10. CentOS 6.4 编译安装 gcc 4.8.1(转)

    今天在isocpp上看到“GCC 4.8.1 released, C++11 feature complete”这个消息,非常兴奋.终于有一个全面支持C++11语言特性的编译器了! 当然了,gcc仅仅 ...