从 Android
诞生起,Dialog
就跟随着用户的使用习惯,不停的改变样式,从开始的Dialog
到之后 DialogFragment
,不仅仅是外观的变化;
Dialog
自身是无法进行显示的,底层借助了 View
以及 Window
,才能和用户进行交互,不过由于应用重启时,会伴随着 Context
对象的创建与销毁,此时 还在显示的 Dialog
会因为原寄主对象不存在而导致应用崩溃。
之后为了解决此类问题,官方提供了 DialogFragment
,原理在于:让dialog的显示与隐藏
与一个fragment的声明周期
进行绑定,当Activity
由于屏幕旋转等原因销毁时,通过 fragment 来控制dialog
先行关闭,由此来避免崩溃等问题。
虽然说DialogFragment
“任务” 完成的不错,但 Dialog 与 DialogFragment 需要编写大量的代码,用于Activity 与 Dialog 进行数据交互;即便是采用了 Builder设计模式
,依然摆脱不了繁琐的事实;
不过幸好,还有其他方法可以完成 Dialog 的工作。
近期出现的Flutter可谓是一剂良方,创建一个项目然后打开其源码,会发现 如下代码:
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
一般而言,我们都是在 onCreate
方法中添加 layout布局 ,然后由系统自动进行加载显示的。那么Flutter在这里做了什么?
跟随源码继续进入:
public class FlutterActivity extends Activity implements Provider, PluginRegistry, ViewFactory {
//....
private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
private final FlutterActivityEvents eventDelegate;
//....
public FlutterActivity() {
this.eventDelegate = this.delegate;
this.viewProvider = this.delegate;
// ...
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.eventDelegate.onCreate(savedInstanceState);
}
}
这里出现的 eventDelegate 其实是一个代理,每当Activity 到了某个生命周期时,会调用该代理类对应的生命周期方法。
在构造方法中,可以看到其实 eventDelegate 是 FlutterActivityDelegate 类型,那就继续进入 FlutterActivityDelegate 中,查看 onCreate 时,做了什么:
public final class FlutterActivityDelegate implements FlutterActivityEvents, Provider, PluginRegistry {
//...
public void onCreate(Bundle savedInstanceState) {
if (VERSION.SDK_INT >= 21) {
// ...
}
// ...
if (this.flutterView == null) {
FlutterNativeView nativeView = this.viewFactory.createFlutterNativeView();
this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView);
this.flutterView.setLayoutParams(matchParent);
// 着重这一行
this.activity.setContentView(this.flutterView);
// ...
}
// ...
}
// ...
}
其他部分 我们先不考虑,只看中间标注的一行,这就说明:Activity 中,只加载 了一个 View——flutterView
。
flutterView 是什么?
public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
// ...
}
在结合 Flutter 官方给出的一个图表:
我们有理由相信,其实在Android端,Flutter 通过一个可以自己进行绘制逻辑的 SurfaceView 完成了整体的构建。
看完了Flutter,接下来借用一个 Cordova 项目来看一下其实现原理;
同样的,我们查看应用的第一个 Activity
public class MainActivity extends CordovaActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// enable Cordova apps to be started in the background
Bundle extras = getIntent().getExtras();
if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
moveTaskToBack(true);
}
// Set by in config.xml
loadUrl(launchUrl);
}
}
然后进入 loadUrl 方法中:
public class CordovaActivity extends Activity {
// ...
public void loadUrl(String url) {
if (appView == null) {
init();
}
// If keepRunning
this.keepRunning = preferences.getBoolean("KeepRunning", true);
appView.loadUrlIntoView(url, true);
}
// ...
}
接着是进入 init 方法:
public class CordovaActivity extends Activity {
// ...
protected void init() {
appView = makeWebView();
createViews();
if (!appView.isInitialized()) {
appView.init(cordovaInterface, pluginEntries, preferences);
}
cordovaInterface.onCordovaInit(appView.getPluginManager());
// Wire the hardware volume controls to control media if desired.
String volumePref = preferences.getString("DefaultVolumeStream", "");
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
}
// ...
}
这时,通过 makeWebView
方法,创建了一个 CordovaWebView
的实例,然后在 createViews
方法中:
protected void createViews() {
//Why are we setting a constant as the ID? This should be investigated
appView.getView().setId(100);
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(appView.getView());
// ...
}
ok,到这就可以看懂,其实 Cordova 项目对于安卓来说 ,也是放入了一个布局:appView.getView()
该方法层层跟踪,其实可以看到是这样的:
public class CordovaWebViewImpl implements CordovaWebView {
public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) {
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
try {
Class<?> webViewClass = Class.forName(className);
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
} catch (Exception e) {
throw new RuntimeException("Failed to create webview. ", e);
}
}
}
最终创建过程在 SystemWebView
类中
即,通过反射最后生成了一个 WebView 的包装类,然后获取到 WebView 后,添加到了Activity中,接着所有的界面通过网页进行加载和显示。
通过对 Flutter 项目 和 Cordova 的观察,我们不禁想,是否可以利用这样的方法,模拟出 dialog 来,而不用每次都去创建 dialog (DialogFragment形式同样需要先创建出 dialog)?
使用WebView当然不行,那样逻辑更复杂了;
使用SurfaceView 呢?当然还是不行,如果要自己一点一点绘制出dialog来,着实没有必要。
那么最后,只能去考虑普通的 View 类了,事实上,Dialog 本身用于显示的部分,也只是其中的 contentView 而已。
用 view 来模拟 dialog,其实跟平时写布局文件一样,只是为了更形象一些,在根部局尽量使用 CardView 来设置一定z轴高度以及弧度。例如这样:
dialog_first.xml
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:cardBackgroundColor="#ee654321"
app:cardCornerRadius="5dp"
app:cardElevation="@dimen/cardview_default_elevation"
android:layout_marginStart="48dp"
android:layout_marginEnd="48dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:textColor="#FFFFFF"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="我是标题:点击确认,弹窗不会消失"/>
<Button
android:id="@+id/bt_submit"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="32dp"
android:gravity="center"
android:text="确认"/>
LinearLayout>
android.support.v7.widget.CardView>
样式大概 是这样的:
单就样式而言,与一般的 dialog 已经没有太大的区别了。
接下来我们考虑怎么控制这个 view 在界面中进行显示:
能否把该 布局
include
到activity 的布局
中,然后通过visibility
属性控制显示与隐藏?
理论上当然可以,每次弹出时候,屏蔽掉其他所有控件的点击等事件,不过这样一来,控件的显示和隐藏逻辑会很复杂,还不如使用 Dialog
使用 include 方式最大的短板,还是代码量过大,逻辑太复杂,如果可以有一种方式,在编写dialog 完成后,可以直接进行显示或者隐藏就好了,那样代码量会减少很多。
控制视图显示或者隐藏,最好的布局就是 FrameLayout,因为它比较简单,自身布局的显示和隐藏不会对其他布局产生很大影响;因此我们可以找一个FrameLayout,然后由FrameLayout来控制创建好的 dialog。
事实上,根据安卓的布局结构来说,已经自带了FrameLayout布局:
有 toolbar 的界面布局 大概这个样子:
(不考虑中间遮挡到的许多按钮)把这个布局展开,会是这样的:
<com.android.internal.policy.DecorView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="720px"
android:layout_height="1280px">
<LinearLayout
android:layout_width="720px"
android:layout_height="1280px">
<ViewStub
android:id="@+id/action_mode_bar_stub"
android:layout_width="720px"
android:layout_height="48px" />
<FrameLayout
android:layout_width="720px"
android:layout_height="1232px">
<android.support.v7.widget.ActionBarOverlayLayout
android:id="@+id/decor_content_parent"
android:layout_width="720px"
android:layout_height="1232px">
<android.support.v7.widget.ActionBarContainer
android:id="@+id/action_bar_container"
android:layout_width="720px"
android:layout_height="112px">
<android.support.v7.widget.Toolbar
android:id="@+id/action_bar"
android:layout_width="720px"
android:layout_height="112px">
android.support.v7.widget.Toolbar>
<android.support.v7.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="0px"
android:layout_height="0px">
android.support.v7.widget.ActionBarContextView>
android.support.v7.widget.ActionBarContainer>
<android.support.v7.widget.ContentFrameLayout
android:id="@+id/content"
android:layout_width="720px"
android:layout_height="1120px">
android.support.v7.widget.ContentFrameLayout>
android.support.v7.widget.ActionBarOverlayLayout>
FrameLayout>
LinearLayout>
<View
android:id="@+id/statusBarBackground"
android:layout_width="720px"
android:layout_height="48px"/>
com.android.internal.policy.DecorView>
也就是说,我们在开始布局时,已经有了五层 parent
存在。
全屏状态 的界面布局 大概这个样子:
同样的布局文件类似如此:
<com.android.internal.policy.DecorView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="720px"
android:layout_height="1280px">
<LinearLayout
android:layout_width="720px"
android:layout_height="1280px">
<ViewStub
android:id="@+id/action_mode_bar_stub"
android:layout_width="720px"
android:layout_height="0px" />
<FrameLayout
android:layout_width="720px"
android:layout_height="1280px">
<android.support.v7.widget.FitWindowsLinearLayout
android:id="@+id/action_bar_root"
android:layout_width="720px"
android:layout_height="1280px">
<android.support.v7.widget.ViewStubCompat
android:id="@+id/action_mode_bar_stub"
android:layout_width="0px"
android:layout_height="0px">
android.support.v7.widget.ViewStubCompat>
<android.support.v7.widget.ContentFrameLayout
android:id="@+id/content"
android:layout_width="720px"
android:layout_height="1280px">
android.support.v7.widget.ContentFrameLayout>
android.support.v7.widget.FitWindowsLinearLayout>
FrameLayout>
LinearLayout>
com.android.internal.policy.DecorView>
没有 toolbar 的界面布局 大概这个样子(与全屏状态相比,多了状态栏):
布局文件类似为:
<com.android.internal.policy.DecorView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="720px"
android:layout_height="1280px">
<LinearLayout
android:layout_width="720px"
android:layout_height="1280px">
<ViewStub
android:id="@+id/action_mode_bar_stub"
android:layout_width="720px"
android:layout_height="48px" />
<FrameLayout
android:layout_width="720px"
android:layout_height="1232px">
<android.support.v7.widget.FitWindowsLinearLayout
android:id="@+id/action_bar_root"
android:layout_width="720px"
android:layout_height="1232px">
<android.support.v7.widget.ViewStubCompat
android:id="@+id/action_mode_bar_stub"
android:layout_width="0px"
android:layout_height="0px">
android.support.v7.widget.ViewStubCompat>
<android.support.v7.widget.ContentFrameLayout
android:id="@+id/content"
android:layout_width="720px"
android:layout_height="1232px">
android.support.v7.widget.ContentFrameLayout>
android.support.v7.widget.FitWindowsLinearLayout>
FrameLayout>
LinearLayout>
<View
android:id="@+id/statusBarBackground"
android:layout_width="720px"
android:layout_height="48px"/>
com.android.internal.policy.DecorView>
附 : 控制上面显示效果的 style属性包括如下
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
- "colorPrimary"
>@color/colorPrimary
- "colorPrimaryDark">@color/colorPrimaryDark
- "colorAccent">@color/colorAccent
- "android:windowFullscreen">true
- "windowActionBar">false
- "windowNoTitle">true
--android:windowTranslucentStatus模式下,布局样式和 全屏模式类似,因此此时 statusBar处于悬浮状态,不占用布局部分(可以理解为单独的window窗口)-->
- "android:windowTranslucentStatus">true
style>
resources>
总结来看,即便界面没有任何内容,父布局层级中也会有两个 FrameLayout
布局(实际上DecorView 也是FrameLayout子类,但由于该类不可见 hide模式 ,所以这里先不考虑),其中第一个布局没有id,因此不好获取,第二个布局就是 我们经常说的content
布局了。
可见看到,几种模式下,布局的大小不尽相同,而我们要做的,是要针对所有的情况,都可以使用同一种方式去显示dialog。
那么我们使用 DecorView 是否可行?
这个问题留待下次再说,我们目前仅以一种简单的方式来实现此种逻辑。
上面提到,要统一的进行弹窗,需要用到一个 FrameLayout 布局,充当 dialog 容器,既然无法找到一个适配所有情况的容器,那么不如干脆更简单的一些,我们不再使用系统已有的布局,现要求用户必须在根部局处放置一个我们自定义的 FrameLayout 布局,该布局中我们修改了部分逻辑,然后定义一些通用的方法:
那么现在来大概猜想一下,此FrameLayout布局大致需要包含的功能:
看起来可能功能比较 麻烦,但其实都 很简单,逻辑很清晰,按照每个逻辑一个方法来设计,大概我们做如下功能:IncludeDialogViewGroup 源码
class IncludeDialogViewGroup : FrameLayout, View.OnClickListener {
/**
* 记录当前装载的dialog
*/
private var allDialogs: MutableList<SimulateDialogInterface<*, *>> = mutableListOf()
/**
* 装载 dialog 对应的 animator对象,可执行动画
*/
private var dialogAnimators: HashMap<SimulateDialogInterface<*, *>, ProvideIncludeDialogVGAnimator> = hashMapOf()
/**
* 遮罩布局
*/
private var shadeView = FrameLayout(context)
/**
* dimen颜色(遮罩颜色值)
*/
var maskColor: Int = defaultMaskColor
/**
* 当点击 dialog "外部" 时候,是否关闭弹框
*/
var closeOnClickOut: Boolean = defaultCloseOnClickOut
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(
context,
attrs,
defStyleAttr,
defStyleRes
)
/**
* 布局加载完成后,再进行事件或其他逻辑(此时务必保证Activity的布局已经进行了加载)
*/
@CallSuper
override fun onFinishInflate() {
super.onFinishInflate()
//当布局结束后,标记到context,当前界面有可能弹出dialog
getActivityFromView().findViewById<View>(android.R.id.content).setTag(R.id.id_include_dialog_view_group, this)
//同时添加一个中间的布局View,用作遮挡板等功效
addView(shadeView, LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
}
/**
* 如果处在处于显示状态的view
*/
fun existDialogOpened(): Boolean {
//避免多个线程调用导致出现异常
synchronized(this) {
return allDialogs.any { it.generateView().parent != null }
}
}
/**
* 显示某个/些dialog,默认不显示所有
*
* 通过instance或者class,或者可以显示所有
*
* [instance] dialog 对象引用
* [clazz] 显示class 为 clazz 的dialog
* [showAll] true 表示显示所有被注册的dialog
* [animator] 显示/隐藏 dialog 时的动画逻辑,空表示不执行动画
*/
fun showDialogs(
instance: SimulateDialogInterface<*, *>? = null,
clazz: Class<SimulateDialogInterface<*, *>>? = null,
showAll: Boolean = false,
animator: ProvideIncludeDialogVGAnimator? = null
) {
allDialogs.asSequence()
.filter {
showAll
|| it === instance
|| clazz?.isAssignableFrom(it::class.java) ?: false
}.map {
//第一,添加布局,使用动画效果
if (animator != null) {
//保存动画对象
dialogAnimators[it] = animator
//调用方法开启动画
animator.startAnimator(this@IncludeDialogViewGroup, it) {
addView(it.generateView(), it.generateLayoutParams())
}
} else {
//移除之前的动画对象
dialogAnimators.remove(it)
//无动画添加 view 进行显示
addView(it.generateView(), it.generateLayoutParams())
}
}
.toList()
.filterAlso({ it.isNotEmpty() }) {
//第二,添加dimenColor
shadeView.setBackgroundColor(maskColor)
//第三,添加点击事件
shadeView.setOnClickListener(this)
}
}
/**
* 关闭已经打开的布局(必须制定布局,或者关闭所有,默认关闭所有)
*
* 1.关闭被模拟的布局
* 2.修改自身的状态信息,包括被屏蔽的点击事件,以及背景颜色等等
*
* [instance] dialog 对象引用
* [clazz] 显示class 为 clazz 的dialog
* [closeAll] true 表示显示所有被注册的dialog
* [useAnimator] 是否使用动画效果来关闭,当为true且之前开启动画时,即[showDialogs]方法的 [showDialogs#animator)]
*
* @return 返回被关闭的dialog数量
*/
fun closeDialogsOpened(
instance: SimulateDialogInterface<*, *>? = null,
clazz: Class<SimulateDialogInterface<*, *>>? = null,
closeAll: Boolean = true,
useAnimator: Boolean = true
): Int {
val removeMask = {
//判断,如果当前所有dialog都已关闭,则修改颜色值,取消遮罩监听
if (!existDialogOpened()) {
//第二,移除背景dimenColor
shadeView.setBackgroundColor(Color.TRANSPARENT)
//第三,移除监听事件
shadeView.setOnClickListener(null)
shadeView.isClickable = false
}
}
return allDialogs.filter {
closeAll
|| it === instance
|| clazz?.isAssignableFrom(it::class.java) ?: false
}.filter {
(it.generateView().parent != null).onTrue {
//第一,移除布局
if (useAnimator && dialogAnimators.containsKey(it)) {
//是否使用动画
dialogAnimators[it]!!.stopAnimator(this@IncludeDialogViewGroup, it) {
removeView(it.generateView())
removeMask()
}
} else {
removeView(it.generateView())
removeMask()
}
}
}.size
}
/**
* 注册 dialog
*
* [dialog] 用于操作的view
*/
fun registerDialog(dialog: SimulateDialogInterface<*, *>) {
if (allDialogs.indexOf(dialog) == -1) {
allDialogs.add(dialog)
//对dialog人为添加点击事件,防止点击dialog时,触发shadeView布局导致弹窗关闭
dialog.generateView().setOnClickListener {
//不处理任何时间,只是防止点击dialog时被关闭
}
}
}
/**
* 注销 某个dialog ,注销后,将移除,不是关闭,默认注销所有dialog
*
* [instance] dialog 引用
* [clazz] 通过类型移除
* [logoutAll] 移除所有
*
* @return 移除了多少个dialog
*/
fun logoutDialogs(
instance: SimulateDialogInterface<*, *>? = null,
clazz: Class<SimulateDialogInterface<*, *>>? = null,
logoutAll: Boolean = true
): Int {
return allDialogs.asSequence()
.filter {
logoutAll
|| it === instance
|| clazz?.isAssignableFrom(it::class.java) ?: false
}.map {
allDialogs.remove(it)
}.toList().size
}
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
override fun onClick(v: View?) {
//当dialog可见时,该点击事件用来防止误点到布局底部的视图
if (existDialogOpened() && closeOnClickOut) {
closeDialogsOpened()
}
}
/**
* 禁止自身设置点击事件
*/
@Deprecated(message = "not support", replaceWith = ReplaceWith("super.setOnClickListener"))
override fun setOnClickListener(l: OnClickListener?) {
TODO()
}
/**
* 默认伴生类,放置静态的 配置选项
*/
companion object {
/**
* 默认的遮罩颜色
*/
var defaultMaskColor: Int = Color.parseColor("#40000000")
/**
* 点击 外部时,是否默认的关闭弹出框
*/
var defaultCloseOnClickOut: Boolean = true
}
}
其实这段代码,除了实现上述基本 功能外,还多了一些逻辑,包括:
我们 还规定了被模拟的 dialog 必须实现 SimulateDialogInterface接口,通过该接口,dialog 容器(即IncludeDialogViewGroup)可以获取需要显示的 View ,以及 添加到父布局的 LayoutParams 属性;
注意:generateView 方法多次调用必须返回同一个 View 对象
/**
* Created on 2019/3/23 20:37
* function : 模拟dialog的控件,必须具有以下功能:
*
* 1.获取加载到ViewGroup中的layoutParams方式
* 2.获取可加载到ViewGroup中的View类型布局
*
* @author mnlin
*/
public interface SimulateDialogInterface<V extends View, L extends ViewGroup.LayoutParams> {
/**
* 获取布局
*
* 请确保: 多次调用该接口应当返回同一对象
*
* @return View或者ViewGroup
*/
@NonNull
V generateView();
/**
* 获取layout-params,加载到FrameLayout中的方式
*/
@Nullable
L generateLayoutParams();
}
我们的容器通过该接口 中方法获取到view,然后在需要显示dialog时,将view 以指定的 layoutParams 形式添加到 自身容器,这样 dialog 就可以正常显示了。
除此之外,还提供 了自定义动画的接口ProvideIncludeDialogVGAnimator。通过该接口,可以自定义dialog弹出 和 关闭时候的动画;如果无从下手,可以参考默认提供的透明度变化动画:AlphaIDVGAnimatorImpl
注意:startAnimator与stopAnimator 方法的实现中,必须按照要求各自调用传入的最后一个runnable 对象,否则动画将无法显示
/**
* Created on 2019/3/29 11:49
* function : 为 {@link com.wallet.usdp.view.IncludeDialogViewGroup} 添加动画处理效果
*
* 注意:
* 开始动画和结束动画前后,要保证 dialog的 状态完全恢复,否则,可能会影响弹出关闭后再次弹出的显示效果
*
* 注意:
* 处理动画时,仅将 dialog.generateView 当做普通的{@link android.view.View} 来进行动画处理即可
*
* 例如:
*
* 当设置透明度变化动画时:
* 在开始动画{@link ProvideIncludeDialogVGAnimator#startAnimator}前,要保证 view 透明度为最小值;以达到动画显示效果
* 在结束动画 {@link ProvideIncludeDialogVGAnimator#stopAnimator}后,要保证 View 透明度为最大值,因为可能下次弹出此 dialog 时动画不再是透明度变化动画
*
* 详细逻辑可参考{@link com.wallet.usdp.util.AlphaIDVGAnimatorImpl}
*
* @author mnlin
*/
public interface ProvideIncludeDialogVGAnimator {
/**
* 开始动画 处理逻辑
*
* @param dialog 被模拟的dialog
* @param parent dialog-container
* @param mustCallOnAnimatorStart 当自己动画开始之前,必须调用执行该 runnable 方法,保证dialog可以正常添加到屏幕上
*/
void startAnimator(IncludeDialogViewGroup parent, SimulateDialogInterface<?, ?> dialog, Runnable mustCallOnAnimatorStart);
/**
* 结束动画 处理逻辑
*
* @param dialog 被模拟的dialog
* @param parent dialog-container
* @param mustCallOnAnimatorEnd 当自己处理完动画后,必须调用执行该 runnable 方法,保证dialog可以正常关闭
*/
void stopAnimator(IncludeDialogViewGroup parent, SimulateDialogInterface<?, ?> dialog, Runnable mustCallOnAnimatorEnd);
}
仅就功能来说,我们已经实现了统一的方式去弹出dialog,那么此种方法使用起来如何?我们来进行一下演示:
先给 出demo演示的效果:demo
使用方法很简单就 分为四步:
IncludeDialogViewGroup
容器,这个是dialog 可以弹出的前提这样所有的准备逻辑就完成了,为了不重复编写相同引导,这里将 GITHUB 地址给出:
https://github.com/lovingning/SimulateDialog
库中已详细说明了如何使用,这里就 不再赘述。
若有不解可直接下载 demo 查看显示 效果:
apk下载地址
demo地址
下一篇会依据前面 给出的系统已有的布局,探寻更简单的实现方式。