Android7.0 拨号盘应用源码分析(一) 界面浅析
前言
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里主要类图