效果图
效果图
使用
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