AnyLayer
Android穩定高效的浮層創建管理框架。
可取代系統自帶Dialog/Popup/BottomSheet等彈窗,可實現單Activity架構的Toast提示,可定制任意樣式的Guide引導層,可實現依附Activity的Float懸浮按鈕。
簡介
- 同時兼容support和androidx
- 鏈式調用
- 支持自由擴展
- 實現幾種常用效果
- Dialog效果
- 占用區域不會超過當前Activity避免導航欄遮擋
- 支持自定義大小和顯示位置
- 支持自定義數據綁定
- 支持自定義進出場動畫
- 支持自定義背景顏色/圖片/高斯模糊
- 支持在Activity的onCreate生命周期彈出
- 支持從ApplicationContext中彈出
- Popup效果
- 擁有Dialog效果特性
- 支持跟隨目標View移動
- Toast效果
- 支持自定義圖標和文字
- 支持自定義顯示時長
- 支持自定義位置
- 支持自定義背景資源和顏色
- 支持自定義透明度
- 支持自定義進出場動畫
- Guide效果
- 引導層效果待開發
- Float效果
- 懸浮按鈕效果待開發
- Dialog效果
說明
詳細原理和使用說明本人會在后續補上,但因個人開發,時間不定,請諒解。
或有大佬在看過源碼后,愿意寫下分析文章或使用說明來分享的,歡迎聯系本人。寫得比較詳細的本人將會放到README中供大家查閱,提前謝過。
當然也歡迎加群共同探討,共同進步。
QQ群:147715512(愛Android)
使用該庫的產品
如果你的產品正在使用AnyLayer,歡迎留下相關信息
這些信息將用來幫助更多開發者關注并使用本框架,增加框架的活躍度。而高活躍度則意味著更多隱藏BUG被發現并修復,即活躍度等同于框架的健壯性。同時這也是我維護項目的最大動力,感謝。
APP名 | APP圖標 | 公司名 |
---|---|---|
玩安卓 | 個人 | |
熊貓淘學 | 西安熊貓寶寶網絡科技有限公司 | |
MBA大師 | MBA大師 |
截圖
截圖效果較差且版本較老,建議下載Demo體驗最新功能
使用說明
集成
-
添加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
// build.gradle(Module:)
dependencies {
// 完整引入
implementation 'com.github.goweii:AnyLayer:3.2.0'
// 基礎庫
// 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(規范子布局層級,加入背景層,分離動畫為背景動畫和內容動畫)
- PopupLayer(可依據錨點View定位)
- ToastLayer(定時消失,不響應事件的Layer)
- GuideLayer(引導層Layer)
- FloatLayer(懸浮按鈕Layer)
-
DialogLayer(規范子布局層級,加入背景層,分離動畫為背景動畫和內容動畫)
-
DecorLayer(規范父布局為DecorView的特殊Layer,引入了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
待實現