android撥號盤的源碼目錄在package/app/Dialerandroid
自7.0之後Incallui的源碼直接放到了Dialer目錄下,雖然在7.0之前incallui有本身獨立的目錄,但實際編譯過程當中只是做爲連接庫最後仍是被編譯到Dialer的apk裏git
博主這裏只取Dialer相關的源碼並導入AS中,並稍做調整兼容至Lgithub
源碼目錄結構以下:數據庫
先理一理各個工程的依賴關係app
com.android.dialer是主工程依賴於ide
com.android.contacts.common工程和com.android.phone.common工程佈局
com.android.contacts.common又依賴於動畫
com.android.phone.common工程和com.android.common工程ui
另一些support包也做爲連接工程被引入,以上代碼均取自google源碼this
github下載連接:https://github.com/geniusgithub/AndroidDialer
先來看看幾張原圖
主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); }
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); }
數據來源包括收藏的聯繫人以及有通話記錄的聯繫人
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(); } } }
當處於通話狀態時顯示以下
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,具體細節就不在這裏詳述了
最後附上Dialer裏主要類圖