Android 拨号盘应用源码分析

拨号盘概览

先来看看几张原图
 

m1_thumb3

m2_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源码

关键类

DialtactsActivity

listsfragment20757_thumb1

 

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

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

 

OldSpeedDialFragment显示常用联系人列表

// 查询源数据的LoaderCallbacks
private static final class ContactTileLoaderListener
      implements LoaderManager.LoaderCallbacks<Cursor> {

    private final OldSpeedDialFragment fragment;
    // 源数据BaseAdapter
    private final PhoneFavoritesTileAdapter adapter;

    ContactTileLoaderListener(OldSpeedDialFragment fragment, PhoneFavoritesTileAdapter adapter) {
      this.fragment = fragment;
      this.adapter = adapter;
    }

    @Override
    public CursorLoader onCreateLoader(int id, Bundle args) {
      return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(fragment.getContext());
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
      adapter.setContactCursor(data);
      fragment.setEmptyViewVisibility(adapter.getCount() == 0);
      FragmentUtils.getParentUnsafe(fragment, HostInterface.class)
          .setHasFrequents(adapter.getNumFrequents() > 0);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {}
  }

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_thumb1

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

SmartDialSearchFragment 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,具体细节就不在这里详述了。
最后附上Dialer主要类图:

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值