Android7.0 撥號盤應用源碼分析(一) 界面淺析

前言

android撥號盤的源碼目錄在package/app/Dialerandroid

自7.0之後Incallui的源碼直接放到了Dialer目錄下,雖然在7.0之前incallui有本身獨立的目錄,但實際編譯過程當中只是做爲連接庫最後仍是被編譯到Dialer的apk裏git

博主這裏只取Dialer相關的源碼並導入AS中,並稍做調整兼容至Lgithub

源碼目錄結構以下:數據庫

source_thumb2

先理一理各個工程的依賴關係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

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裏主要類圖

相關文章
相關標籤/搜索