AnyLayer

Project Url: goweii/AnyLayer
Introduction: 用于替代 Android 自带 Dialog 和 PopupWindow
More: Author   ReportBugs   OfficialWebsite   
Tags:

Android 稳定高效的浮层创建管理框架。

可取代系统自带 Dialog/Popup/BottomSheet 等弹窗,可实现单 Activity 架构的 Toast 提示,可定制任意样式的 Guide 引导层,可实现依附 Activity 的 Float 悬浮按钮。

GitHub 主页

Demo 下载

简介

  • 同时兼容 support 和 androidx
  • 链式调用
  • 支持自由扩展
  • 实现几种常用效果
    • Dialog/BottomSheet 效果
      • 占用区域不会超过当前 Activity 避免导航栏遮挡
      • 支持自定义大小和显示位置
      • 支持自定义数据绑定
      • 支持自定义进出场动画
      • 支持自定义背景颜色/图片/高斯模糊
      • 支持在 Activity 的 onCreate 生命周期弹出
      • 支持从 ApplicationContext 中弹出
      • 支持拖拽关闭
      • 支持不拦截外部事件
    • Popup 效果
      • 拥有 Dialog 效果特性
      • 支持跟随目标 View 移动
    • Toast 效果
      • 支持自定义图标和文字
      • 支持自定义显示时长
      • 支持自定义位置
      • 支持自定义背景资源和颜色
      • 支持自定义透明度
      • 支持自定义进出场动画
    • Guide 效果
      • 引导层效果待开发
    • Float 效果
      • 悬浮按钮效果待开发

说明

详细原理和使用说明本人会在后续补上,但因个人开发,时间不定,请谅解。

或有大佬在看过源码后,愿意写下分析文章或使用说明来分享的,欢迎联系本人。写得比较详细的本人将会放到 README 中供大家查阅,提前谢过。

当然也欢迎加群共同探讨,共同进步。

QQ 群:147715512(爱 Android)

使用该库的产品

如果你的产品正在使用 AnyLayer,欢迎留下相关信息

这些信息将用来帮助更多开发者关注并使用本框架,增加框架的活跃度。而高活跃度则意味着更多隐藏 BUG 被发现并修复,即活跃度等同于框架的健壮性。同时这也是我维护项目的最大动力,感谢。

APP 名 APP 图标 公司名
玩安卓 个人
熊猫淘学 西安熊猫宝宝网络科技有限公司
MBA 大师 MBA 大师

截图

截图效果较差且版本较老,建议下载 Demo体验最新功能

anylayer.gif

使用说明

集成

  • 添加 jitpack 库

// build.gradle(Project:)
allprojects {
    repositories {
        ...
            maven { url 'https://www.jitpack.io' }
    }
}
  • 添加依赖

    点击查看最新版本号

    引用时需注意版本号,从 2.3.1 版本开始,版本号前不加 v。

    通用库为在 2.4.0 版本新增,有效引用为 2.4.0~2.5.0 之间版本。

    从 3.0.0 版本开始,框架重构,删除通用库。因重构代码变化较大,不建议使用较多的老项目升级,保持 2.5.0 版即可,在实现 Dialog/Popup 等效果上无本质差别。

    从 3.1.0 版本开始移除对 support-v7 的依赖,可同时兼容 support 和 androidx

    从 3.3.0 版本开始分为 support 和 androidx 两个分支,主要是适配滚动控件的拖拽关闭 ```groovy // build.gradle(Module:) dependencies { // 完整引入,二选一 implementation 'com.github.goweii:AnyLayer:3.3.0-androidx' implementation 'com.github.goweii:AnyLayer:3.3.0-support'

    // 基础库 // implementation 'com.github.goweii.AnyLayer:anylayer:2.5.0'

    // 通用弹窗(依赖基础库) // 从 3.0.0 版本暂时删除 // implementation 'com.github.goweii.AnyLayer:anylayer-common:2.5.0' } ```

类间关系

  • AnyLayer(提供常用效果的调用入口)

  • ViewManager(管理 View 的动态添加移除和 KeyEvent 事件注册)

  • Layer(对 ViewManager 的包装,实现进出场动画逻辑和事件监听,规范接口形式,分离出 ViewHolder/ListenerHolder/Config 三大内部类)

    • DecorLayer(规范父布局为 DecorView 的特殊 Layer,引入了 Layer 层级概念)
      • DialogLayer(规范子布局层级,加入背景层,分离动画为背景动画和内容动画)
      • ToastLayer(定时消失,不响应事件的 Layer)
      • GuideLayer(引导层 Layer)
      • FloatLayer(悬浮按钮 Layer)
  • AnimatorHelper(创建常用属性动画)

类说明

AnyLayer

一个工厂效果的工具类,与 Layer 类族的关系就像 Executors 于 Executor 的关系这样。

只是提供了静态方法方便调用,不用 new 来 new 去的。

/**
 * 初始化高斯模糊,可在用到高斯模糊背景的 Activity 的 onCreate 方法调用
 * 其实不调用也没关系,第一次显示的时候也会初始化的,只是这样第一次显示就会比后面显示稍微慢一点
 */
public static void initBlurred(Context context)

/**
 * 回收高斯模糊内存占用,可在用到高斯模糊背景的 Activity 的 onDestroy 方法调用,也可以在内存不足时调用
 */
public static void recycleBlurred()

/**
 * 创建一个 DialogLayer
 * 这个 Context 不能是 ApplicationContext
 */
public static DialogLayer dialog(Context context)

/**
 * 创建一个 DialogLayer
 * 依附的是当前显示的 Activity
 */
public static DialogLayer dialog()

/**
 * 创建一个 DialogLayer
 * 依附的是特定 Class 的 Activity 实例,这个 Activity 必须是已经启动的
 */
public static DialogLayer dialog(Class<Activity> clazz)

/**
 * 创建一个 DialogLayer
 * 会启动一个新的全透明 Activity 依附
 */
public static void dialog(LayerActivity.OnLayerCreatedCallback callback)

/**
 * 创建一个 PopupLayer
 */
public static PopupLayer popup(View targetView)

/**
 * 创建一个 ToastLayer
 * 依附的是当前显示的 Activity
 */
public static ToastLayer toast()

/**
 * 创建一个 ToastLayer
 * 这个 Context 不能是 ApplicationContext
 */
public static ToastLayer toast(Context context)

ViewManager

这个就不介绍了吧?一般用的时候也用不到,后面原理剖析的时候再说吧!

Layer

上面类间关系简单描述了一下

对 ViewManager 的包装,实现进出场动画逻辑和事件监听,规范接口形式,分离出 ViewHolder/ListenerHolder/Config 三大内部类

可以看出这个类是所有效果的基类,有以下几个特定:

  • 对 ViewManager 的包装

    可以自由指定父布局和子布局,可以通过继承在 onGetParent 和 onCreateChild 中返回,也可以通过 parent()和 child()方法设置。

    其次是几个生命周期回调方法

    • onAttach(View 刚被添加到父布局)
    • onPreDraw(View 开始绘制前,这里开始执行进场动画)
    • onShow(View 显示,进场动画结束)
    • onPreRemove(View 准备移除时,这里开始执行出场动画)
    • onDetach(View 已被移除,出场动画结束)
  • 实现进出场动画逻辑

    在 onPreDraw 和 onPreRemove 中实现了进出场动画的流程。可通过继承在 onCreateInAnimator 和 onCreateOutAnimator 中返回,也可以通过 animator()方法设置。

    其中 AnimatorCreator 为创建自定义进出场动画的接口

  • 事件监听

    有几个常用的事件监听,在 ListenerHolder 中统一管理

    • DataBinder(绑定数据)
    • OnClickListener(点击事件监听)
    • OnShowListener(显示动画开始和结束监听)
    • OnDismissListener(消失时动画开始和结束监听)
    • OnVisibleChangeListener(显示隐藏状态改变的监听)
  • 分离出 ViewHolder/ListenerHolder/Config 三大内部类

    这个看名字应该就知道了,就不介绍了。

  • 常用方法

    /**
     * 一组控制显示隐藏的方法
     */
    public void show()
    public void show(boolean withAnim)
    public void dismiss()
    public void dismiss(boolean withAnim)
    
    /**
     * 判断当前显示隐藏状态
     */  
    public boolean isShow()
    
    /**
     * 根据 ID 获取对应 View
     */  
    public <V extends View> V getView(int id)
    
    /**
     * 指定父布局
     */  
    public Layer parent(ViewGroup parent)
    
    /**
     * 指定子布局
     */  
    public Layer child(View child)
    
    /**
     * 自定义进出场动画
     */  
    public Layer animator(AnimatorCreator creator)
    
    /**
     * 是否拦截物理按键,为 true 时 cancelableOnKeyBack 才有效
     */  
    public Layer interceptKeyEvent(boolean intercept)
    
    /**
     * 是否可点击返回键关闭浮层,interceptKeyEvent 为 true 才有效
     */  
    public Layer cancelableOnKeyBack(boolean cancelable)
    
    /**
     * 一组事件监听,见上面介绍
     */  
    public Layer bindData(DataBinder dataBinder)
    public Layer onVisibleChangeListener(OnVisibleChangeListener onVisibleChangeListener)
    public Layer onShowListener(OnShowListener onShowListener)
    public Layer onDismissListener(OnDismissListener onDismissListener)
    
    /**
     * 点击某些控件关闭浮层
     */  
    public Layer onClickToDismiss(OnClickListener listener, int... viewIds)
    public Layer onClickToDismiss(int... viewIds)
    
    /**
     * 对控件绑定点击事件
     */  
    public Layer onClick(OnClickListener listener, int... viewIds)
    

DecorLayer

继承自 Layer,强制父布局为 DecorView。主要就是引入了 Layer 层级概念。

而这个层级就是由两个静态内部类实现的

  • LayerLayout

    继承自 FrameLayout,是各个层级浮层的容器,直接添加进 DecorView。

  • LevelLayout

    继承自 FrameLayout,定义了 Level 概念,是每个浮层的父布局,也就是在外面包了一层,用来控制浮层上下层级的容器。这个是直接添加进 LayerLayout 的。

好了就这么多了。更多了细节还是看源码吧。

DialogLayer

这个就是模仿传说中 Dialog 效果的浮层。用上面的描述就是:

规范子布局层级,加入背景层,分离动画为背景动画和内容动画

一个一个来看。

  • 规范子布局层级,加入背景层

    从 DecorLayer 的介绍中可以知道 LevelLayout 是该类浮层的直接父布局。但它并不是 DialogLayer 中 contentView 的直接父布局。就是因为又加了一个 ViewGroup 把 contextView 和 background 包裹起来。布局文件是这样子的。

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fl_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true">
    
        <ImageView
            android:id="@+id/iv_background"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"/>
    
        <FrameLayout
            android:id="@+id/fl_content_wrapper"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </FrameLayout>
    

    contentView 其实是直接添加至 fl_content_wrapper 中的。

  • 分离动画为背景动画和内容动画

    因为有了 contextView 和 background 的概念,原来的动画就不好用了。所以从写了动画的创建逻辑,分离了背景和前景应用不同的动画。

最后看下常用的方法。

/**
 * 设置自定义布局 View/资源 ID
 */
public DialogLayer contentView(View contentView)
public DialogLayer contentView(int contentViewId)

/**
 * 设置自定义布局文件中状态栏的占位 View
 * 该控件高度将设置为状态栏高度,可用来使布局整体下移,避免状态栏遮挡
 */
public DialogLayer asStatusBar(int statusBarId)

/**
 * 设置避开状态栏
 */
public DialogLayer avoidStatusBar(boolean avoid)

/**
 * 设置子布局的 gravity
 * 可直接在布局文件指定 layout_gravity 属性,作用相同
 */
public DialogLayer gravity(int gravity)

/**
 * 自定义浮层的进入和退出动画
 */
public DialogLayer contentAnimator(AnimatorCreator contentAnimatorCreator)

/**
 * 自定义背景的进入和退出动画
 */
public DialogLayer backgroundAnimator(AnimatorCreator backgroundAnimatorCreator)

/**
 * 设置背景高斯模糊/图片/颜色
 */
public DialogLayer backgroundBlurRadius(float radius)
public DialogLayer backgroundBlurPercent(float percent)
public DialogLayer backgroundBlurScale(float scale)
public DialogLayer backgroundBitmap(Bitmap bitmap)
public DialogLayer backgroundDimAmount(float dimAmount)
public DialogLayer backgroundResource(int resource)
public DialogLayer backgroundDrawable(Drawable drawable)
public DialogLayer backgroundColorInt(int colorInt)
public DialogLayer backgroundColorRes(int colorRes)

/**
 * 设置点击浮层以外区域是否可关闭
 */
public DialogLayer cancelableOnTouchOutside(boolean cancelable)

/**
 * 设置点击返回键是否可关闭
 */
public DialogLayer cancelableOnClickKeyBack(boolean cancelable)

算了,再看下调用代码凑凑字数把。

AnyLayer.dialog(this)
        .contentView(R.layout.dialog_test_2)
        .backgroundColorRes(R.color.dialog_bg)
        .gravity(Gravity.CENTER)
        .cancelableOnTouchOutside(true)
        .cancelableOnClickKeyBack(true)
        .bindData(new Layer.DataBinder() {
            @Override
            public void bindData(Layer layer) {
                // TODO 绑定数据
            }
        })
        .onClickToDismiss(R.id.fl_dialog_no)
        .show();

PopupLayer

因为继承自 DialogLayer,所以可以使用定义过的方法(这不是废话吗)。

主要就是加入了可依据锚点 View 定位。

这个效果可能还要重构,暂时就不介绍了。

看下其他新增方法。

/**
 * 设置浮层外部是否拦截触摸
 * 默认为 true,false 则事件有 activityContent 本身消费
 */
public PopupLayer outsideInterceptTouchEvent(boolean intercept)

/**
 * 是否裁剪 contentView 至包裹边界
 */
public PopupLayer contentClip(boolean clip)

/**
 * 是否偏移背景对齐目标控件
 */
public PopupLayer backgroundAlign(boolean align)

/**
 * 背景应用 offset 设置
 */
public PopupLayer backgroundOffset(boolean offset)

/**
 * 当以 target 方式创建时为参照 View 位置显示
 * 可自己指定浮层相对于参照 View 的对齐方式
 */
public PopupLayer align(Align.Direction direction,
                        Align.Horizontal horizontal,
                        Align.Vertical vertical,
                        boolean inside)

/**
 * 指定浮层相对于参照 View 的对齐方式
 */
public PopupLayer direction(Align.Direction direction)

/**
 * 指定浮层相对于参照 View 的对齐方式
 */
public PopupLayer horizontal(Align.Horizontal horizontal)

/**
 * 指定浮层相对于参照 View 的对齐方式
 */
public PopupLayer vertical(Align.Vertical vertical)

/**
 * 指定浮层是否强制位于屏幕内部
 */
public PopupLayer inside(boolean inside)

/**
 * X 轴偏移
 */
public PopupLayer offsetX(float offsetX, int unit)
public PopupLayer offsetXdp(float dp)
public PopupLayer offsetXpx(float px)
/**
 * Y 轴偏移
 */
public PopupLayer offsetY(float offsetY, int unit)
public PopupLayer offsetYdp(float dp)
public PopupLayer offsetYpY(float px)

国际惯例,最后看下调用代码凑凑字数。

AnyLayer.popup(targetView)
        .contentView(R.layout.dialog_test_4)
        .backgroundColorRes(R.color.dialog_bg)
        .direction(Align.Direction.VERTICAL)
        .horizontal(Align.Horizontal.CENTER)
        .vertical(Align.Vertical.BELOW)
        .inside(true)
        .contentAnim(new LayerManager.IAnim() {
            @Override
            public Animator inAnim(View content) {
                return AnimHelper.createTopInAnim(content);
            }

            @Override
            public Animator outAnim(View content) {
                return AnimHelper.createTopOutAnim(content);
            }
        })
        .show();

ToastLayer

累了,这个写简单点,就看下调用代码算了。

AnyLayer.toast()
        .duration(3000)
        .icon(isSucc ? R.drawable.ic_success : R.drawable.ic_fail)
        .message(isSucc ? "哈哈,成功了" : "哎呀,失败了")
        .show();

GuideLayer

待实现

FloatLayer

待实现

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools