最近在入门Android,写了一个计算器,学习了一些比较精妙的处理方法,拿出来供大家学习参考啦。
完整源代码和签名打包好的apk文件可以在这里下载,apk文件可以在Android5.0以上版本安装运行
先上图看看界面
这是在我手机上截的图,我们先来看看界面是怎么实现的,外层是LinearLayout布局,按钮采用GridLayout布局,另按钮跨两行或跨两列排布的代码是在Button属性里设置
android:layout_rowSpan="2"//跨两行
android:layout_columnSpan="2"//跨两列
让几个按钮平分占满GridLayout一行或一列的Button属性是
android:layout_rowWeight="1"//一个按钮占一行空间的比重
android:layout_columnWeight="1"//一个按钮占占一列空间的比重
这里1只是用作计算比例,如果想让按钮平分的话就都设置一样的数就好了,不一定非要是1;另外如果某一行的按钮要设置rowWeight属性的话,必须要这一行的每一个按钮都设置,否则会显示异常,columnWeight属性对每一列的按钮也一样。
这里TextView的大小是固定的,如果要显示的东西太多,就要求TextView可以滚动查看,这里只有为TextView设置一个监听就好了
tvDisplay=(TextView) findViewById(R.id.tvDisplay);//获取TextView的id
tvDisplay.setMovementMethod(ScrollingMovementMethod.getInstance());//设置滚动监听
简简单单一行代码就可以实现滚动功能呢,那新问题来了,当我们点击按钮向里面输入时,我们必须手动滑动到TextView最底部才能看到我们新输入的东西,那有没有办法让我们新输入时自动跳转到最后一行呢?这里用到了TextView的scrollTo(int x,int y);方法,我们在每一次文本长度改变后,加上这样一段代码
int tvDisplayHeight=tvDisplay.getHeight();//获取显示框高度
int heightDiff=tvDisplay.getLayout().getLineTop(tvDisplay.getLineCount())-tvDisplayHeight;//获取文本高度与显示框高度的差
if(heightDiff>0)
{
//如果高度差大于零,就让显示框的左上角定位到刚好够显示文本最后一行的位置
tvDisplay.scrollTo(0,heightDiff);
}else{
//否则显示到最开头
tvDisplay.scrollTo(0,0);
}
再上两张图
可以看到,为了让运算结果更突出,区别计算记录和新输入的表达式,我改变了部分字体的大小,这个可以用Spannable实现
spannable=new SpannableString(strRecord);
spannable.setSpan(new AbsoluteSizeSpan(15,true),0,str.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tvDisplay.setText(spannable);
Spannable是可以改变部分文本属性(如大小,颜色等)的类,具体更详尽的用法大家可以查一下。
另外给TextView添加双击满屏的方法,以便我们更方便的查看运算记录,如图
需要给TextView设置点击监听,并通过获取两次点击时的系统时间判断两次点击的时间间隔,代码如下
//第一次点击时间在activity启动时初始化
private long firstClickTime= System.currentTimeMillis();
private long secondClickTime;
/*
*//此处省略无关代码
*
*
*/
tvDisplay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//第二次点击时间在点击TextView时初始化
secondClickTime=System.currentTimeMillis();
if(secondClickTime-firstClickTime<500)
{
ViewGroup.LayoutParams layoutParams=tvDisplay.getLayoutParams();
if(tvExtent)//tvExtent是一个布尔值,用于判断此次双击执行放大还是恢复操作
{
Log.d("MainActivity",tvDisplay.getHeight()+"");
layoutParams.height=((LinearLayout)findViewById(R.id.activity_main)).getHeight();
tvExtent=false;
}else
{
layoutParams.height=300;
tvExtent=true;
}
tvDisplay.setLayoutParams(layoutParams);
}
firstClickTime=secondClickTime;
}
});
ok,到这里界面设置的处理技巧就介绍完了,接下来我们看看这个计算器的核心:如何实现计算。
计算基于字符串处理,当我们点击“=”按钮时,开始计算,首先提取出TextView里的字符串,定义两个栈,一个Double型一个Character行。再定义一个StringBuffer。然后遍历表达式字符串的每一个字符,如果字符是0~9或者小数点,就先存入StringBuffer,当遇到一个字符不属于上述范围,就一定是一个运算符了,这时候把StringBuffer里的字符串转化为Double
型数值压入存储运算数的栈。然后比较新获取的运算符与运算符栈里的最顶部的运算符的优先级(如果运算符为空的话就是新获得的运算符优先级高啦),如果新获得的优先级高于最栈里最顶部的,就取出一个运算符两个数进行运算(运算结果压入数字栈),直到顶部的运算符高于新获得的这个,然后将新获得的运算符压入栈。直到所有运算完成。这是数值栈里唯一的一个数就是最后结果
下面时定义运算符优先级的代码:
private boolean compare(char symbol){
if(symbolStack.isEmpty())
{
return false;
}else{
char pop=symbolStack.peek();
switch (pop)
{
case '^':
if (symbol!='(')
return true;
case 'x':
case '÷':
case '%':
if(symbol!='^'&&symbol!='(')
return true;
case '+':
case '-':
if(symbol=='+'||symbol=='-'||symbol==')'||symbol=='=')
return true;
default://pop=='('
return false;
}
}
}
字符串处理算法代码:
private Double getResult (String str)throws Exception
{
str=str.substring(strRecord.length(),str.length());
str.trim();
Log.d("MainActivity","str:"+str+"\nstrRecord:"+strRecord);
Double result=0.0;
numberStack=new Stack<Double>();
symbolStack=new Stack<Character>();
StringBuffer number=new StringBuffer();
for(int i=0;i<str.length();i++)
{
char ch=str.charAt(i);
if((ch>='0'&&ch<='9')||ch=='.')
{
number.append(ch);
}else{
if(number.length()!=0)
{
numberStack.push(Double.parseDouble(number.toString()));
number.setLength(0);
}
//数字入栈,处理是否运算
while(compare(ch))
{
Double b=numberStack.pop();
Double a=numberStack.pop();
switch ((char)symbolStack.pop())
{
case '+':
numberStack.push(a+b);
break;
case '-':
numberStack.push(a-b);
break;
case 'x':
numberStack.push(a*b);
break;
case '÷':
numberStack.push(a/b);
break;
case'%':
numberStack.push(a%b);
break;
case '^':
numberStack.push(Math.pow(a,b));
break;
}
}
//运算结束,处理运算符ch
if(ch=='='&&!numberStack.isEmpty())
result=numberStack.pop();
else {
if(ch==')')
{
symbolStack.pop();
}else {
symbolStack.push(ch);
}
}
}
}
return result;
}
可以看到,getResult方法可能会抛出异常,这个异常会在处理表达式字符串出错时抛出,抛出的可能有很大,只有表达式不符合规范,无法运算,就会抛出,这是在“=”点击监听里捕获,给用户一个提示
效果如图:
代码如下:
btnEqual.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String str=tvDisplay.getText().toString();
if(str.length()==strRecord.length())
return;
str+="=";
double result=0;
try{
result= getResult(str);
if(result%1==0){
strRecord=str+(int)result+"\n";
}
else {
strRecord=str+result+"\n";
}
spannable=new SpannableString(strRecord);
spannable.setSpan(new AbsoluteSizeSpan(15,true),0,str.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tvDisplay.setText(spannable);
int tvDisplayHeight=tvDisplay.getHeight();
int heightDiff=tvDisplay.getLayout().getLineTop(tvDisplay.getLineCount())-tvDisplayHeight;
if(heightDiff>0)
{
tvDisplay.scrollTo(0,heightDiff);
}else{
tvDisplay.scrollTo(0,0);
}
}catch(Exception e){
Toast.makeText(MainActivity.this,"表达式有误",Toast.LENGTH_SHORT).show();
}
}
});
接下来最后一个就是换掉软件图标啦,这个很简单,相信大家都会了,但是我遇到了一个问题,就是我真机测试的时候图标无法显示,但是换一个手机就可以,百度了一下,这个可能是还没有换图标时我装过这个软件,手机系统缓存了软件图标,当同签名同工程名的软件再次安装时,手机系统自动调用缓存图标,就导致无法正常显示了,这个可以解决方法据说有三个,一个是改打包时用的签名,一个是改工程名(这两个方法我都没试过,不知道行不行),什么都不想改的话就换一下手机主题,软件图标就可以同步了,这个方法亲测有效。
好了,分享这么多,第一次写博客,难免有瑕疵,希望大家留言指正