zoukankan      html  css  js  c++  java
  • Android7.0 拨号盘应用源码分析(一) 界面浅析

    前言

    android拨号盘的源码目录在package/app/Dialer

    自7.0以后Incallui的源码直接放到了Dialer目录下,虽然在7.0以前incallui有自己独立的目录,但实际编译过程中只是作为链接库最后还是被编译到Dialer的apk里

    博主这里只取Dialer相关的源码并导入AS中,并稍作调整兼容至L

    源码目录结构如下:

    source_thumb2

    先理一理各个工程的依赖关系

    com.android.dialer是主工程依赖于

    com.android.contacts.common工程和com.android.phone.common工程

    com.android.contacts.common又依赖于

    com.android.phone.common工程和com.android.common工程

    另外一些support包也作为链接工程被引入,以上代码均取自google源码

    github下载链接:https://github.com/geniusgithub/AndroidDialer

    1.1拨号盘概览

    先来看看几张原图

    m1_thumb3

    m2_thumb2

    1.2 DialtactsActivity

    listsfragment20757_thumb1

    主activity为DialtactsActivity

    com.android.dialer.DialtactsActivity
    public class DialtactsActivity extends TransactionSafeActivity 。。。{
    
        // Fragment containing the dialpad that slides into view
        protected DialpadFragment mDialpadFragment;
       
        // Fragment for searching phone numbers using the alphanumeric keyboard.
        private RegularSearchFragment mRegularSearchFragment;
    
        // Fragment for searching phone numbers using the dialpad.
        private SmartDialSearchFragment mSmartDialSearchFragment;
    
        // Fragment containing the speed dial list, call history list, and all contacts list.    
        private ListsFragment mListsFragment;
    
        private DialerDatabaseHelper mDialerDatabaseHelper;
    
        private FloatingActionButtonController mFloatingActionButtonController;
    
        ...... ......
        ...... .....
     }

    如类图关系所示,主要有以下几个关键的成员变量

    com.android.dialer.dialpad .DialpadFragment // 拨号盘fragment

    com.android.dialer.list.RegularSearchFragment // 联系人搜索fragment

    com.android.dialer.list.SmartDialSearchFragment // 拨号搜索fragment

    com.android.dialer.list.ListsFragment // TAB页fragment,包含快速联系人,最近通话记录,联系人列表三个子fragment

    com.android.dialer.database.DialerDatabaseHelper // 拨号搜索数据库SQLiteOpenHelper对象

    com.android.contacts.common.widget.FloatingActionButtonController // 悬浮按钮控制器

    再看看onCreate里的主要实现(部分内容省略)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        setContentView(R.layout.dialtacts_activity);
    
        final ActionBar actionBar = getSupportActionBar();
        actionBar.setCustomView(R.layout.search_edittext);
        // 给actionbar设置自定义view (SearchEditTextLayout)
        SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar
        .getCustomView().findViewById(R.id.search_view_container);
    
        // 给SearchEditTextLayout添加管理器ActionBarController
        mActionBarController = new ActionBarController(this, searchEditTextLayout);
       
      final View floatingActionButtonContainer = findViewById(
                R.id.floating_action_button_container);
      ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
      floatingActionButton.setOnClickListener(this);
       // 用FloatingActionButtonController管理悬浮按钮
      mFloatingActionButtonController = new FloatingActionButtonController(this,
         floatingActionButtonContainer, floatingActionButton);
    
       // 添加ListsFragment
        getFragmentManager().beginTransaction()
            .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
            .commit();
    
      // 初始化单例对象DialerDatabaseHelper
       mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
      SmartDialPrefix.initializeNanpSettings(this);
    
    }

    1.3 ListsFragment

    ListsFragment是主fragment,结构如下

    public class ListsFragment extends Fragment{
     
        private ViewPager mViewPager;
        private ViewPagerTabs mViewPagerTabs;  
        // 自定义TAB标签,继承自HorizontalScrollView
        private ViewPagerAdapter mViewPagerAdapter;
    
        // 拖拽常用联系人时悬浮视图
        private RemoveView mRemoveView;
        private View mRemoveViewContent;
    
        // 常用联系人fragment
        private SpeedDialFragment mSpeedDialFragment;
    
        // 最近通话记录fragment
        private CallLogFragment mHistoryFragment;
    
        // 联系人列表fragment
        private AllContactsFragment mAllContactsFragment;
    
        // Voicemail列表fragment
        private CallLogFragment mVoicemailFragment;
    
       @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
       
            final View parentView = inflater.inflate(R.layout.lists_fragment, container, false);
    
            mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager);
            mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
            mViewPager.setAdapter(mViewPagerAdapter);
            mViewPager.setOffscreenPageLimit(TAB_COUNT_WITH_VOICEMAIL - 1);
            mViewPager.setOnPageChangeListener(this);
            showTab(TAB_INDEX_SPEED_DIAL);
    
            ...... ......  ...... ......
    
            mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header);
            mViewPagerTabs.configureTabIcons(mTabIcons);
            mViewPagerTabs.setViewPager(mViewPager);
            addOnPageChangeListener(mViewPagerTabs);
    
            mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view);
            mRemoveViewContent = parentView.findViewById(R.id.remove_view_content);
    
            return parentView;
        }
    }

    ListsFragment最多可以显示四个fragment,有个VisualVoicemailCallLogFragment显示一种特定的通话记录(提供视频语音邮件服务)

    类型为Calls.VOICEMAIL_TYPE,需要运营商支持,只有存在该类通话记录才会显示该TAB页,国内运营商暂不支持

    voicemail_thumb2

    SpeedDialFragment显示常用联系人列表

    public class SpeedDialFragment extends Fragment ...{
    
        // 显示数据的GridView列表
        private PhoneFavoriteListView mListView;    
    
       // 源数据BaseAdapter
        private PhoneFavoritesTileAdapter mContactTileAdapter;
    
      // 查询源数据的LoaderCallbacks
      private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
            @Override
            public CursorLoader onCreateLoader(int id, Bundle args) {
                if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader.");
                return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity());
            }
    
            @Override
            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
                if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished");
                mContactTileAdapter.setContactCursor(data);
                setEmptyViewVisibility(mContactTileAdapter.getCount() == 0);
            }
    
            @Override
            public void onLoaderReset(Loader<Cursor> loader) {
                if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. ");
            }
        }
    }

    使用LoadManager方式获取cursor数据,查询ContactsProvider数据库的data表

    com.android.contacts.common.ContactTileLoaderFactory
    public static CursorLoader createStrequentPhoneOnlyLoader(Context context) {
         Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon()
              .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
    
         return new CursorLoader(context, uri, COLUMNS_PHONE_ONLY, null, null, null);
    }

    数据来源包括收藏的联系人以及有通话记录的联系人

    1.4 DialpadFragment

    DialpadFragment显示拨号盘fragment

    在DialtactsActivity中添加如下

    private void showDialpadFragment(boolean animate) {
    
      if (mDialpadFragment == null) {
                mDialpadFragment = new DialpadFragment();
                ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT);
            } else {
                ft.show(mDialpadFragment);
            }
     }

    第一次显示时动态添加进去,后续动态控制显示隐藏

    public class DialpadFragment extends Fragment{
     
      private DialpadView mDialpadView; // 拨号数字面板(包括输入号码框)
      private EditText mDigits;          // 输入号码框
    
      private ToneGenerator mToneGenerator; // DTMF音播放器
      private ListView mDialpadChooser;     // 通话状态时显示的视图
      private DialpadChooserAdapter mDialpadChooserAdapter;
      // 通话状态时显示的视图adapter
    
      @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
    
            // 横竖屏加载不同的布局
            final View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container,
                    false);
            fragmentView.buildLayer();
    
            mDialpadView = (DialpadView) fragmentView.findViewById(R.id.dialpad_view);
            mDialpadView.setCanDigitsBeEdited(true);
            mDigits = mDialpadView.getDigits();
            ...... ........... ......
            PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);  // 格式化输入框中的号码
            // Check for the presence of the keypad
            View oneButton = fragmentView.findViewById(R.id.one);
            if (oneButton != null) {  // 绑定各个数字按键onPress事件
                configureKeypadListeners(fragmentView);
            }
           ...... ............ ......
            mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
            mDialpadChooser.setOnItemClickListener(this);
            ...... ..... ...... ......
            return fragmentView;
        }
    
    }

    横屏和竖屏所加载的拨号面板布局是不一样的

    dialpad_por_thumb1dialpad_land_thumb3

    DialpadView是个自定义视图,主要用于显示数字按键和输入号码框

    public class DialpadView extends LinearLayout {
    
        private EditText mDigits;     // 输入号码框
        private ImageButton mDelete; // 删除按钮
    
        private void setupKeypad() {
            ...... ............ ......
            DialpadKeyButton dialpadKey;
            TextView numberView;
            TextView lettersView;
             ...... ............ ......
            for (int i = 0; i < mButtonIds.length; i++) {
                dialpadKey = (DialpadKeyButton) findViewById(mButtonIds[i]);
                numberView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_number);
                lettersView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_letters);
              ...... ............ ......
                final RippleDrawable rippleBackground = (RippleDrawable)
                        getDrawableCompat(getContext(), R.drawable.btn_dialpad_key);
                if (mRippleColor != null) {
                    rippleBackground.setColor(mRippleColor);            }
    
                numberView.setText(numberString);
                numberView.setElegantTextHeight(false);
                dialpadKey.setContentDescription(numberContentDescription);
                dialpadKey.setBackground(rippleBackground); // 设置数字按键水波纹背景色
    
    
                if (lettersView != null) {
                    lettersView.setText(resources.getString(letterIds[i]));
                }
            }
             ...... ............ ......
        }
    public void animateShow() {  // 显示拨号面板时各个数字按键的动画效果
                ...... ............ ......
            for (int i = 0; i < mButtonIds.length; i++) {
                 ...... ............ ......
                ViewPropertyAnimator animator = dialpadKey.animate();
                if (mIsLandscape) {
                    // Landscape orientation requires translation along the X axis.
                    // For RTL locales, ensure we translate negative on the X axis.
                    dialpadKey.setTranslationX((mIsRtl ? -1 : 1) * mTranslateDistance);
                    animator.translationX(0);
                } else {
                    // Portrait orientation requires translation along the Y axis.
                    dialpadKey.setTranslationY(mTranslateDistance);
                    animator.translationY(0);
                }
                animator.setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
                        .setStartDelay(delay)
                        .setDuration(duration)
                        .setListener(showListener)
                        .start();
            }
        }
    }

    当处于通话状态时显示如下

    device-2016-11-27-164311_thumb2

    1.5SmartDialSearchFragment RegularSearchFragment

    SmartDialSearchFragment显示拨号搜索结果fragment(在拨号面板输入数字时显示)

    RegularSearchFragment显示联系人搜索结果fragment(在actionbar输入框输入字符时显示)

    在DialtactsActivity中进入或退出搜索模式时动态添加移除

    private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) {
             ...... ............ ......
            if (fragment == null) {
                if (smartDialSearch) {
                    fragment = new SmartDialSearchFragment();
                } else {
                    fragment = ObjectFactory.newRegularSearchFragment();
                      ...... ............ ......
                }
                transaction.add(R.id.dialtacts_frame, fragment, tag);
            } else {
                transaction.show(fragment);
            }
             ...... ............ ......
        }
        private void exitSearchUi() {
             ...... ............ ......
            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
            if (mSmartDialSearchFragment != null) {
                transaction.remove(mSmartDialSearchFragment);
            }
            if (mRegularSearchFragment != null) {
                transaction.remove(mRegularSearchFragment);
            }
            transaction.commit();
    
            mListsFragment.getView().animate().alpha(1).withLayer();
             ...... ............ ......
            mActionBarController.onSearchUiExited();
        }

    smart_search_thumb2

    拨号搜素只能通过拨号面板的输入数字,支持T9搜索,但是原生不支持拼音检索

    public class SmartDialSearchFragment extends SearchFragment{
    
        @Override
        protected ContactEntryListAdapter createListAdapter() {
            SmartDialNumberListAdapter adapter = 
                new SmartDialNumberListAdapter(getActivity());
            adapter.setUseCallableUri(super.usesCallableUri());
            adapter.setQuickContactEnabled(true);
            // Set adapter's query string to restore previous instance state.
            adapter.setQueryString(getQueryString());
            adapter.setListener(this);
            return adapter;
        }
    
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            // Smart dialing does not support Directory Load, falls back to normal search instead.
            if (id == getDirectoryLoaderId()) {
                return super.onCreateLoader(id, args);
            } else {
                final SmartDialNumberListAdapter adapter = 
                     (SmartDialNumberListAdapter) getAdapter();
                SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());
                adapter.configureLoader(loader);
                return loader;
            }
        }
    }

    regular_search_thumb1

    联系人搜索则通过软键盘输入,不过不支持T9搜索

    public class RegularSearchFragment extends SearchFragment{
    
      @Override
        protected ContactEntryListAdapter createListAdapter() {
            RegularSearchListAdapter adapter = new RegularSearchListAdapter(getActivity());
            adapter.setDisplayPhotos(true);
            adapter.setUseCallableUri(usesCallableUri());
            adapter.setListener(this);
            return adapter;
        }
    }

    从类关系图上可以得知两个fragment和对应的adapter都继承于同一个父类,最终都派生自ContactsCommon工程里的模板类ContactEntryListFragment

    public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter>
            extends Fragment{
    
        private T mAdapter;          // 模板adapter
        private View mView;
        private ListView mListView;
    
        private ContactPhotoManager mPhotoManager;  // 头像管理
    
    
        protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
        protected abstract T createListAdapter();        // 子类中实现具体adapter
    
        @Override   // 子类可重写获取数据的Loader
        public Loader<Cursor> onCreateLoader(int id, Bundle args) { 
            if (id == DIRECTORY_LOADER_ID) {
                DirectoryListLoader loader = new DirectoryListLoader(mContext);
                loader.setDirectorySearchMode(mAdapter.getDirectorySearchMode());
                loader.setLocalInvisibleDirectoryEnabled(
                        ContactEntryListAdapter.LOCAL_INVISIBLE_DIRECTORY_ENABLED);
                return loader;
            } else {
                CursorLoader loader = createCursorLoader(mContext);
                long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
                        ? args.getLong(DIRECTORY_ID_ARG_KEY)
                        : Directory.DEFAULT;
                mAdapter.configureLoader(loader, directoryId);
                return loader;
            }
        }
    
    }

    ContactEntryListFragment内部封装了很多操作,绑定了ContactEntryListAdapter,具体细节就不在这里详述了

    1.6小结

    最后附上Dialer里主要类图

  • 相关阅读:
    Python 语言规范(Google)
    Python 代码风格规范(Google)
    GBM,XGBoost,LightGBM
    面试编程总结
    MagicNotes:如何迈向工作的坦途
    番茄工作法:让时间变成你最好的朋友
    时间管理:如何高效地利用时间
    读点大脑科学,学会变得更聪明
    为什么我那么努力,吃了那么多苦,也没见那么优秀?(转自知乎)
    不要被懒惰夺走你的思考能力
  • 原文地址:https://www.cnblogs.com/lance2016/p/6107376.html
Copyright © 2011-2022 走看看