@stepbystep
2015-01-28T18:20:35.000000Z
字数 11063
阅读 4921
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"
>
<!--标题-->
<TextView
android: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之上同时匹配父容器顶部-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android: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>
<EditText
android: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"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android: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
//告诉适配器取得目前容器中的数据ID
public long getItemId(int position) {
return position;
}
@Override
//取得当前欲显示的按钮View
public View getView(int position, View convertView, ViewGroup parent) {
// 利用View的inflate方法实例化一个View出来
View view = View.inflate(mContext, R.layout.item_button, null);
// 通过view找到按钮对应的控件TextView
TextView 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",".","=","-",};
@Override
protected 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,同时更新最后的表达式的内容为 表达式 + '=' + result
mIsExcuteNow = true;
mLastStr += "="+result;
mEditInput.setError(null);
// 显示执行结果
setText();
}
/**
* 该类是自定义选项按钮单击事件监听器
*/
private class OnButtonItemClickListener implements AdapterView.OnItemClickListener{
@Override
public 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=8
mPreStr = 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;
// 重置标识为false
mIsExcuteNow = false;
// 设置最新表达式的第一个字符为当前按钮按下的内容
mLastStr = text;
}else{
// 否则直接在最新表达式后面添加内容就好了
mLastStr += text;
}
// 更新内容
setText();
}
}
}
}
项目最终的目录结构为:
该项目主要的难点在于控制按钮的点击事件,在处理历史表达式为灰色,最新表达式为红色上花费了较多的逻辑处理。另外,要灵活运用自定义适配器,DIY控件风格等。
实验楼下面项目运行截图:
主界面及其按下效果
错误提示结果
按退格键修改后,错误提示消失,并显示正确答案