【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相对比较简单,Google也很懒的没有对其进行分包,一股脑将所有文件放一起了。

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,我们就到其中一探究竟。

        首先看一下onCreate方法,
        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这一段。
        new PageAdapter(mPager)这是个啥玩意?看名字应该是一个绑定视图的适配器,跟进代码看看。

 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三个事件接口为后面的输入做准备。
        终于从new PageAdapter(mPager)里面出来了,原来它就是一个适配器,顺便实例化了一些App所需要的视图对象。
         mPager.setAdapter(new PageAdapter(mPager))这个方法是将这里的PageAdapter绑定到mPager上,这里mPager的类型是ViewPager。ViewPager具体用法可参照http://developer.android.com/reference/android/support/v4/view/ViewPager.html
        接着看onCreate方法,获得mClearButton和mBackspaceButton两个按钮,分别是“清除”按钮和“删除”按钮。可能有人问,为什么图中只显示“删除”按钮?那是应为这两个按钮的位置是重叠在一起的,在不同的输入状态下会显示不同的按钮。可以通过下面的布局文件判断得到:
        <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方法做了哪些事情:

  1. 加载视图
  2. 加载历史记录
  3. 判断是否显示最后一次的计算结果
  4. 更新删除按钮

        文章写的比较仓促,有问题的地方欢迎指出。

        在下一篇中我会重点分析Calculator的计算过程的实现~

0 0
原创粉丝点击