Android-DialogX使用与分享


 1 底部可拖动Dialog

1.1 效果展示

1.2 实现细节

        说在前面,我们使用了开源库去实现,感谢作者Kongzue。

        如上图,我们可直接使用BottomDialog,根据UI效果做一定改造即可,实现细节参考如下:

1)自定义Dialog布局:dialog_bottom_subscribe_notification.xml。

 2)调用BottomDialog.show(),并设置背景透明(去掉默认白色背景),以及设置压黑遮罩(默认压黑遮罩颜色太浅)

BottomDialog.show(OnBindView).setBackgroundColorRes(R.color.color_00000000).setMaskColor(R.color.color_99000000)

3)将自定义布局Bind到Dialog,去掉BottomDialog顶部滑动提示条,实现自己的View逻辑。

OnBindView = object : OnBindView<BottomDialog>(R.layout.dialog_bottom_subscribe_notification) {
    override fun onBind(dialog: BottomDialog, v: View) {
        // 去掉默认滑动提示条
        if (dialog.dialogImpl.imgTab != null) {
            (dialog.dialogImpl.imgTab.parent as ViewGroup).removeView(dialog.dialogImpl.imgTab)
        }

        // 实现自己的View逻辑...
    }
}

4)可选:将View逻辑处理点击等回调,抛给调用者处理。

        下面我们拿视频中的Switch举例:

        a、回调声明:之前采用CallBack方式实现,在Kotlin中可采用高阶函数作为方法参数的方式实现。

fun showSubscribe(isChecked: Boolean, selectCallback: (isSelect: Boolean, switch: SwitchCompat) -> Unit)

        b、回调抛给调用者处理。

v.findViewById<SwitchCompat>(R.id.notificationSwitch).setOnCheckedChangeListener { _, isChecked ->
    selectCallback(isChecked, notificationSwitch)
}

        c、调用者处理回调。

showSubscribe(isChecked) { isSelect: Boolean, switch: SwitchCompat -> 
    if (isSelect) {
        switch.isChecked = false
        // ...
    } else {
        // ...
    }
}

1.3 代码参考

/**
 * Description:    Notifications Subscribe的弹窗
 * CreateDate:     2022/6/10 14:55
 * Author:         agg
 */
object SubscribeNotificationDialog {

    /**
     * Notifications Subscribe的弹窗
     */
    @JvmStatic
    fun showSubscribe(
        name: String,
        isChecked: Boolean,
        selectCallback: (isSelect: Boolean, switch: SwitchCompat) -> Unit
    ) {
        BottomDialog.show(object :
            OnBindView<BottomDialog>(R.layout.dialog_bottom_subscribe_notification) {
            override fun onBind(dialog: BottomDialog, v: View) {
                // 去掉默认滑动提示条
                if (dialog.dialogImpl.imgTab != null) {
                    (dialog.dialogImpl.imgTab.parent as ViewGroup).removeView(dialog.dialogImpl.imgTab)
                }

                val notificationDesc = v.findViewById<CustomStrokeTextView>(R.id.notificationDesc)
                notificationDesc.text = String.format(
                    ApplicationUtils.getApplication().getString(R.string.a_notification_desc), name
                )

                val notificationSwitch = v.findViewById<SwitchCompat>(R.id.notificationSwitch)
                notificationSwitch.isChecked = isChecked
                notificationSwitch.setOnCheckedChangeListener { _, isChecked ->
                    selectCallback(isChecked, notificationSwitch)
                }
            }
        }).setBackgroundColorRes(R.color.color_transparent)
            .setMaskColor(ApplicationUtils.getApplication().resources.getColor(R.color.color_99000000))
    }
    
}

2 居中Dialog

2.1 效果展示

2.2 实现细节

        居中Dialog使用CustomDialog即可,实现细节参考如下:

1)自定义Dialog布局:layout_airdrop_dialog.xml。

 2)调用CustomDialog.show(),并设置压黑遮罩,以及设置全屏显示。

CustomDialog.build().setCustomView(OnBindView).setMaskColor(R.color.color_99000000).setCancelable(true).setFullScreen(true)

3)将自定义布局Bind到Dialog,并实现自己的View逻辑。

OnBindView = object : OnBindView<CustomDialog>(R.layout.layout_airdrop_dialog) {
    override fun onBind(dialog: CustomDialog, v: View) {
        binding.parent.setOnCustomClickListener {
            dialog.dismiss()  
        }
        binding.bg.setOnCustomClickListener {
        }
        
        // 实现自己的View逻辑...
    }
}

4)可选:居中布局,需要考虑Android碎片化问题,尽可能使用比例去动态布局,减少使用具体的dp、px或sp值。

        a、比如,布局文件的ImageView-bg控件,使用layout_constraintDimensionRatio比例属性:

<ImageView
    android:id="@+id/bg"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginHorizontal="36dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintDimensionRatio="303:339"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

        b、再比如,ConstraintLayout的另一个属性,layout_constraintWidth_percent可以限定控件所占父控件的尺寸比例:

2.3 代码参考

/**
 * Description:
 * CreateDate:     2022/8/30 11:06
 * Author:         agg
 */
object AirdropRewardsDialog {

    fun show(
        airdropInfo: AirdropInfo,
        isFromPopup: Boolean = false,
        callback: () -> Unit
    ) {
        CustomDialog.build().setCustomView(object :
            OnBindView<CustomDialog>(R.layout.layout_airdrop_dialog) {
            override fun onBind(dialog: CustomDialog, v: View) {
                binding.parent.setOnCustomClickListener {
                    dialog.dismiss()  
                }
                binding.bg.setOnCustomClickListener {
                }
        
                // 实现自己的View逻辑...
            }
        }).setMaskColor(ColorUtils.getColor(R.color.color_99000000))
            .setCancelable(true)
            .setFullScreen(true)
            .show()
    }
}

3 Dialog闲谈

3.1 内存泄漏与Dialog

        从步骤(1)-(5),我们可以看到DialogX内部会调用application.registerActivityLifecycleCallbacks,在activity销毁时会清除Dialog,并消除对它的引用。

        由此可见,开源者已考虑过此处可能引起的内存泄漏。 

3.2 Context与Dialog

        我们可以看到,show一个Dialog可以传参也可不传,这两种方式有什么区别呢?

 

3.2.1 先来看看带Activity参数的show方法

  • 由于初始化并未设置dialogImplMode,所以此处会走到default分支;

  • 然后通过Activity拿到getDecorView,并把Dialog的view通过addView(view)添加到根布局中。

3.2.2 再来看看不带参数的show方法

        可以看到Dialog的view通过addView(view)被添加到rootFrameLayout布局中。那么rootFrameLayout是什么布局呢?这就得从源头一步一步展开看看。

1)show方法中调用父类beforeShow方法,在beforeShow获取topActivity

public void show() {
    // 调用父类beforeShow方法
    super.beforeShow();
    if (getDialogView() == null) {
        dialogView = createView(R.layout.layout_dialogx_custom);
        dialogImpl = new DialogImpl(dialogView);
        if (dialogView != null) dialogView.setTag(me);
    }
    show(dialogView);
}

protected void beforeShow() {
    dismissAnimFlag = false;
    // 获取topActivity
    if (getTopActivity() == null) {
        init(null);
        if (getTopActivity() == null) {
            error("DialogX 未初始化。\n请检查是否在启动对话框前进行初始化操作,使用以下代码进行初始化:\nDialogX.init(context);\n\n另外建议您前往查看 DialogX 的文档进行使用:https://github.com/kongzue/DialogX");
            return;
        }
    }
    // ...
}

2)getTopActivity方法中,activityWeakReference为null,会去调用init(null)方法

public static Activity getTopActivity() {
    if (activityWeakReference == null) {
        // 调用init(null)方法
        init(null);
        if (activityWeakReference == null) {
            return ActivityLifecycleImpl.getTopActivity();
        }
        return activityWeakReference.get();
    }
    return activityWeakReference.get();
}

3)init(null)方法会调用ActivityLifecycleImpl.getTopActivity()方法,并调用initActivityContext方法

public static void init(Context context) {
    if (context == null) {
        // 调用ActivityLifecycleImpl.getTopActivity()方法
        context = ActivityLifecycleImpl.getTopActivity();
    }
    if (context instanceof Activity) {
        // 调用initActivityContext方法
        initActivityContext((Activity) context);
    }
    ActivityLifecycleImpl.init(context, new ActivityLifecycleImpl.onActivityResumeCallBack() {
        @Override
        public void getActivity(Activity activity) {
            initActivityContext(activity);
        }
    });
}

4)initActivityContext方法,将当前Activity的decorView赋值给rootFrameLayout变量

private static void initActivityContext(Activity activity) {
    try {
        uiThread = Looper.getMainLooper().getThread();
        activityWeakReference = new WeakReference<>(activity);
        // 将当前Activity的decorView赋值给rootFrameLayout变量
        rootFrameLayout = new WeakReference<>((FrameLayout) activity.getWindow().getDecorView());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            publicWindowInsets(rootFrameLayout.get().getRootWindowInsets());
        }
    } catch (Exception e) {
        e.printStackTrace();
        error("DialogX.init: 初始化异常,找不到Activity的根布局");
    }
}

        从上面源码中,我们知道了rootFrameLayout其实就是,应用当前顶部Activity的decorView。

        再回过头来看看不带参数的show方法,一切就明了了,当不传Activity时,就会拿当前当前顶部Activity作为dialog的根布局。

3.2.3 小结

  • 调用带Activity参数的show方法时,Dialog布局依赖于传入Activity,布局将添加进此Activity中;

  • 调用不带Activity参数的show方法时,Dialog布局依赖于应用当前顶部Activity,布局添加进应用当前顶部Activity中。

3.3 后台弹Dialog

        基于3.2的小结,我们知道:

  • 如果Dialog不希望在其他界面弹出,则需要指定Activity;

  • 如果Dialog不依赖于某个具体页面,可不传Activity,在任何界面都可弹出。

        简单来说,当Dialog不传Activity时,此Dialog可依赖于应用当前顶部Activity而弹出,从而实现后台触发任意界面弹Dialog。

 

猜你喜欢

转载自blog.csdn.net/Agg_bin/article/details/127536490