DialogPlus解析

简介

早些时候用过这个控件,由于业务需要也追踪过部分源码 发现这并不是一个DialogFragment的衍生类,而是通过DecorView进行插入,所以他是一个阻塞式的窗口也就是说这个Dialog一打开 其他控件就接收不到焦点了。
那么为什么要去写这么哥东西呢,首先我觉得理解这个队员阅读代码有一定提升,然后可以模仿DialogPlus的方式对界面进行操作

基础

在阅读源码前,我们需要了解一些基础只是,就是Activity的Gui构建大致模型(不细讲,展开的话需要阅读源码)

mcontentParentView即我们布局加进去的地方

如果想要查看布局层级,可以随便建一个布局文件,打开Hierachy Viewer如下图所示

Hierarchy Viewer

都有解释主要看第二层的LinearLayout->FrameLayout这条分支
稍微解释下某几个View,首先是DecorView这是哥FrameLayout的实现类,我们的自定义布局其实都是加到了id/content下,最开始是分标题栏,状态栏跟自定义View实在属于不同的分支的,所以如果我们获取到decorView再对其进行操作即可实现ui的覆盖

DialogPlus的使用

要看源码之前我们先来看看怎么简单使用这个控件(只介绍最简单的用法)
1.首先需要new ViewHolder
2.然后需要一个adapter(new SimpleAdapter)
3.需要一个DialogPlus将实现准备好的参数传入

        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控制哪个地方等下分析源码再展开

源码分析

分析了使用以后我们发现所有的操作均集中在Builder内 那么我们追踪到DialogPlus的builder看看

Paste_Image.png

看了这个结构图后我们发现其实这个builder的实质是构建一个属性集合,那么就不需要太关心了,但是我们要注意这个create()函数也就是我们生成DialogPlus实例的方法

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

这是后又要回过头看DialogPlus对应的构造函数

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

这个就是我们要关心的重点函数
那些listener不是我们关心的重点,所以忽略不讲

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

首先我们根据activity获取到了我们自定义view填充的父容器,这里的decorView跟真正意义的decorView有区别(如上面所讲应该是DecorView负责绘制用户定义UI的分支),holder,rootView我们先放着看看下面哪里用到

rootView的布局文件

接下来我们用rootView填充了outmostView,contentContainer(没往下看的时候我们其实就已经可以猜测这个就是我们dialog布局要放的地方,接下来就验证下看看)
来到第二个重要的函数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,是不是有点绕,还是需要看看ViewHolder,而且我们也从这个函数里看出了ViewHolder与Adapter的关系
ViewHolder
ViewHolder其实是Holder接口的一个实现,这里的getView是用来返回整个布局的,这样我们才可以添加header,footer
BaseAdapter
这个是一个数据源,适用于GridHolder,ListHolder,注意这边对布局有点要求不可随意定制,因为事先已实现功能是指定控件跟id挂钩的
以上总结是如果你选用了ViewHolder的话adapter是不对其产生影响的,因为过不了if (adapter != null && holder instanceof HolderAdapter)的条件(ViewHolder未实现HolderAdapter接口)
返回到上级函数我们只需要关注contentContainer.addView(contentView);
这句话验证了我们刚才的猜测,因为我们返回的contentView正是我们自定义的显示View
再回到上级函数,我们开始执行

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

这个函数很简单,其实是实现了点击内容区域以外关闭Dialog的操作
前面我们获取到了outAnim,inAnim属性,这个在show跟dismiss中会用到,看完了初始化我们就来看看,
show()
这里面只是改变了一个状态主要逻辑在onAttached函数内

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

最后我们来看看这个动画在哪(Utils内)

animation集合

这边采用了系统自带的当然我们也可以自定义
另外还要注意dismiss函数,他在动画监听器里面将我们原来加在decorView的rootView移除了

总结

源码解析就到这里,原理其实蛮简单的,有很多地方没有讲解到,主要还是细节的把控才能成就一款出色的控件,文章的精华其实实在分析的过程把,其实真正的东西也就没几句话,如果资深的开发觉得不对的还有望指出,感觉浪费了您的时间的也请谅解

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 158,304评论 24 688
  • 一.常用控件的使用方法 1.TextView match_parent 由父布局来决定当前控件的大小。wrap_c...
    魔都吴小猛阅读 739评论 5 0
  • 前段时间做高铁,检票的时候,身后有人叫我,我扭头 “阿水”?我有些不确定,因为眼前的人比我当初认识她的时候漂亮多了...
    芃方阅读 165评论 1 1
  • 从小我便对火车有种近乎执念的想法,无论看过什么风景等过怎样的时光,脑海中一次次重复的都是顶着亮光而来的呼啸。...
    端木酸奶包阅读 71评论 0 1
  • 1、Ruby环境搭建 a、查看ruby版本: ruby -v(保证2.2以上版本,如果pod安装失败了,则更新) ...
    ZhangCc_阅读 84评论 0 1