项目背景
拿来练手的小项目,一开始觉得毛毛雨啦~~~真正动起手来却发现有很多细节值得深究,先po上开发环境。
- 操作系统:win10
- JDK:1.8
- SDK:
- 开发工具:Android Studio 2.2
布局
关于计算器的布局向来有多种实现方法,比较主流的是LinearLayout、GridLayout 和 TableLayout。
LinearLayout
LinearLayout是最原始的实现方法,布局效果和层级结构如下:
这里为Button equal设置了较大margin是因为一旦为按钮设置了背景色就会失去按钮原先的圆角,排列会比较拥挤。实现布局的要点有三个:
- 可以通过LinearLayout的层级嵌套实现跨行
- 通过父组件的 android:weightSum 和 子组件的 android:layout_weight 属性实现跨列
- 使用 android:layout_weight 属性时,如果是对竖直方向分割,组件的 android:layout_width 属性值设为0dp ,水平方向同理,这样可以均匀分配屏幕空间以自适应屏幕尺寸,且每一格的大小不受组件自身的大小影响
缺点
代码比较复杂,嵌套太多有时会有性能问题
GridLayout
Gridlayout是Android 4.0之后出现的,通过行和列将空间划分成若干单元格,每个单元格的最小尺寸是其中组件自身的尺寸,且会与同一行/列里最长的单元格自动对齐。这样的特性使其不能很好地适应不同屏幕的分辨率,会出现不能均匀地占满空间或者超出屏幕边界的问题。
在网上看到GridLayout 和 LinearLayout使用的API差不多,然后我就想用LinearLayout的方式来解决屏幕适配问题,即将子控件的宽高设为0dp,然后通过android:layout_columWeight 和 android:layout_rowWeight 属性来分配屏幕空间。效果如下,但是Android 7.0以前的版本并不支持这种写法。
小伙伴们如果有gridlayout优化的解决方案,还请不吝赐教,拜托拜托~~~
TableLayout
TableLayout是继承自LinearLayout的,所以也可以通过android:WeightSum属性和android:layout_weight 属性来解决屏幕适配问题,但与LinearLayout不同的是TableLayout 的子组件不用将宽高设为0dp,因为TableLayout子组件的默认宽高是wrap_content且无法更改,这一点也简化了代码。
缺点:只能用android:layout_span 属性跨列,无法实现跨行
综上,基于实现效果的考虑,最后我选择了LinearLayout来布局
逻辑实现
将输入拼接成字符串
- 定义一个String全局变量 last 来存放输入的字符串,为 0~9 和 clear、delete、point 按钮添加 buildNumber 方法,用来处理输入的拼接;
- clear 是清除键,按下此键后,程序里所有与数据及操作有关的全局变量都应重置为0或“0”
- delete是清除键,String.subString(int start,int end) 方法可以用来截取字符串,我们这里从0截取到 length()-1 就好了。另外,last只剩下一个字符时继续delete要将last置为“0”,否则last的值会为null
- 处理0~9的时候只要判断last是否为“0”,如果是,那就覆盖掉,如果不是,就直接在后边拼接。
- point的处理不仅要注意4中的问题,还有就是判断last里是否已经含有“.“,只有在不含有”.“的last后边才可以拼接
- 因为0~9的处理逻辑是相似的,所以可以考虑合并在一起,我是用把接收到的View类型变量向下强制转换成Button类型来获取要拼接的数字
哒哒哒哒,po代码:
public void buildNumber(View v){
switch(v.getId()){
case R.id.clear:
factorFirst=new Double(0.0);
factorSecond=new Double(0.0);
factor=new Double(0.0);
flag=0;
last="0";
break;
case R.id.delete:
if(last.length()==1)
last="0";
else
last=last.substring(0,last.length()-1);
break;
case R.id.number0:case R.id.number1:case R.id.number2:case R.id.number3:
case R.id.number4:case R.id.number5:case R.id.number6:case R.id.number7:
case R.id.number8:case R.id.number9:
if(last.equals("0"))
last=((Button)v).getText().toString();
else
last+=((Button)v).getText().toString();
break;
case R.id.point:
if(last.indexOf(".")<0)
last+=".";
break;
}
mResultText.setText(last);
}
操作符响应
- 定义Double类型的全局变量factor、factorFirst、factorSecond,分别用来存放计算结果、第一个操作数和第二个操作数,之所以使用double的包装类是因为将来factor要作为setText()的参数传进去,必须将数值转化成String类型
- 为+、-、×、÷四个Button添加calculate方法,用来处理操作符的响应
- 使用包装类的Double.pareDouble(String), 将输入转化为第一个操作数,清空输入历史
- 定义一个Int全局变量来保存时那种操作符
代码如下:
public void calculate(View v) {
factorFirst = Double.parseDouble(last);
last = "0";
switch (v.getId()) {
case R.id.add:
flag = 1;
break;
case R.id.minus:
flag = 2;
break;
case R.id.multiply:
flag = 3;
break;
case R.id.divide:
flag = 4;
break;
}
}
计算结果——等号的响应
- 将新的输入结果转化成数值存储在factorSecond变量里,清零输入记录
- 根据flag的数值进行运算得出结果,注意,处理除法运算时要判断factorSecond的值是否为0,如果为0要给出警告
- 显示factor,如果factor的值为0,要将显示设为”0“,否则显示结果会为”0.0“
- 清零所有操作记录
public void equal(View v){
factorSecond=Double.parseDouble(last);
last="0";
switch (flag){
case 1:
factor=factorFirst+factorSecond;
break;
case 2:
factor=factorFirst-factorSecond;
break;
case 3:
factor=factorFirst*factorSecond;
break;
case 4:
if(factorSecond!=0)
factor=factorFirst/factorSecond;
else {
Toast.makeText(this, "除数不能为0!", Toast.LENGTH_LONG).show();
break;
}
break;
}
if(factor==0)
mResultText.setText("0");
else
mResultText.setText(factor.toString());
clear();
}
至此,一个完整、简单的计算器就实现了,但是还不能连续操作,有待改进。