@stepbystep
2015-01-28T10:20:35.000000Z
字数 11063
阅读 5144
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文件,修改内容如下
<!--普通按钮文字颜色--><resources><color name="buttom_color">#4B0082</color></resources>
添加res/drawable/selector_button.xml文件,修改内容如下
该文件为普通按钮的背景选择器,设置按下效果
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"><!--selector 背景选择器, 每一个item对应一种状态,从上至下扫描,如果满足则选择该item作为背景主要用来处理按下的效果,获取获取焦掉与离开焦点的效果正常情况下 state_pressed="false" 且 state_focused="false",此时应选择最后一个item(此item无特殊要求)当控件被按下或者获取焦点的时候,就会匹配到第一个item,颜色就会变深,从而得到了按下的效果。当然每个item可以有其他的属性,这里的radius就是圆角的意思。--><item android:state_pressed="true"><shape><corners android:radius="5dp"/><solid android:color="#EE3B3B"></solid></shape></item><item android:state_focused="true"><shape><corners android:radius="5dp"/><solid android:color="#EE3B3B"></solid></shape></item><item><shape><corners android:radius="5dp"/><solid android:color="#EE9572"></solid></shape></item></selector>
添加res/drawable/selector_button_backspace.xml文件,修改内容如下
该文件为退格键的按下效果文件
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true"><shape><corners android:radius="5dp"/><solid android:color="#DA6052"></solid></shape></item><item android:state_focused="true"><shape><corners android:radius="5dp"/><solid android:color="#DA6052"></solid></shape></item><item><shape><corners android:radius="5dp"/><solid android:color="#FA8072"></solid></shape></item></selector>
添加res/drawable/selector_button_clear.xml文件,修改内容如下
该文件为CE按钮,清空的背景选择器,设置按下效果
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true"><shape><corners android:radius="5dp"/><solid android:color="#FF83FA"></solid></shape></item><item android:state_focused="true"><shape><corners android:radius="5dp"/><solid android:color="#FF83FA"></solid></shape></item><item><shape><corners android:radius="5dp"/><solid android:color="#FFBBFF"></solid></shape></item></selector>
添加res/drawable/shape_edit.xml文件,修改内容如下
该文件为EditText的背景文件
<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="#fff"></solid><stroke android:color="#EED2EE" android:width="1dp"></stroke></shape>
接下来修该系统默认添加的布局文件res/layout/activity_calculator.xml文件
即主界面的布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="#6aE9967A"><!--标题--><TextViewandroid:layout_width="match_parent"android:layout_height="50dp"android:text="计算器"android:textSize="18sp"android:textColor="#fff"android:background="#FA8072"android:gravity="center"/><!--利用相对布局,首先根据自适应使GridView居于底部再使EditText在GridView之上同时匹配父容器顶部--><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><GridViewandroid:layout_alignParentBottom="true"android:id="@+id/grid_buttons"android:layout_width="match_parent"android:layout_height="wrap_content"android:numColumns="4"android:layout_margin="10dp"android:verticalSpacing="10dp"android:horizontalSpacing="10dp"android:stretchMode="columnWidth"android:gravity="center" ></GridView><EditTextandroid:id="@+id/edit_input"android:padding="10dp"android:layout_width="match_parent"android:layout_height="match_parent"android:singleLine="false"android:scrollbars="vertical"android:hint="输入表达式"android:gravity="start"android:textSize="22sp"android:layout_alignParentTop="true"android:layout_above="@id/grid_buttons"android:background="@drawable/shape_edit"/></RelativeLayout></LinearLayout>
添加每个按钮的布局文件res/layout/item_button.xml
该文件就是GridView中每个按钮的布局文件。用于自定义适配器的生成
<?xml version="1.0" encoding="utf-8"?><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"xmlns:android="http://schemas.android.com/apk/res/android"><TextViewandroid:id="@+id/txt_button"android:layout_width="match_parent"android:layout_height="match_parent"android:text="计算器"android:layout_gravity="center"android:textSize="22sp"android:paddingTop="10dp"android:paddingBottom="10dp"android:textColor="@color/buttom_color"android:background="@drawable/selector_button"android:gravity="center"/></LinearLayout>
为什么要定义自己的适配器呢,原因就在于,当我们想用一些其它的展现方式,或者是我们需要的,呈现方式,这是就得DIY了。本项目是为了自定义部分按钮的按下效果。
首先我们定义一个类让它继承自BaseAdapter,再让它实现如下几个方法。那么这个自定义适配器就算好了。
文件新建在与CalculatorActivity.java同一个目录下
选择新建java class就好了,输入文件名为CalculatorAdapter,新建后修改内容如下
package com.example.shiyanlou.calculator;import android.content.Context;import android.graphics.Color;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.TextView;/*** 该类为自定义适配器类,需要继承BaseAdapter类* 主要是重新几个方法,如getView等*/public class CalculatorAdapter extends BaseAdapter{private Context mContext;private String[] mStrs = null;/*** 自定义适配器构造函数* @param context 上下文* @param strs 数据源*/public CalculatorAdapter(Context context, String[] strs){this.mContext = context;this.mStrs = strs;}@Override//返回已定义数据源总数量public int getCount() {return mStrs.length;}@Override//告诉适配器取得目前容器中的数据对象public Object getItem(int position) {return mStrs[position];}@Override//告诉适配器取得目前容器中的数据IDpublic long getItemId(int position) {return position;}@Override//取得当前欲显示的按钮Viewpublic View getView(int position, View convertView, ViewGroup parent) {// 利用View的inflate方法实例化一个View出来View view = View.inflate(mContext, R.layout.item_button, null);// 通过view找到按钮对应的控件TextViewTextView textView = (TextView) view.findViewById(R.id.txt_button);// 根据position获取按钮应该设置的内容,并设置String str = mStrs[position];textView.setText(str);// 此处主要是为了给Back和CE两个按钮单独的按下效果。根据str的值来判断if(str.equals("Back")){textView.setBackgroundResource(R.drawable.selector_button_backspace);textView.setTextColor(Color.WHITE);}else if(str.equals("CE")){//textView.setBackgroundResource(R.drawable.selector_button_clear);textView.setBackgroundResource(R.drawable.selector_button_backspace);textView.setTextColor(Color.WHITE);}return view;}}
修改CalculatorActivity.java文件
详细内容已经在注释里面阐述的很详细了。
package com.example.shiyanlou.calculator;import android.app.Activity;import android.os.Bundle;import android.text.Html;import android.view.View;import android.view.Window;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.EditText;import android.widget.GridView;import org.wltea.expression.ExpressionEvaluator;public class CalculatorActivity extends Activity{// 界面的主要的两个控件private GridView mGridButtons = null;private EditText mEditInput = null;// 适配器private BaseAdapter mAdapter = null;// EditText显示的内容,mPreStr表示灰色表达式部分,要么为空,要么以换行符结尾private String mPreStr = "";// mLastStr表示显示内容的深色部分private String mLastStr = "";/*** 这个变量非常重要,用于判断是否是刚刚成功执行完一个表达式* 因为,新加一个表达式的时候,需要在原来的表达式后面加上换行标签等*/private boolean mIsExcuteNow = false;// html换行的标签private final String newLine = "<br\\>";// gridview的所有按钮对应的键的内容private final String[] mTextBtns = new String[]{"Back","(",")","CE","7","8","9","/","4","5","6","*","1","2","3","+","0",".","=","-",};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 设置全屏,需要在setContentView之前调用requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_calculator);// 查找控件mGridButtons = (GridView) findViewById(R.id.grid_buttons);mEditInput = (EditText) findViewById(R.id.edit_input);// 新建adpater对象,并给GridView设置适配器mAdapter = new CalculatorAdapter(this, mTextBtns);mGridButtons.setAdapter(mAdapter);// 这句话的目的是为了让EditText不能从键盘输入mEditInput.setKeyListener(null);// 新建一个自定义AdapterView.OnItemClickListener的对象,用于设置GridView每个选项按钮点击事件OnButtonItemClickListener listener = new OnButtonItemClickListener();mGridButtons.setOnItemClickListener(listener);}/*** 这个函数用于设置EditText的显示内容,主要是为了加上html颜色标签。* 所有的显示EditText内容都需要调用此函数*/private void setText(){final String[] tags = new String[]{"<font color='#858585'>", "<font color='#CD2626'>", "</font> "};StringBuilder builder = new StringBuilder();// 添加颜色标签builder.append(tags[0]); builder.append(mPreStr); builder.append(tags[2]);builder.append(tags[1]); builder.append(mLastStr); builder.append(tags[2]);mEditInput.setText(Html.fromHtml(builder.toString()));mEditInput.setSelection(mEditInput.getText().length());// 表示获取焦点mEditInput.requestFocus();}/*** 当用户按下 = 号的时候,执行的函数* 用于执行当前表达式,并判断是否有错误*/private void excuteExpression(){Object result = null;try{// 第三方包执行表达是的调用result = ExpressionEvaluator.evaluate(mLastStr);}catch (Exception e){// 如果捕获到异常,表示表达式执行失败,调用setError方法显示错误信息//Toast.makeText(this, "表达式解析错误,请检查!", Toast.LENGTH_SHORT).show();mEditInput.setError(e.getMessage());mEditInput.requestFocus();// 这里设置为false是因为并没有执行成功,还不能开始新的表达式求值mIsExcuteNow=false;return;}// 执行成功了,设置标志为true,同时更新最后的表达式的内容为 表达式 + '=' + resultmIsExcuteNow = true;mLastStr += "="+result;mEditInput.setError(null);// 显示执行结果setText();}/*** 该类是自定义选项按钮单击事件监听器*/private class OnButtonItemClickListener implements AdapterView.OnItemClickListener{@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {String text = (String) parent.getAdapter().getItem(position);if(text.equals("=")){// 为 = 号时直接调用该方法就好excuteExpression();}else if(text.equals("Back")){// 如果按下退格按钮,表示删除一个字符// 如果最新的表达式长度为0,则需要把前面的表达式的最后部分赋值给最新的表达式if(mLastStr.length() == 0){// 如果历史表达式的长度不是0,那么此时历史表达式必然以换行符结尾// 如 3+5=8<br/>if(mPreStr.length() != 0){// 此时首先清除mPreStr的末尾的换行符 即 3+5=8mPreStr = mPreStr.substring(0, mPreStr.length()-newLine.length());mIsExcuteNow = true;// 找到前一个换行符的位置int index = mPreStr.lastIndexOf(newLine);if(index == -1){// 表示没有找到,即历史表达式只有一个 3+5=8不含有换行符就表示没有找到mLastStr = mPreStr;mPreStr = "";}else{// 找到了的话,就把历史表达式的最后一个表达式赋值给// 比如历史表达式为3=3<br/>3+5=8此时,就需要吧3+5=8作为最新表达式mLastStr = mPreStr.substring(index+newLine.length(), mPreStr.length());mPreStr = mPreStr.substring(0, index);}}}else{// 如果最新的表达式长度不是0,则直接减掉一个字符就好了mLastStr = mLastStr.substring(0, mLastStr.length()-1);}// 显示内容setText();}else if(text.equals("CE")){// 需要全被设置为空字符串,并设置标识为false,同时清空显示内容mPreStr = "";mLastStr = "";mIsExcuteNow = false;mEditInput.setText("");}else{// 按下其它键的情况if(mIsExcuteNow){// 如果刚刚成功执行了一个表达式,那么需要把当前表达式加到历史表达式后面并添加换行符mPreStr += mLastStr + newLine;// 重置标识为falsemIsExcuteNow = false;// 设置最新表达式的第一个字符为当前按钮按下的内容mLastStr = text;}else{// 否则直接在最新表达式后面添加内容就好了mLastStr += text;}// 更新内容setText();}}}}
项目最终的目录结构为:

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

错误提示结果

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