DialogPlus解析

簡(jiǎn)介

早些時(shí)候用過(guò)這個(gè)控件,由于業(yè)務(wù)需要也追蹤過(guò)部分源碼 發(fā)現(xiàn)這并不是一個(gè)DialogFragment的衍生類(lèi),而是通過(guò)DecorView進(jìn)行插入框沟,所以他是一個(gè)阻塞式的窗口也就是說(shuō)這個(gè)Dialog一打開(kāi) 其他控件就接收不到焦點(diǎn)了撮奏。
那么為什么要去寫(xiě)這么哥東西呢,首先我覺(jué)得理解這個(gè)隊(duì)員閱讀代碼有一定提升柬唯,然后可以模仿DialogPlus的方式對(duì)界面進(jìn)行操作

基礎(chǔ)

在閱讀源碼前,我們需要了解一些基礎(chǔ)只是,就是Activity的Gui構(gòu)建大致模型(不細(xì)講扛拨,展開(kāi)的話需要閱讀源碼)

mcontentParentView即我們布局加進(jìn)去的地方

如果想要查看布局層級(jí),可以隨便建一個(gè)布局文件举塔,打開(kāi)Hierachy Viewer如下圖所示

Hierarchy Viewer

都有解釋主要看第二層的LinearLayout->FrameLayout這條分支
稍微解釋下某幾個(gè)View绑警,首先是DecorView這是哥FrameLayout的實(shí)現(xiàn)類(lèi),我們的自定義布局其實(shí)都是加到了id/content下央渣,最開(kāi)始是分標(biāo)題欄计盒,狀態(tài)欄跟自定義View實(shí)在屬于不同的分支的,所以如果我們獲取到decorView再對(duì)其進(jìn)行操作即可實(shí)現(xiàn)ui的覆蓋

DialogPlus的使用

要看源碼之前我們先來(lái)看看怎么簡(jiǎn)單使用這個(gè)控件(只介紹最簡(jiǎn)單的用法)
1.首先需要new ViewHolder
2.然后需要一個(gè)adapter(new SimpleAdapter)
3.需要一個(gè)DialogPlus將實(shí)現(xiàn)準(zhǔn)備好的參數(shù)傳入

        final DialogPlus dialog = DialogPlus.newDialog(this)
                .setContentHolder(new ViewHolder(R.layout.content)) 
                .setHeader(R.layout.header)
                .setFooter(R.layout.footer)
                .setCancelable(true)
                .setGravity(gravity)
                .setAdapter(new SimpleAdapter(MainActivity.this, false))
                .setOnClickListener(clickListener)
                .setOnItemClickListener(new OnItemClickListener() { 
                   @Override
                    public void onItemClick(DialogPlus dialog, Object item, View view, int position) {
                        Log.d("DialogPlus", "onItemClick() called with: " + "item = [" +
                                item + "], position = [" + position + "]"); 
                   } 
               })
                .setOnDismissListener(dismissListener)
                .setExpanded(expanded)//
                .setContentWidth(800) 
                .setContentHeight(ViewGroup.LayoutParams.WRAP_CONTENT)
                .setOnCancelListener(cancelListener)
                .setOverlayBackgroundResource(android.R.color.transparent)//
                .setContentBackgroundResource(R.drawable.corner_background)//
                .setOutMostMargin(0, 100, 0, 0)
                .create();
        dialog.show();

具體ViewHolder芽丹,SimpleAdapter控制哪個(gè)地方等下分析源碼再展開(kāi)

源碼分析

分析了使用以后我們發(fā)現(xiàn)所有的操作均集中在Builder內(nèi) 那么我們追蹤到DialogPlus的builder看看

Paste_Image.png

看了這個(gè)結(jié)構(gòu)圖后我們發(fā)現(xiàn)其實(shí)這個(gè)builder的實(shí)質(zhì)是構(gòu)建一個(gè)屬性集合北启,那么就不需要太關(guān)心了,但是我們要注意這個(gè)create()函數(shù)也就是我們生成DialogPlus實(shí)例的方法

public DialogPlus create() {
  getHolder().setBackgroundResource(getContentBackgroundResource());
  return new DialogPlus(this);
}

這是后又要回過(guò)頭看DialogPlus對(duì)應(yīng)的構(gòu)造函數(shù)

DialogPlus(DialogPlusBuilder builder) {
  LayoutInflater layoutInflater = LayoutInflater.from(builder.getContext());
  Activity activity = (Activity) builder.getContext();
  holder = builder.getHolder();
  onItemClickListener = builder.getOnItemClickListener();
  onClickListener = builder.getOnClickListener();
  onDismissListener = builder.getOnDismissListener();
  onCancelListener = builder.getOnCancelListener();
  onBackPressListener = builder.getOnBackPressListener();
  isCancelable = builder.isCancelable();
  /**
   * Avoid getting directly from the decor view because by doing that we are overlapping the black soft key on
   * nexus device. I think it should be tested on different devices but in my opinion is the way to go.
   * @link http://stackoverflow.com/questions/4486034/get-root-view-from-current-activity 
  */
  decorView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
  rootView = (ViewGroup) layoutInflater.inflate(R.layout.base_container, decorView, false);
  rootView.setLayoutParams(builder.getOutmostLayoutParams());
  View outmostView = rootView.findViewById(R.id.dialogplus_outmost_container);
  outmostView.setBackgroundResource(builder.getOverlayBackgroundResource());
  contentContainer = (ViewGroup) rootView.findViewById(R.id.dialogplus_content_container);
  contentContainer.setLayoutParams(builder.getContentParams());
  outAnim = builder.getOutAnimation();
  inAnim = builder.getInAnimation();
  initContentView(
      layoutInflater,
      builder.getHeaderView(),
      builder.getFooterView(),
      builder.getAdapter(),
      builder.getContentPadding(),
      builder.getContentMargin()
  );
  initCancelable();
  if (builder.isExpanded()) {
    initExpandAnimator(activity, builder.getDefaultContentHeight(), builder.getContentParams().gravity);
  }}

這個(gè)就是我們要關(guān)心的重點(diǎn)函數(shù)
那些listener不是我們關(guān)心的重點(diǎn)拔第,所以忽略不講

decorView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);

首先我們根據(jù)activity獲取到了我們自定義view填充的父容器咕村,這里的decorView跟真正意義的decorView有區(qū)別(如上面所講應(yīng)該是DecorView負(fù)責(zé)繪制用戶(hù)定義UI的分支),holder,rootView我們先放著看看下面哪里用到

rootView的布局文件

接下來(lái)我們用rootView填充了outmostView,contentContainer(沒(méi)往下看的時(shí)候我們其實(shí)就已經(jīng)可以猜測(cè)這個(gè)就是我們dialog布局要放的地方蚊俺,接下來(lái)就驗(yàn)證下看看)
來(lái)到第二個(gè)重要的函數(shù)initContentView,里面又嵌套了createView

private void initContentView(LayoutInflater inflater, View header, View footer, BaseAdapter adapter,
                             int[] padding, int[] margin) {
  View contentView = createView(inflater, header, footer, adapter);
  FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
       ViewGroup.LayoutParams.MATCH_PARENT,
       ViewGroup.LayoutParams.MATCH_PARENT
  );
  params.setMargins(margin[0], margin[1], margin[2], margin[3]);
  contentView.setLayoutParams(params);
  getHolderView().setPadding(padding[0], padding[1], padding[2], padding[3]);
  contentContainer.addView(contentView);}

private View createView(LayoutInflater inflater, View headerView, View footerView, BaseAdapter adapter) {
  View view = holder.getView(inflater, rootView);
  if (holder instanceof ViewHolder) {
    assignClickListenerRecursively(view);
  }
  assignClickListenerRecursively(headerView);
  holder.addHeader(headerView);
  assignClickListenerRecursively(footerView);  
  holder.addFooter(footerView);
  if (adapter != null && holder instanceof HolderAdapter) {
    HolderAdapter holderAdapter = (HolderAdapter) holder;
    holderAdapter.setAdapter(adapter);
    holderAdapter.setOnItemClickListener(new OnHolderListener() { 
     @Override
 public void onItemClick(Object item, View view, int position) {
        if (onItemClickListener == null) {
          return;
        }
        onItemClickListener.onItemClick(DialogPlus.this, item, view, position);
      }
    });
  }
  return view;}

這里又牽扯到ViewHolder懈涛,是不是有點(diǎn)繞,還是需要看看ViewHolder泳猬,而且我們也從這個(gè)函數(shù)里看出了ViewHolder與Adapter的關(guān)系
ViewHolder
ViewHolder其實(shí)是Holder接口的一個(gè)實(shí)現(xiàn)批钠,這里的getView是用來(lái)返回整個(gè)布局的,這樣我們才可以添加header得封,footer
BaseAdapter
這個(gè)是一個(gè)數(shù)據(jù)源埋心,適用于GridHolder,ListHolder忙上,注意這邊對(duì)布局有點(diǎn)要求不可隨意定制拷呆,因?yàn)槭孪纫褜?shí)現(xiàn)功能是指定控件跟id掛鉤的
以上總結(jié)是如果你選用了ViewHolder的話adapter是不對(duì)其產(chǎn)生影響的,因?yàn)檫^(guò)不了if (adapter != null && holder instanceof HolderAdapter)的條件(ViewHolder未實(shí)現(xiàn)HolderAdapter接口)
返回到上級(jí)函數(shù)我們只需要關(guān)注contentContainer.addView(contentView);
這句話驗(yàn)證了我們剛才的猜測(cè),因?yàn)槲覀兎祷氐腸ontentView正是我們自定義的顯示View
再回到上級(jí)函數(shù)茬斧,我們開(kāi)始執(zhí)行

private void initCancelable() {
  if (!isCancelable) {
    return;
  }
  View view = rootView.findViewById(R.id.dialogplus_outmost_container);
  view.setOnTouchListener(onCancelableTouchListener);}

這個(gè)函數(shù)很簡(jiǎn)單箫柳,其實(shí)是實(shí)現(xiàn)了點(diǎn)擊內(nèi)容區(qū)域以外關(guān)閉Dialog的操作
前面我們獲取到了outAnim,inAnim屬性,這個(gè)在show跟dismiss中會(huì)用到啥供,看完了初始化我們就來(lái)看看悯恍,
show()
這里面只是改變了一個(gè)狀態(tài)主要邏輯在onAttached函數(shù)內(nèi)

private void onAttached(View view) {
  decorView.addView(view);
  contentContainer.startAnimation(inAnim);
  contentContainer.requestFocus();
  holder.setOnKeyListener(new View.OnKeyListener() {
    @Override
   public boolean onKey(View v, int keyCode, KeyEvent event) {
      switch (event.getAction()) {
        case KeyEvent.ACTION_UP:
          if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (onBackPressListener != null) {
              onBackPressListener.onBackPressed(DialogPlus.this); 
           } 
           if (isCancelable) {
              onBackPressed(DialogPlus.this);
            } 
           return true;
          }
          break;
        default:
          break;
      }
      return false;
    }  });}

最后我們來(lái)看看這個(gè)動(dòng)畫(huà)在哪(Utils內(nèi))

animation集合

這邊采用了系統(tǒng)自帶的當(dāng)然我們也可以自定義
另外還要注意dismiss函數(shù),他在動(dòng)畫(huà)監(jiān)聽(tīng)器里面將我們?cè)瓉?lái)加在decorView的rootView移除了

總結(jié)

源碼解析就到這里伙狐,原理其實(shí)蠻簡(jiǎn)單的涮毫,有很多地方?jīng)]有講解到,主要還是細(xì)節(jié)的把控才能成就一款出色的控件贷屎,文章的精華其實(shí)實(shí)在分析的過(guò)程把罢防,其實(shí)真正的東西也就沒(méi)幾句話,如果資深的開(kāi)發(fā)覺(jué)得不對(duì)的還有望指出唉侄,感覺(jué)浪費(fèi)了您的時(shí)間的也請(qǐng)諒解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咒吐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子属划,更是在濱河造成了極大的恐慌恬叹,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件同眯,死亡現(xiàn)場(chǎng)離奇詭異绽昼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)须蜗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)硅确,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人明肮,你說(shuō)我怎么就攤上這事菱农。” “怎么了柿估?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵循未,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我官份,道長(zhǎng)只厘,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任舅巷,我火速辦了婚禮,結(jié)果婚禮上河咽,老公的妹妹穿的比我還像新娘钠右。我一直安慰自己,他們只是感情好忘蟹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布飒房。 她就那樣靜靜地躺著搁凸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狠毯。 梳的紋絲不亂的頭發(fā)上护糖,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音嚼松,去河邊找鬼嫡良。 笑死,一個(gè)胖子當(dāng)著我的面吹牛献酗,可吹牛的內(nèi)容都是我干的寝受。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼罕偎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼很澄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起颜及,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤甩苛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后俏站,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體浪藻,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年乾翔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爱葵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡反浓,死狀恐怖萌丈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雷则,我是刑警寧澤辆雾,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站月劈,受9級(jí)特大地震影響度迂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜猜揪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一惭墓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧而姐,春花似錦腊凶、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)褐缠。三九已至,卻和暖如春风瘦,著一層夾襖步出監(jiān)牢的瞬間队魏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工万搔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胡桨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓蟹略,卻偏偏與公主長(zhǎng)得像登失,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挖炬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,091評(píng)論 25 707
  • 一.常用控件的使用方法 1.TextView match_parent 由父布局來(lái)決定當(dāng)前控件的大小揽浙。wrap_c...
    努力生活的西魚(yú)閱讀 1,179評(píng)論 5 0
  • 在android開(kāi)發(fā)中Listview是一個(gè)很重要的組件,它以列表的形式根據(jù)數(shù)據(jù)的長(zhǎng)自適應(yīng)展示具體內(nèi)容,用戶(hù)可以自...
    丶萌面超人閱讀 1,311評(píng)論 0 13
  • 前段時(shí)間做高鐵意敛,檢票的時(shí)候馅巷,身后有人叫我,我扭頭 “阿水”草姻?我有些不確定钓猬,因?yàn)檠矍暗娜吮任耶?dāng)初認(rèn)識(shí)她的時(shí)候漂亮多了...
    芃方閱讀 361評(píng)論 1 1
  • 從小我便對(duì)火車(chē)有種近乎執(zhí)念的想法,無(wú)論看過(guò)什么風(fēng)景等過(guò)怎樣的時(shí)光撩独,腦海中一次次重復(fù)的都是頂著亮光而來(lái)的呼嘯敞曹。...
    端木酸奶包閱讀 215評(píng)論 0 1