[关闭]
@stepbystep 2015-01-28T10:20:35.000000Z 字数 11063 阅读 4830

Android平台上的精美计算器实现

android


项目简述

该项目是在有了Android基础之后,再开展的一个小项目-标准型计算器。主要是基于第三方包IKExpression2.1.2.jar实现了四则混合运算的简易表达式。项目需要了解的知识点如下:

真机效果

说明: 因为模拟器效果不能完全展现出来,这里用本人的手机真实截图。界面的色调风格如果你喜欢,可以在之后的实验中自行修改。

项目进入后页面如下

输入一个表达式的效果,注意,可以用括号哟

继续输入的时候,前面的表达式会自动变成灰色

输入错误的时候,会有错误信息提示

实验楼项目重现

现在我们开始在实验楼的环境下进行实验。

新建工程

首先,我们新建工程Calculator,

选择一个API版本

选中BlankActivity,点击next

输入工程进入的Activity为CalculatorActivity

进入工程后切换到Project视图

添加第三方库

首先点击此处下载第三方IKExpression库,或者在浏览器输入http://7u2m19.com1.z0.glb.clouddn.com/IKExpression2.1.2.jar地址下载,如下图:

打开下载成功后的目录,赋值文件

粘贴到工程的app/libs目录下

不需要重新修改名字,点击确定就好

右键该库,选择Add as Library...,在弹出的对话框中也点击确定即可

添加和修改资源文件

添加资源文件的方法
资源文件实际上就是以.xml格式结尾的文件。当然在android stuido中可以右键新建文件。如下图

添加res/values/colors.xml文件,修改内容如下

  1. <!--普通按钮文字颜色-->
  2. <resources>
  3. <color name="buttom_color">#4B0082</color>
  4. </resources>

添加res/drawable/selector_button.xml文件,修改内容如下
该文件为普通按钮的背景选择器,设置按下效果

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <!--selector 背景选择器, 每一个item对应一种状态,从上至下扫描,如果满足则选择该item作为背景
  4. 主要用来处理按下的效果,获取获取焦掉与离开焦点的效果
  5. 正常情况下 state_pressed="false" 且 state_focused="false",此时应选择最后一个item(此item无特殊要求)
  6. 当控件被按下或者获取焦点的时候,就会匹配到第一个item,颜色就会变深,从而得到了按下的效果。
  7. 当然每个item可以有其他的属性,这里的radius就是圆角的意思。
  8. -->
  9. <item android:state_pressed="true"><shape>
  10. <corners android:radius="5dp"/>
  11. <solid android:color="#EE3B3B"></solid>
  12. </shape></item>
  13. <item android:state_focused="true"><shape>
  14. <corners android:radius="5dp"/>
  15. <solid android:color="#EE3B3B"></solid>
  16. </shape></item>
  17. <item><shape>
  18. <corners android:radius="5dp"/>
  19. <solid android:color="#EE9572"></solid>
  20. </shape></item>
  21. </selector>

添加res/drawable/selector_button_backspace.xml文件,修改内容如下
该文件为退格键的按下效果文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <item android:state_pressed="true"><shape>
  4. <corners android:radius="5dp"/>
  5. <solid android:color="#DA6052"></solid>
  6. </shape></item>
  7. <item android:state_focused="true"><shape>
  8. <corners android:radius="5dp"/>
  9. <solid android:color="#DA6052"></solid>
  10. </shape></item>
  11. <item><shape>
  12. <corners android:radius="5dp"/>
  13. <solid android:color="#FA8072"></solid>
  14. </shape></item>
  15. </selector>

添加res/drawable/selector_button_clear.xml文件,修改内容如下
该文件为CE按钮,清空的背景选择器,设置按下效果

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <item android:state_pressed="true"><shape>
  4. <corners android:radius="5dp"/>
  5. <solid android:color="#FF83FA"></solid>
  6. </shape></item>
  7. <item android:state_focused="true"><shape>
  8. <corners android:radius="5dp"/>
  9. <solid android:color="#FF83FA"></solid>
  10. </shape></item>
  11. <item><shape>
  12. <corners android:radius="5dp"/>
  13. <solid android:color="#FFBBFF"></solid>
  14. </shape></item>
  15. </selector>

添加res/drawable/shape_edit.xml文件,修改内容如下
该文件为EditText的背景文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android">
  3. <solid android:color="#fff"></solid>
  4. <stroke android:color="#EED2EE" android:width="1dp"></stroke>
  5. </shape>

接下来修该系统默认添加的布局文件res/layout/activity_calculator.xml文件
即主界面的布局文件

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:orientation="vertical"
  5. android:background="#6aE9967A"
  6. >
  7. <!--标题-->
  8. <TextView
  9. android:layout_width="match_parent"
  10. android:layout_height="50dp"
  11. android:text="计算器"
  12. android:textSize="18sp"
  13. android:textColor="#fff"
  14. android:background="#FA8072"
  15. android:gravity="center"
  16. />
  17. <!--利用相对布局,首先根据自适应使GridView居于底部
  18. 再使EditText在GridView之上同时匹配父容器顶部-->
  19. <RelativeLayout
  20. android:layout_width="match_parent"
  21. android:layout_height="match_parent">
  22. <GridView
  23. android:layout_alignParentBottom="true"
  24. android:id="@+id/grid_buttons"
  25. android:layout_width="match_parent"
  26. android:layout_height="wrap_content"
  27. android:numColumns="4"
  28. android:layout_margin="10dp"
  29. android:verticalSpacing="10dp"
  30. android:horizontalSpacing="10dp"
  31. android:stretchMode="columnWidth"
  32. android:gravity="center" >
  33. </GridView>
  34. <EditText
  35. android:id="@+id/edit_input"
  36. android:padding="10dp"
  37. android:layout_width="match_parent"
  38. android:layout_height="match_parent"
  39. android:singleLine="false"
  40. android:scrollbars="vertical"
  41. android:hint="输入表达式"
  42. android:gravity="start"
  43. android:textSize="22sp"
  44. android:layout_alignParentTop="true"
  45. android:layout_above="@id/grid_buttons"
  46. android:background="@drawable/shape_edit"/>
  47. </RelativeLayout>
  48. </LinearLayout>

添加每个按钮的布局文件res/layout/item_button.xml
该文件就是GridView中每个按钮的布局文件。用于自定义适配器的生成

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. xmlns:android="http://schemas.android.com/apk/res/android">
  6. <TextView
  7. android:id="@+id/txt_button"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent"
  10. android:text="计算器"
  11. android:layout_gravity="center"
  12. android:textSize="22sp"
  13. android:paddingTop="10dp"
  14. android:paddingBottom="10dp"
  15. android:textColor="@color/buttom_color"
  16. android:background="@drawable/selector_button"
  17. android:gravity="center"
  18. />
  19. </LinearLayout>

添加适配器文件CalculatorAdapter.java

为什么要定义自己的适配器呢,原因就在于,当我们想用一些其它的展现方式,或者是我们需要的,呈现方式,这是就得DIY了。本项目是为了自定义部分按钮的按下效果。
首先我们定义一个类让它继承自BaseAdapter,再让它实现如下几个方法。那么这个自定义适配器就算好了。

文件新建在与CalculatorActivity.java同一个目录下
选择新建java class就好了,输入文件名为CalculatorAdapter,新建后修改内容如下

  1. package com.example.shiyanlou.calculator;
  2. import android.content.Context;
  3. import android.graphics.Color;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.widget.BaseAdapter;
  7. import android.widget.TextView;
  8. /**
  9. * 该类为自定义适配器类,需要继承BaseAdapter类
  10. * 主要是重新几个方法,如getView等
  11. */
  12. public class CalculatorAdapter extends BaseAdapter{
  13. private Context mContext;
  14. private String[] mStrs = null;
  15. /**
  16. * 自定义适配器构造函数
  17. * @param context 上下文
  18. * @param strs 数据源
  19. */
  20. public CalculatorAdapter(Context context, String[] strs){
  21. this.mContext = context;
  22. this.mStrs = strs;
  23. }
  24. @Override
  25. //返回已定义数据源总数量
  26. public int getCount() {
  27. return mStrs.length;
  28. }
  29. @Override
  30. //告诉适配器取得目前容器中的数据对象
  31. public Object getItem(int position) {
  32. return mStrs[position];
  33. }
  34. @Override
  35. //告诉适配器取得目前容器中的数据ID
  36. public long getItemId(int position) {
  37. return position;
  38. }
  39. @Override
  40. //取得当前欲显示的按钮View
  41. public View getView(int position, View convertView, ViewGroup parent) {
  42. // 利用View的inflate方法实例化一个View出来
  43. View view = View.inflate(mContext, R.layout.item_button, null);
  44. // 通过view找到按钮对应的控件TextView
  45. TextView textView = (TextView) view.findViewById(R.id.txt_button);
  46. // 根据position获取按钮应该设置的内容,并设置
  47. String str = mStrs[position];
  48. textView.setText(str);
  49. // 此处主要是为了给Back和CE两个按钮单独的按下效果。根据str的值来判断
  50. if(str.equals("Back")){
  51. textView.setBackgroundResource(R.drawable.selector_button_backspace);
  52. textView.setTextColor(Color.WHITE);
  53. }else if(str.equals("CE")){
  54. //textView.setBackgroundResource(R.drawable.selector_button_clear);
  55. textView.setBackgroundResource(R.drawable.selector_button_backspace);
  56. textView.setTextColor(Color.WHITE);
  57. }
  58. return view;
  59. }
  60. }

修改CalculatorActivity.java文件
详细内容已经在注释里面阐述的很详细了。

  1. package com.example.shiyanlou.calculator;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.text.Html;
  5. import android.view.View;
  6. import android.view.Window;
  7. import android.widget.AdapterView;
  8. import android.widget.BaseAdapter;
  9. import android.widget.EditText;
  10. import android.widget.GridView;
  11. import org.wltea.expression.ExpressionEvaluator;
  12. public class CalculatorActivity extends Activity{
  13. // 界面的主要的两个控件
  14. private GridView mGridButtons = null;
  15. private EditText mEditInput = null;
  16. // 适配器
  17. private BaseAdapter mAdapter = null;
  18. // EditText显示的内容,mPreStr表示灰色表达式部分,要么为空,要么以换行符结尾
  19. private String mPreStr = "";
  20. // mLastStr表示显示内容的深色部分
  21. private String mLastStr = "";
  22. /**
  23. * 这个变量非常重要,用于判断是否是刚刚成功执行完一个表达式
  24. * 因为,新加一个表达式的时候,需要在原来的表达式后面加上换行标签等
  25. */
  26. private boolean mIsExcuteNow = false;
  27. // html换行的标签
  28. private final String newLine = "<br\\>";
  29. // gridview的所有按钮对应的键的内容
  30. private final String[] mTextBtns = new String[]{
  31. "Back","(",")","CE",
  32. "7","8","9","/",
  33. "4","5","6","*",
  34. "1","2","3","+",
  35. "0",".","=","-",};
  36. @Override
  37. protected void onCreate(Bundle savedInstanceState) {
  38. super.onCreate(savedInstanceState);
  39. // 设置全屏,需要在setContentView之前调用
  40. requestWindowFeature(Window.FEATURE_NO_TITLE);
  41. setContentView(R.layout.activity_calculator);
  42. // 查找控件
  43. mGridButtons = (GridView) findViewById(R.id.grid_buttons);
  44. mEditInput = (EditText) findViewById(R.id.edit_input);
  45. // 新建adpater对象,并给GridView设置适配器
  46. mAdapter = new CalculatorAdapter(this, mTextBtns);
  47. mGridButtons.setAdapter(mAdapter);
  48. // 这句话的目的是为了让EditText不能从键盘输入
  49. mEditInput.setKeyListener(null);
  50. // 新建一个自定义AdapterView.OnItemClickListener的对象,用于设置GridView每个选项按钮点击事件
  51. OnButtonItemClickListener listener = new OnButtonItemClickListener();
  52. mGridButtons.setOnItemClickListener(listener);
  53. }
  54. /**
  55. * 这个函数用于设置EditText的显示内容,主要是为了加上html颜色标签。
  56. * 所有的显示EditText内容都需要调用此函数
  57. */
  58. private void setText(){
  59. final String[] tags = new String[]{"<font color='#858585'>", "<font color='#CD2626'>", "</font> "};
  60. StringBuilder builder = new StringBuilder();
  61. // 添加颜色标签
  62. builder.append(tags[0]); builder.append(mPreStr); builder.append(tags[2]);
  63. builder.append(tags[1]); builder.append(mLastStr); builder.append(tags[2]);
  64. mEditInput.setText(Html.fromHtml(builder.toString()));
  65. mEditInput.setSelection(mEditInput.getText().length());
  66. // 表示获取焦点
  67. mEditInput.requestFocus();
  68. }
  69. /**
  70. * 当用户按下 = 号的时候,执行的函数
  71. * 用于执行当前表达式,并判断是否有错误
  72. */
  73. private void excuteExpression(){
  74. Object result = null;
  75. try{
  76. // 第三方包执行表达是的调用
  77. result = ExpressionEvaluator.evaluate(mLastStr);
  78. }catch (Exception e){
  79. // 如果捕获到异常,表示表达式执行失败,调用setError方法显示错误信息
  80. //Toast.makeText(this, "表达式解析错误,请检查!", Toast.LENGTH_SHORT).show();
  81. mEditInput.setError(e.getMessage());
  82. mEditInput.requestFocus();
  83. // 这里设置为false是因为并没有执行成功,还不能开始新的表达式求值
  84. mIsExcuteNow=false;
  85. return;
  86. }
  87. // 执行成功了,设置标志为true,同时更新最后的表达式的内容为 表达式 + '=' + result
  88. mIsExcuteNow = true;
  89. mLastStr += "="+result;
  90. mEditInput.setError(null);
  91. // 显示执行结果
  92. setText();
  93. }
  94. /**
  95. * 该类是自定义选项按钮单击事件监听器
  96. */
  97. private class OnButtonItemClickListener implements AdapterView.OnItemClickListener{
  98. @Override
  99. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  100. String text = (String) parent.getAdapter().getItem(position);
  101. if(text.equals("=")){
  102. // 为 = 号时直接调用该方法就好
  103. excuteExpression();
  104. }else if(text.equals("Back")){
  105. // 如果按下退格按钮,表示删除一个字符
  106. // 如果最新的表达式长度为0,则需要把前面的表达式的最后部分赋值给最新的表达式
  107. if(mLastStr.length() == 0){
  108. // 如果历史表达式的长度不是0,那么此时历史表达式必然以换行符结尾
  109. // 如 3+5=8<br/>
  110. if(mPreStr.length() != 0){
  111. // 此时首先清除mPreStr的末尾的换行符 即 3+5=8
  112. mPreStr = mPreStr.substring(0, mPreStr.length()-newLine.length());
  113. mIsExcuteNow = true;
  114. // 找到前一个换行符的位置
  115. int index = mPreStr.lastIndexOf(newLine);
  116. if(index == -1){
  117. // 表示没有找到,即历史表达式只有一个 3+5=8不含有换行符就表示没有找到
  118. mLastStr = mPreStr;
  119. mPreStr = "";
  120. }else{
  121. // 找到了的话,就把历史表达式的最后一个表达式赋值给
  122. // 比如历史表达式为3=3<br/>3+5=8此时,就需要吧3+5=8作为最新表达式
  123. mLastStr = mPreStr.substring(index+newLine.length(), mPreStr.length());
  124. mPreStr = mPreStr.substring(0, index);
  125. }
  126. }
  127. }else{
  128. // 如果最新的表达式长度不是0,则直接减掉一个字符就好了
  129. mLastStr = mLastStr.substring(0, mLastStr.length()-1);
  130. }
  131. // 显示内容
  132. setText();
  133. }else if(text.equals("CE")){
  134. // 需要全被设置为空字符串,并设置标识为false,同时清空显示内容
  135. mPreStr = "";
  136. mLastStr = "";
  137. mIsExcuteNow = false;
  138. mEditInput.setText("");
  139. }else{
  140. // 按下其它键的情况
  141. if(mIsExcuteNow){
  142. // 如果刚刚成功执行了一个表达式,那么需要把当前表达式加到历史表达式后面并添加换行符
  143. mPreStr += mLastStr + newLine;
  144. // 重置标识为false
  145. mIsExcuteNow = false;
  146. // 设置最新表达式的第一个字符为当前按钮按下的内容
  147. mLastStr = text;
  148. }else{
  149. // 否则直接在最新表达式后面添加内容就好了
  150. mLastStr += text;
  151. }
  152. // 更新内容
  153. setText();
  154. }
  155. }
  156. }
  157. }

项目最终的目录结构为:

项目总结

该项目主要的难点在于控制按钮的点击事件,在处理历史表达式为灰色,最新表达式为红色上花费了较多的逻辑处理。另外,要灵活运用自定义适配器,DIY控件风格等。
实验楼下面项目运行截图:

主界面及其按下效果

错误提示结果

按退格键修改后,错误提示消失,并显示正确答案

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注