效果圖
彈性彈窗.gif
效果圖
使用
build.gradle中添加
compile 'com.cool:elasticdialog:1.0.0'
你要使用的地方
if(elasticDialog== null) {
elasticDialog = new ElasticDialog(this)
.layout(R.layout.dialog_elastic)
.arcColor(Color.WHITE)
.duration(1000)
.arcHight(40);
mRecyclerView = elasticDialog.findViewById(R.id.recyclerview);
}
布局文件dialog_elastic
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_gravity="bottom">
</android.support.v7.widget.RecyclerView>
</FrameLayout>
有兩點需要注意
:
- 布局文件dialog_elastic子View需要設置
android:layout_gravity="bottom"
屬性 - 根布局使用
FrameLayout
實現思路:
我們可以在dialog中設置的布局中做手腳,dialog的根布局使用FrameLayout,在渲染dialog的布局完成后,偷偷的添加一個背景view,放在FrameLayout的最底部,然后通過背景view的onDraw方法畫view的背景,可以看到,背景view的背景是不規則的,首先想到的就是path,具體實現方式當然是貝塞爾曲線了,下面是具體步驟。
一、創建ElasticDialog
ElasticDialog是繼承Dialog的
1.1在構造函數里進行相關初始化
public ElasticDialog(@NonNull Context context) {
super(context, R.style.stlyle_dialog_transparent_bg);
this.mContext = context;
mArcHight = dp2px(40);//弧形的高度
mDuration = 1000;//動畫時長
arcColor = Color.WHITE;//弧形顏色
}
1.2設置dialog的布局和相關參數
/**
* 設置dialog的布局
* @param layoutId
* @return
*/
public ElasticDialog layout(int layoutId) {
view = LayoutInflater.from(mContext).inflate(layoutId, null);
setContentView(view);
Window window = getWindow();
window.setGravity(Gravity.BOTTOM);
window.setWindowAnimations(R.style.style_anim_bottom_in);
WindowManager.LayoutParams params = window.getAttributes();
params.width = getContext().getResources().getDisplayMetrics().widthPixels;
window.setAttributes(params);
return this;
}
其中看到的R.style.stlyle_dialog_transparent_bg和R.style.style_anim_bottom_in是兩個主題,如下:
<!--透明背景dialog主題-->
<style name="stlyle_dialog_transparent_bg" parent="android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<!--底部彈框dialog動畫主題-->
<style name="style_anim_bottom_in">
<item name="android:windowEnterAnimation">@anim/dialog_enter</item>
<item name="android:windowExitAnimation">@anim/dialog_exit</item>
</style>
1.3顯示dialog
@Override
public void show() {
super.show();
addBackgroundView();//添加背景view
doAnim();//開始動畫
}
/**
* 添加背景動畫view
*/
private void addBackgroundView() {
if (view instanceof FrameLayout) {
FrameLayout fl = (FrameLayout) view;
View backView = fl.getChildAt(0);
if(backView instanceof ElasticBackgroundView){//判斷之前是否已經添加過背景view
return;
}
fl.measure(0, 0);//為了拿到根布局測量高度
int measuredHeight = fl.getMeasuredHeight();
int realHight = measuredHeight + mArcHight;//根布局高度加上圓弧高度
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, realHight);
backgroundView = new ElasticBackgroundView(mContext);
backgroundView.setArcHight(mArcHight);
backgroundView.setDuration(mDuration);
backgroundView.setArcColor(arcColor);
fl.addView(backgroundView, 0, layoutParams);
}else {
throw new IllegalArgumentException("dialog的根布局必須為FrameLayout");
}
}
/**
* 開始動畫
*/
private void doAnim() {
if (backgroundView != null) {
backgroundView.doStartAnimation();
}
}
二、繪制ElasticBackgroundView的背景
2.1初始化paint、path、動畫時長
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
path = new Path();
mDuration = 1000;
}
2.2重寫onDraw方法
mArcHight為圓弧的高度,currentArcHight為動畫過程中圓弧的高度,mWidth,mHight是背景View的寬高
@Override
protected void onDraw(Canvas canvas) {
path.reset();
path.moveTo(0, mArcHight);
path.quadTo(mWidth/2,currentArcHight,mWidth, mArcHight);
path.lineTo(mWidth,mHight);
path.lineTo(0,mHight);
path.close();
canvas.drawPath(path,mPaint);
}
2.3開啟動畫
public void doStartAnimation() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(mArcHight,-mArcHight, mArcHight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currentArcHight = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(mDuration);
valueAnimator.start();
}
完整的ElasticDialog
public class ElasticDialog extends Dialog {
private ElasticBackgroundView backgroundView;
private Context mContext;
private View view;
private int mArcHight;//頂部遺留高度,默認40dp,白色圓弧形能夠達到的高度相關連,經測試40效果較好
private long mDuration;//動畫執行時間
private int arcColor;
public ElasticDialog(@NonNull Context context) {
super(context, R.style.stlyle_dialog_transparent_bg);
this.mContext = context;
mArcHight = dp2px(40);
mDuration = 1000;
arcColor = Color.WHITE;
}
/**
* 設置dialog的布局
* @param layoutId
* @return
*/
public ElasticDialog layout(int layoutId) {
view = LayoutInflater.from(mContext).inflate(layoutId, null);
setContentView(view);
Window window = getWindow();
window.setGravity(Gravity.BOTTOM);
window.setWindowAnimations(R.style.style_anim_bottom_in);
WindowManager.LayoutParams params = window.getAttributes();
params.width = getContext().getResources().getDisplayMetrics().widthPixels;
window.setAttributes(params);
return this;
}
@Override
public void show() {
super.show();
addBackgroundView();
doAnim();
}
/**
* 添加背景動畫view
*/
private void addBackgroundView() {
if (view instanceof FrameLayout) {
FrameLayout fl = (FrameLayout) view;
View backView = fl.getChildAt(0);
if(backView instanceof ElasticBackgroundView){
return;
}
fl.measure(0, 0);
int measuredHeight = fl.getMeasuredHeight();
int realHight = measuredHeight + mArcHight;
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, realHight);
backgroundView = new ElasticBackgroundView(mContext);
backgroundView.setArcHight(mArcHight);
backgroundView.setDuration(mDuration);
backgroundView.setArcColor(arcColor);
fl.addView(backgroundView, 0, layoutParams);
}else {
throw new IllegalArgumentException("dialog的根布局必須為FrameLayout");
}
}
/**
* 開始動畫
*/
private void doAnim() {
if (backgroundView != null) {
backgroundView.doStartAnimation();
}
}
/**
* 設置達到的弧高
* @param arcHight
* @return
*/
public ElasticDialog arcHight(int arcHight) {
this.mArcHight = dp2px(arcHight);
return this;
}
/**
* 設置背景動畫時間
* @param duration
* @return
*/
public ElasticDialog duration(long duration) {
if (duration < 0) {
this.mDuration = 1000;
}
this.mDuration = duration;
return this;
}
/**
* 弧形背景顏色
* @param color
* @return
*/
public ElasticDialog arcColor(int color){
this.arcColor = color;
return this;
}
/**
* 是否可取消
* @param cancelable
* @return
*/
public ElasticDialog cancelable(boolean cancelable){
setCancelable(cancelable);
return this;
}
private int dp2px(int dp){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mContext.getResources().getDisplayMetrics());
}
}
完整的ElasticBackgroundView
public class ElasticBackgroundView extends View {
private Paint mPaint;
private Path path;
private int mWidth;
private int mHight;
private int mArcHight;//頂部遺留高度,默認40dp,遺留為了畫頂部白色弧形并且能夠看到
private float currentArcHight;
private long mDuration;//動畫執行時間
public ElasticBackgroundView(Context context) {
this(context,null);
}
public ElasticBackgroundView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ElasticBackgroundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
path = new Path();
mDuration = 1000;
}
@Override
protected void onDraw(Canvas canvas) {
path.reset();
path.moveTo(0, mArcHight);
path.quadTo(mWidth/2, currentArcHight,mWidth, mArcHight);
path.lineTo(mWidth,mHight);
path.lineTo(0,mHight);
path.close();
canvas.drawPath(path,mPaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHight = h;
}
public void doStartAnimation() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(mArcHight,-mArcHight, mArcHight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currentArcHight = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(mDuration);
valueAnimator.start();
}
public void setArcHight(int leaveHight){
this.mArcHight = leaveHight;
}
public void setDuration(long duration){
this.mDuration = duration;
}
public void setArcColor(int color){
if(mPaint != null){
mPaint.setColor(color);
}
}
}
源碼地址:https://github.com/lkkz/ElasticDialog
歡迎star,issuse