DialogManager 一个Dialog和DialogFragment轻量级框架

DialogManager

一、预览(Preview)

二、导入(Import)

🍎很轻量, 目前只有五个类 (Very lightweight, currently there are only five classes)

repositories {
  maven { url "https://dl.bintray.com/javakam/AndoDialog" }
}

implementation 'ando.dialog:core:1.2.0'
implementation 'ando.dialog:usage:1.2.0'

三、用法(Usage)

fun showLoadingDialog() {
      DialogManager.with(this, R.style.AndoLoadingDialog) //建议设置一个主题样式 (It is recommended to set a theme style)
          .useDialogFragment()//默认为`DialogFragment`实现, useDialog()表示由`Dialog`实现
          .setContentView(R.layout.layout_ando_dialog_loading) { v -> //设置显示布局 (Set display layout)
              v.findViewById<View>(R.id.progressbar_ando_dialog_loading).visibility = View.VISIBLE
          }
          .setTitle("Title")//Need Config `<item name="android:windowNoTitle">false</item>`
          .setWidth(200)//设置宽
          .setHeight(200)//设置高
          .setSize(200, 200)//设置宽高(Set width and height)
          .setAnimationId(R.style.AndoBottomDialogAnimation)//设置动画(Set up animation)
          .setCancelable(true)
          .setCanceledOnTouchOutside(true)
          .setDimAmount(0.7F) //设置背景透明度, 0 ~ 1 之间,0为透明,1为不透明. 只要该值不是 -1, 就会应用该值
          .setDimmedBehind(false) //设置背景透明, false透明, true不透明
          .addOnGlobalLayoutListener { width, height -> }//获取显示后的真实宽高
          .setOnCancelListener {} //取消监听
          .setOnDismissListener {}//关闭监听
          .setOnKeyListener { dialog, keyCode, event -> true }//按键监听
          .setOnShowListener {}//显示监听
          .apply {
              //显示之前配置,如:
              //Display the previous configuration, such as:
              //dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE)
          }
          .show()
          .apply {
              //显示之后配置, 效果和`setOnShowListener`相同
              //Configure after display, the effect is the same as `setOnShowListener`
          }

      //Dialog是否正在显示
      //Whether Dialog is showing
      DialogManager.isShowing()
      
      //Dialog显示后动态改变展示效果
      //Dynamically change the display effect after Dialog is displayed
      changeDialogSize()
}

/**
 * 动态改变显示中的Dialog位置/宽高/动画等
 * Dynamically change the width and height animation of the Dialog in the display, etc.
 */
private fun changeDialogSize() {
    View.postDelayed({
        if (!DialogManager.isShowing()) return@postDelayed

        //改变弹窗宽高 (Change the width and height of the dialog)
        DialogManager.setWidth(280)
        DialogManager.setHeight(280)
        DialogManager.applySize()

        //控制背景亮度 (Control background brightness)
        DialogManager.setDimAmount(0.3F)
        DialogManager.applyDimAmount()
        //or 直接移除背景变暗 (Directly remove the background darkening)
        //DialogManager.dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
        
    }, 1500)
}
  • 同时支持DialogDialogFragment(Support both Dialog and Dialog Fragment):
Dialog: useDialog() ; DialogFragment: useDialogFragment()
  • 控制背景变暗(Control the darkening of the background)
Window.addFlags/clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)

四、Dialog.Window Setting

1. show()之前设置(Set before Dialog.show)

Dialog/Window.requestWindowFeature(Window.FEATURE_LEFT_ICON)

2. show()之后设置(Set after Dialog.show)

  • Window相关属性(WindowManager.LayoutParams), 如动态改变Dialog的位置、宽高、动画、背景等

  • setFeatureXXX 相关方法, 如: setFeatureDrawable/setFeatureDrawableResource/setFeatureDrawableUri/setFeatureDrawableAlpha

  • setFeatureXXX 方法必须在Dialog.show之前设置requestWindowFeature才能生效, 否则出现BUG:java.lang.RuntimeException: The feature has not been requested

  • 🍎 通常在show执行后或者setOnShowListener中设置window属性

setOnShowListener {
    //对`Window`的设置需要在`Dialog`显示后才有效果
    //The setting of `Window` needs to be effective after `Dialog` is displayed
    val attributes = DialogManager.getDialog().window?.attributes
    attributes?.apply {
        width = 800
        height = 500
        gravity = Gravity.CENTER //居中显示 (Center display)
        dimAmount = 0.5f         //背景透明度 (Background transparency)  0 ~ 1
    }
    DialogManager.getDialog().window?.attributes = attributes
}

五、Dialog设置动画(Dialog set animation)

1. 准备配置文件(Prepare configuration file)

anim_ando_dialog_bottom_in.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@integer/integer_ando_dialog_bottom_translate_duration"
    android:fromXDelta="0"
    android:fromYDelta="100%"
    android:toXDelta="0"
    android:toYDelta="0" />

anim_ando_dialog_bottom_out.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@integer/integer_ando_dialog_bottom_translate_duration"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="0"
    android:toYDelta="100%" />

styles.xml

<style name="AndoBottomDialogAnimation">
    <item name="android:windowEnterAnimation">@anim/anim_ando_dialog_bottom_in</item>
    <item name="android:windowExitAnimation">@anim/anim_ando_dialog_bottom_out</item>
</style>

2. 设置动画(Set animation)

Dialog.window.setWindowAnimations(R.style.AndoBottomDialogAnimation)

注意: Dialog设置动画在new BottomDialog(context, R.style.AndoBottomDialogAnimation)时设置是无效的, 必须在show之后再对Window 设置动画(setWindowAnimations)才能生效。经实际测试发现 API 5.0~11都是如此。

六、Dialog设置圆角(Dialog set rounded corners)

DialogsetContentView之后(即show之后)设置window.setBackgroundDrawableResource(R.drawable.rectangle_ando_dialog_bottom)

rectangle_ando_dialog_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp" />

    <solid android:color="@android:color/white" />
</shape>

就是给Window加上个background, 详见👉Dialog 圆角问题

七、Dialog设置宽高(Dialog set width and height)

要在setContentView外包一层FrameLayout防止宽高设置无效问题

🍎 LoadingDialog样式的弹窗提供了两种实现方案,一种是animated-rotate/rotate直接配置动画方式,另一种是android.view.animation.AnimationUtils; 其中的布局文件需要包一层FragmeLayout

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <LinearLayout
        android:layout_width="200dp"
        android:layout_height="150dp"
        android:layout_gravity="center"
        android:orientation="vertical">

        ...
    </LinearLayout>
</FrameLayout>

DialogsetContentView(layoutId)setContentView(view)是不一样的, 如果使用的是layoutId:Int则不需要外面套一层FrameLayout, 但如果是用的view:View, 则必须在自定义布局的最外层在套一层其它布局,如FrameLayout。前者用的是LayoutInflater.inflate(layoutId,mContentParent,true) 而后者用的是mContentParent.addView(view)LayoutInflater.inflate(view,null,false) . 我们看下inflate方法的特性:

inflate(view, null);/inflate(resource, null, true/false);

只创建viewview没有LayoutParams值,然后直接返回view 布局中最外层的layout_widthlayout_height将失效

inflate(resource, mContentParent);/inflate(resource, root, true);

创建view, 然后执行mContentParent.addView(view, params), 最后返回mContentParent

综上所述, Dialog宽高无效问题, 本质上就是LayoutInflater.inflate不同方法之间差异的问题. 其中的mContentParent:ViewGroupPhoneWindow.installDecor()创建. 详见: PhoneWindow.setContentView

DialogManager中已处理该问题(This issue has been dealt with in Dialog Manager)

fun setContentView(
    layoutId: Int,
    block: ((Dialog?, View) -> Unit)? = null
): DialogManager {
    FrameLayout(mContext ?: return this).apply {
        layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        contentView = LayoutInflater.from(mContext).inflate(layoutId, this, true)
        ...
    }
    return this
}

八、总结(Summary)

  1. DialogFragment源码中加载视图用的是 Dialog.setContentView(View)

  2. 如果要改变Window属性, 可以在onStart中处理。因为DialogFragment.onStart中执行了Dialog.show()

九、感谢(Thanks)

Android源码在线阅读 www.androidos.net.cn

Android Dialog - Rounded Corners and Transparency stackoverflow.com/questions/1…

LayoutInflater的正确使用姿势 www.jianshu.com/p/74bb29077…

LayoutInflater中inflate方法的区别 blog.csdn.net/u012702547/…

十、遇到的BUG(BUG encountered)

  • java.lang.RuntimeException: The feature has not been requested

Fixed: see above

  • android.util.AndroidRuntimeException: requestFeature() must be called before adding content

Fixed: setContentView(...)之前设置即可

  • java.lang.IllegalStateException: Fragment xxx not associated with a fragment manager.

  • java.lang.IllegalArgumentException: View not attached to window manager

Fixed: stackoverflow.com/questions/2…

  • WindowManager: android.view.WindowLeaked: Activity ando.dialog.sample.MainActivity has leaked window DecorView@54f9439[MainActivity] that was originally added here

如果只是处理DialogActicity.onConfigurationChanged出现的问题

EN: If you just deal with the problem of Dialog in Activity.onConfigurationChanged

//若`AndroidManifest.xml`中已经配置了`android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"`则不需要设置该项
Acticity/Context.registerComponentCallbacks(object : ComponentCallbacks {
    override fun onConfigurationChanged(newConfig: Configuration) {
        dialog?.dismiss()
    }
    override fun onLowMemory() {
    }
})

onDestroy中也加上销毁

override fun onDestroy() {
    super.onDestroy()
    DialogManager.dismiss()
}
  • java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again

Fixed:

fun addOnGlobalLayoutListener(onGlobalLayout: (width: Int, height: Int) -> Unit): DialogManager {
    contentView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
        ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            contentView?.viewTreeObserver?.removeOnGlobalLayoutListener(this)
            onGlobalLayout.invoke(contentView?.width ?: 0, contentView?.height ?: 0)
        }
    })
    return this
}
posted @ 2021-01-12 16:44  javakam  阅读(0)  评论(0)    收藏  举报  来源