【Android App】Calculator(一)onCreate过程分析

前言

        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的计算过程的实现~

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值