【Android App】Calculator(一)onCreate过程分析
来源:互联网 发布:自学编程语言 知乎 编辑:程序博客网 时间:2024/06/10 08:12
前言
Calculator作为日常生活的必备工具之一,也被Android收录在了它的原生App之中,但由于Google并没有过多的开发这款App,只实现了简单计算和一般的科学计算,历史记录功能在代码中实现了但是在App的界面上却怎么也找不到历史记录的入口,不懂这是不是Google有意而为之。大伙现在买的Android手机上面的Calculator一般是经过定制的,如果你发现你手机上的Calculaotr和原生的一样的话,那你这款手机的厂商绝对不够良心。
下面先看一下Calculator的界面:
如你所看到的,原生的Calculator的界面就是这么的简单,其实Google这样做也是为开发者着想,让我们这些开发者有口饭吃。Calculator存在横屏界面,与上面的基本一致就不再次多赘述。
Calculator源码下载地址:http://pan.baidu.com/s/1dDnHw2l
工程目录构成
Calculator启动分析
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.app.calculator2"> <original-package android:name="com.android.app.calculator2" /> <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="20"/> <application android:label="@string/app_name" android:icon="@mipmap/ic_launcher_calculator"> <activity android:name="Calculator" android:theme="@android:style/Theme.Holo.NoActionBar" android:windowSoftInputMode="stateAlwaysHidden"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.APP_CALCULATOR" /> </intent-filter> </activity> </application></manifest>
由上面的代码可以知道,Calculator.java是这个App默认启动Activity,我们就到其中一探究竟。
setContentView(R.layout.main); mPager = (ViewPager) findViewById(R.id.panelswitch); if (mPager != null) { mPager.setAdapter(new PageAdapter(mPager)); } else { // Single page UI final TypedArray buttons = getResources().obtainTypedArray(R.array.buttons); for (int i = 0; i < buttons.length(); i++) { setOnClickListener(null, buttons.getResourceId(i, 0)); } buttons.recycle(); }加载布局文件,获得布局文件根节点下的panelswitch节点赋值给mPager对象,看这名字起的有点玄乎,现在还看不出它的作用。往下看,一般会走if这一段。
class PageAdapter extends PagerAdapter { //果然是一个绑定视图的适配器 private View mSimplePage; private View mAdvancedPage; public PageAdapter(ViewPager parent) { final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); final View simplePage = inflater.inflate(R.layout.simple_pad, parent, false); final View advancedPage = inflater.inflate(R.layout.advanced_pad, parent, false); mSimplePage = simplePage; mAdvancedPage = advancedPage; final Resources res = getResources(); final TypedArray simpleButtons = res.obtainTypedArray(R.array.simple_buttons); for (int i = 0; i < simpleButtons.length(); i++) { setOnClickListener(simplePage, simpleButtons.getResourceId(i, 0)); } simpleButtons.recycle(); final TypedArray advancedButtons = res.obtainTypedArray(R.array.advanced_buttons); for (int i = 0; i < advancedButtons.length(); i++) { setOnClickListener(advancedPage, advancedButtons.getResourceId(i, 0)); } advancedButtons.recycle(); final View clearButton = simplePage.findViewById(R.id.clear); if (clearButton != null) { mClearButton = clearButton; } final View backspaceButton = simplePage.findViewById(R.id.del); if (backspaceButton != null) { mBackspaceButton = backspaceButton; } } @Override public int getCount() { return 2; } @Override public void startUpdate(View container) { } @Override public Object instantiateItem(View container, int position) { final View page = position == 0 ? mSimplePage : mAdvancedPage; ((ViewGroup) container).addView(page); return page; } @Override public void destroyItem(View container, int position, Object object) { ((ViewGroup) container).removeView((View) object); } @Override public void finishUpdate(View container) { } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Parcelable saveState() { return null; } @Override public void restoreState(Parcelable state, ClassLoader loader) { } }通过上面的代码可以知道,这个构造函数的作用就是用来获得mSimplePage和mAdvancedPage两个视图类,通过查看R.layout.simple_pad和R.layout.advanced_pad两个布局文件我们可以知道mSimplePage和mAdvancedPage就是基本面板(第一张图所示的输入部分)和高级面板(第二张图的输入部分)
知道了这两个货的作用下面的代码也就好懂了,simpleButtons和advancedButtons是两个数组,存放的元素就是对应mSimplePage和mAdvancedPage上每个按钮的Id。 然后再通过setOnClickListener这个方法给每个按钮加上时间监听mListener,mListener是EventListener的实例,EventListener一共实现了View.OnKeyListener, View.OnClickListener和View.OnLongClickListener三个事件接口为后面的输入做准备。
<FrameLayout android:layout_width="wrap_content" android:layout_height="match_parent"> <!-- marginRight has to be 0 to catch border-touch --> <com.android.app.calculator2.ColorButton android:id="@+id/clear" android:text="@string/clear" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="0dp" android:textSize="15dp" style="@style/button_style" android:minWidth="89dip" /> <!-- marginRight has to be 0 to catch border-touch --> <com.android.app.calculator2.ColorButton android:id="@+id/del" android:text="@string/del" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="0dp" android:textSize="15dp" style="@style/button_style" android:contentDescription="@string/delDesc" android:ellipsize="end" android:minWidth="89dip" /> </FrameLayout>
接着是获得Persist的对象mPersist并调用它的load方法,这里我们来看看Persist是干嘛的,load又做了什么事情?
public void load() { try { InputStream is = new BufferedInputStream(mContext.openFileInput(FILE_NAME), 8192); DataInputStream in = new DataInputStream(is); int version = in.readInt(); if (version > 1) { mDeleteMode = in.readInt(); } else if (version > LAST_VERSION) { throw new IOException("data version " + version + "; expected " + LAST_VERSION); } history = new History(version, in); in.close(); } catch (FileNotFoundException e) { Calculator.log("" + e); } catch (IOException e) { Calculator.log("" + e); } }Persist的意思有“坚持”这里应该是“持久化数据”的意思。在load方法中我们可以看到有一系列的流操作。
首先获得一个in,对应的文件是FILE_NAME = "calculator.data",然后读取一个整数version,这里暂时还不知道version是干嘛用的先放一放。
接着获得History的对象history,我们进History的构造函数看看它究竟做了些什么?
History(int version, DataInput in) throws IOException { if (version >= VERSION_1) { int size = in.readInt(); for (int i = 0; i < size; ++i) { mEntries.add(new HistoryEntry(version, in)); } mPos = in.readInt(); } else { throw new IOException("invalid version " + version); } }假设这里走if这一段,又是一个循环,根据size的大小,向mEntries中添加HistoryEntry的实例。我们继续进入HistoryEntry的构造函数看看:
HistoryEntry(int version, DataInput in) throws IOException { if (version >= VERSION_1) { mBase = in.readUTF(); mEdited = in.readUTF(); Log.d("zql", "" + mBase + " " + mEdited) ; //Calculator.log("load " + mEdited); } else { throw new IOException("invalid version " + version); } }终于到底了,这里通过in输入流获得mBase和mEdited,这两个字段具体是什么东西我们可以用Log打印出来看看:
看出来是什么了吧?这两个字段里面存的都是我之前输入的表达式,但有一些是空的,还有?号,这个这里还看不出来,在后面写数据的时候可以知道。
我们再来看看calculator.data这个文件究竟在哪里?
看到了吧,它在data/data/com.android.app.calculator2/files/目录下。
onCreate方法继续往下走,将前面你的history赋值给当前类的mHistory,这么做的目的大家应该很清楚,是为了在将来用到这里的历史记录。然后是获得CalculatorDisplay的实例mDisplay。mDisplay是啥呢?其实就是Calculator的显示输入表达式的区域。
接着实例化Logic,我们还是进入Logic的构造函数:
Logic(Context context, History history, CalculatorDisplay display) { mContext = context; mErrorString = mContext.getResources().getString(R.string.error); mHistory = history; mDisplay = display; mDisplay.setLogic(this); }这个Logic相当于一个逻辑的控制器,在构造函数中它获得了前面的历史记录mHistory和mDisplay。这样mLogic就能操作mHistory和mDisplay了。
继续往下走(何处是个头啊~)
给mLogic设置了字段,这里我们不多讨论。接着是获得一个HistoryAdapter对象,然后跟mHistory进行绑定。接着一个if语句是用来显示当前面板是mSimplePage还是mAdvancedPage。
接着看mListener.setHandler(mLogic, mPager)这个方法
void setHandler(Logic handler, ViewPager pager) { mHandler = handler; mPager = pager; }还记得前面给每一个Button设置事件监听mListener吗?这里给mListener设置了它的逻辑处理器为mLogic,由此我们可以知道,当我们点击按钮后,事件响应的处理部分肯定是由mLogic来完成,是不是这样我们后面继续看。
接着给mDisplay设置监听,这里的监听应该是监听用户点击表达式输入域来改变光标的位置(我这么说能想象出来么?)
if (!ViewConfiguration.get(this).hasPermanentMenuKey()) { createFakeMenu(); }这段代码是查看手机是否有“菜单”这个实体按键,如果没有则在App界面上显示mOverflowMenuButton这个按钮,用来显示菜单。这里我用的测试手机是小米1s,是具有“菜单”按键的,所以界面如上面的截图所示,但如果我在这里把if判断去掉,直接调用createFakeMenu,看看界面会变成什么样?
接着看onCreate,mLogic.resumeWithHistory()是去都历史记录,看看具体怎么做的?
public void resumeWithHistory() { clearWithHistory(false); } private void clearWithHistory(boolean scroll) { String text = mHistory.getText(); if (MARKER_EVALUATE_ON_RESUME.equals(text)) { if (!mHistory.moveToPrevious()) { text = ""; } text = mHistory.getText(); evaluateAndShowResult(text, CalculatorDisplay.Scroll.NONE); } else { mResult = ""; mDisplay.setText( text, scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE); mIsError = false; } }首先调用mHistory.getText()
HistoryEntry current() { return mEntries.elementAt(mPos); } String getText() { return current().getEdited(); }mHistory.getText()获得之前读取的历史记录的最后一个HistoryEntry的mEdited字段,接着看,
如果mEdited等于MARKER_EVALUATE_ON_RESUME即?的话历史记录向前移动一个,即mPos-1,去获得前一个历史记录的mEdited字段,然后调用evaluateAndShowResult这个方法来计算和显示你退出Calculator前最后一次计算结果。evaluateAndShowResult这个方法相对较复杂,我们后面分析。
如果mEdited等于“”的话,直接显示“”。这里我猜测?的作用就是用来判断是否显示最后一侧计算结果的。
onCreate中最后一行updateDeleteMode()
private void updateDeleteMode() { if (mLogic.getDeleteMode() == Logic.DELETE_MODE_BACKSPACE) { mClearButton.setVisibility(View.GONE); mBackspaceButton.setVisibility(View.VISIBLE); } else { mClearButton.setVisibility(View.VISIBLE); mBackspaceButton.setVisibility(View.GONE); } }
这个方法的作用是根据mDeleteMode这个值来更新删除按钮,选择显示mBackspaceButton或mClearButton。
mClearButton的作用是清楚计算结果,mBackspaceButton的作用的一个一个字符的清除输入表达式。
到这里整个oncCeate方法就分析完了,总结一下onCreate方法做了哪些事情:
- 加载视图
- 加载历史记录
- 判断是否显示最后一次的计算结果
- 更新删除按钮
文章写的比较仓促,有问题的地方欢迎指出。
在下一篇中我会重点分析Calculator的计算过程的实现~
- 【Android App】Calculator(一)onCreate过程分析
- 【Android App】Calculator(二)计算过程详细分析
- 计算器Calculator分析(一)
- Android(一)onCreate方法
- Android App 启动过程分析
- Android Browser App 源码分析(一)
- [Linphone Android]LinphoneService分析@onCreate
- Android多进程app中Application回调onCreate()方法被执行多次分析及解决
- Android应用安装过程分析(一)
- android源生Browser分析(一)--loadUrl APP层分析
- Android应用程序(app)进程启动过程的源代码分析
- Android app安装过程分析(基于Nougat)
- Android app安装过程分析(基于Nougat)
- android studio context getapplicationcontext app oncreate
- Android的APP启动过程分析
- 第二次启动android app的过程分析
- Android 启动过程分析 (一)
- 深入分析:Android中app之间的交互(一)
- _variant_t类型和CString类型、CTime类型的相互转换
- Error: could not find java.dll如何解决
- 函数封装成类库
- SQL 高级(8) 数据类型
- TCP和UDP传输
- 【Android App】Calculator(一)onCreate过程分析
- Function Run Fun(记忆化搜索)
- 清除UIWebView的缓存
- mutt+exim+fetchmail收发邮件
- <c:when>和<c:otherwise>两部分都执行了?
- 基础篇--插入排序
- js自定义对象
- InnoDB 引擎独立表空间 innodb_file_per_table
- java中 >、>>、>>>三者的区别