前言

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

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

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

源码目录结构如下:

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

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拨号盘概览

先来看看几张原图

1.2 DialtactsActivity

主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页,国内运营商暂不支持

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;
} }

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

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();
}
}
}

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

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();
}

拨号搜素只能通过拨号面板的输入数字,支持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;
}
}
}

联系人搜索则通过软键盘输入,不过不支持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里主要类图

Android7.0 拨号盘应用源码分析(一) 界面浅析的更多相关文章

  1. Android7.0 Phone应用源码分析(一) phone拨号流程分析

    1.1 dialer拨号 拨号盘点击拨号DialpadFragment的onClick方法会被调用 public void onClick(View view) { int resId = view. ...

  2. Android7.0 Phone应用源码分析(二) phone来电流程分析

    接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有 ...

  3. Android7.0 Phone应用源码分析(三) phone拒接流程分析

    本文主要分析Android拒接电话的流程,下面先来看一下拒接电话流程时序图 步骤1:滑动按钮到拒接图标,会调用到AnswerFragment的onDecline方法 com.android.incal ...

  4. Android7.0 Phone应用源码分析(四) phone挂断流程分析

    电话挂断分为本地挂断和远程挂断,下面我们就针对这两种情况各做分析 先来看下本地挂断电话的时序图: 步骤1:点击通话界面的挂断按钮,会调用到CallCardPresenter的endCallClicke ...

  5. Android 7.0 Gallery图库源码分析3 - 数据加载及显示流程

    前面分析Gallery启动流程时,说了传给DataManager的data的key是AlbumSetPage.KEY_MEDIA_PATH,value值,是”/combo/{/local/all,/p ...

  6. Backbone.js 0.9.2 源码分析收藏

    Backbone 为复杂Javascript应用程序提供模型(models).集合(collections).视图(views)的结构.其中模型用于绑定键值数据和自定义事件:集合附有可枚举函数的丰富A ...

  7. [Abp vNext 源码分析] - 21. 界面与文字的本地化

    一.简介 ABP vNext 提供了全套的本地化字符串支持,具体用法可以参考官方使用文档.vNext 本身是对 Microsoft 提供的本地化组件进行了实现,通过 JSON 文件提供本地化源,这一点 ...

  8. Android 7.0 Gallery图库源码分析1 - 初识Gallery源码

    分析一个项目的源代码时,第一件事就是查看清单文件,找到程序入口,我们从Gallery2源码的清单文件中可以看到GalleryActivity是此应用的启动Activity. <activity ...

  9. Java SPI、servlet3.0与@HandlesTypes源码分析

    关于Java SPI与servlet3.0的应用,这里说的很精炼,链接地址如下. https://blog.csdn.net/pingnanlee/article/details/80940993 以 ...

随机推荐

  1. 剑指offer系列42---二叉树深度

    [题目]输入一棵二叉树,求该树的深度. * 从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度. package com.exe9.offer; /** * [题 ...

  2. Redis 宣言(Redis Manifesto)

    Redis 的作者 antirez(Salvatore Sanfilippo)曾经发表了一篇名为 Redis 宣言(Redis Manifesto)的文章,文中列举了 Redis 的七个原则,以向大家 ...

  3. GCC 编译使用动态链接库和静态链接库

    1 库的分类 根据链接时期的不同,库又有静态库和动态库之分. 静态库是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行. 有 ...

  4. 如何配置apache最大的并发数

    如何配置apache最大的并发数MPM(多路处理模块)常见:1.perfork 预处理进程方式2.worker 工作者模式3.winnt 在windows使用 案例:把apache的最大并发数配置成1 ...

  5. Maven本地安装JAR包组件

    http://www.mkyong.com/maven/how-to-add-oracle-jdbc-driver-in-your-maven-local-repository/ mvn instal ...

  6. Well, let's start everything from the very beginning.

    帝都的霾仿佛亘古不变,不知觉2015年竟已快到尾声,而我在IBM也已呆了4个月.回顾过往多遇贵人,所获颇丰.最幸运的还是自己不忘初心,仍在不断成长.继续学习. 过去的几个月一直用WordPress,搭 ...

  7. OAF_OAF Debug And Log调试和记录工具的详解(案例)

    2014-06-16 Created By BaoXinjian

  8. linux命令(5)文件操作:ls命令、显示文件总个数

    一:ls命令是最常用的linux命令了:下面是ls --help里面的用法 在提示符下输入ls --help ,屏幕会显示该命令的使用格式及参数信息: 先介绍一下ls命令的主要参数: -a 列出目录下 ...

  9. mac eclipse 下安装subclipse

    参考 http://www.cnblogs.com/yinxiangpei/articles/3859057.html 推荐安装homebrew 在安装javahl时注意版本对应 http://sub ...

  10. CQL操作

    http://docs.datastax.com/en/cql/3.1/pdf/cql31.pdf CQL是Cassandra Query Language的缩写,目前作为Cassandra默认并且主 ...