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