普通的弹框多以dialog的形式弹出,这个类库是以布局的形式弹出来的。
以下是作者的源类库中的包定义:
SweetSheet.class
代码中主要使用的类,在初始化时候需要传入依附的父布局(以后弹出的view就是要加入到这个view中的):
// SweetSheet 控件,根据 rl 确认位置
mSweetSheet = new SweetSheet(rl);
然后将代理类设置到该类中,以后实际就是通过代理类来控制属性:
//根据设置不同的 Delegate 来显示不同的风格.
mSweetSheet.setDelegate(new RecyclerViewDelegate(true));
实际上调用:
//设置代理对象
public void setDelegate(Delegate delegate) {
mDelegate = delegate;
mDelegate.init(mParentVG);
setup();
}
这里通过代理类中的初始化方法来先初始化一个点击背景(该背景可以设置模糊的效果),加载列表代理类RecyclerViewDelegate实现的抽象方法createView(),实现如下:
@Override
protected View createView() {
View rootView = LayoutInflater.from(mParentVG.getContext()).inflate(R.layout.layout_rv_sweet, null, false);
//动画效果控件
mSweetView = (SweetView) rootView.findViewById(R.id.sv);
//控制随着手势移动后重绘布局
mFreeGrowUpParentRelativeLayout = (FreeGrowUpParentRelativeLayout) rootView.findViewById(R.id.freeGrowUpParentF);
//列表
mRV = (RecyclerView) rootView.findViewById(R.id.rv);
//手势拖动布局
sliderIm = (CRImageView) rootView.findViewById(R.id.sliderIM);
mRV.setLayoutManager(new LinearLayoutManager(mParentVG.getContext(), LinearLayoutManager.VERTICAL, false));
mSweetView.setAnimationListener(new AnimationImp());
//设置内容的高度
if (mContentViewHeight > 0) {
mFreeGrowUpParentRelativeLayout.setContentHeight(mContentViewHeight);
}
return rootView;
}
然后需要传入数据集合:
//设置数据源 (数据源支持设置 list 数组,也支持从菜单中获取)
mSweetSheet.setMenuList(list);
实际上是调用了代理抽象类中的方法:
/**
* 设置数据源
*
* @param list
*/
protected abstract void setMenuList(List<MenuEntity> list);
不同的子代理可以通过不同的途径来实现该方法以展示数据的显示,例如列表子代理RecyclerViewDelegate的实现如下:
//设置数据源之后
protected void setMenuList(final List<MenuEntity> menuEntities) {
mMenuRVAdapter = new MenuRVAdapter(menuEntities, SweetSheet.Type.RecyclerView);
mRV.setAdapter(mMenuRVAdapter);
mMenuRVAdapter.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mOnMenuItemClickListener != null) {
if (mOnMenuItemClickListener.onItemClick(position, menuEntities.get(position))) {
delayedDismiss();
}
}
}
});
mRV.setLayoutAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mRV.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
mFreeGrowUpParentRelativeLayout.setClipChildren(false);
}
@Override
public void onAnimationEnd(Animation animation) {
mRV.setOnTouchListener(null);
mFreeGrowUpParentRelativeLayout.setClipChildren(true);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
这里就是简单的设置了适配器和监听器。
然后还可以设置背景的效果:
//根据设置不同Effect 来显示背景效果BlurEffect:模糊效果.DimEffect 变暗效果
mSweetSheet.setBackgroundEffect(new BlurEffect(8));
设置菜单点击监听:
//设置点击事件
mSweetSheet.setOnMenuItemClickListener(new SweetSheet.OnMenuItemClickListener() {
@Override
public boolean onItemClick(int position, MenuEntity menuEntity1) {
//根据返回值, true 会关闭 SweetSheet ,false 则不会.
Toast.makeText(MainActivity.this, menuEntity1.title + " " + position, Toast.LENGTH_SHORT).show();
return true;
}
});
以上就是一些初始化的操作,总结就是再xml的一个父布局中先加入一个背景view,再加入一个内容的view.
接下来就是点击触发弹出效果的过程:
SweetSheet类中主要提供了这三个方法来控制菜单的显示与移除,实际也是通过代理类来实现的:
//显示菜单
public void show() {
if (mDelegate != null) {
mDelegate.show();
} else {
Log.e(Tag, "you must setDelegate before");
}
}
//关闭菜单
public void dismiss() {
if (mDelegate != null) {
mDelegate.dismiss();
} else {
Log.e(Tag, "you must setDelegate before");
}
}
//切换显示关闭菜单
public void toggle() {
if (mDelegate != null) {
mDelegate.toggle();
} else {
Log.e(Tag, "you must setDelegate before");
}
}
由于这三个方法属于共同的操作,所以在抽象代理类中已经就又相应的实现,子类可以看情况重写:
//默认的显示方法,子类需要super调用该方法
protected void show() {
if (getStatus() != SweetSheet.Status.DISMISS) {
return;
}
mBg.setClickable(mIsBgClickEnable);
showShowdown();
}
/**
* 显示模糊背景
*/
protected void showShowdown() {
ViewHelper.setTranslationY(mRootView, 0);
//设置模糊效果
mEffect.effect(mParentVG, mBg);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
//先添加一个iamgeview用作模糊背景
mParentVG.addView(mBg, lp);
//初始化模糊效果为透明
ViewHelper.setAlpha(mBg, 0);
//模糊背景从虚到实
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mBg, "alpha", 0, 1);
objectAnimator.setDuration(400);
objectAnimator.start();
}
/**
* 消失
*/
protected void dismiss() {
if (getStatus() == SweetSheet.Status.DISMISS) {
return;
}
mBg.setClickable(false);
// 隐藏模糊背景, 并在结束的时候移除
dismissShowdown();
//整个布局的向下移动动画
ObjectAnimator translationOut = ObjectAnimator.ofFloat(mRootView,
"translationY", 0, mRootView.getHeight());
translationOut.setDuration(600);
translationOut.setInterpolator(new DecelerateInterpolator());
translationOut.addListener(new SimpleAnimationListener() {
@Override
public void onAnimationStart(Animator animation) {
mStatus = SweetSheet.Status.DISMISSING;
}
@Override
public void onAnimationEnd(Animator animation) {
//设置状态为dismiss,并移除view
mStatus = SweetSheet.Status.DISMISS;
mParentVG.removeView(mRootView);
}
});
translationOut.start();
}
/**
* 隐藏模糊背景, 并在结束的时候移除
*/
protected void dismissShowdown() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mBg, "alpha", 1, 0);
objectAnimator.setDuration(400);
objectAnimator.start();
objectAnimator.addListener(new SimpleAnimationListener() {
@Override
public void onAnimationEnd(Animator animation) {
mParentVG.removeView(mBg);
}
});
}
//切换显示关闭菜单
protected void toggle() {
switch (mStatus) {
case SHOW:
dismiss();
break;
case DISMISS:
show();
break;
default:
break;
}
}
从以上代码可以看出,show的时候先是加入一个背景的view,该view可以设置背景模糊效果,dim效果等等,然后先设置背景为透明,然后再给他一个alpha属性从0到1的动画效果,然后在列表代理类RecyclerViewDelegate中重新实现show方法,这里在加入背景view后,紧接着把内容的view加入到父布局中:
@Override
protected void show() {
super.show();
ViewGroup.LayoutParams lp =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mParentVG.addView(mRootView, lp);
mSweetView.show();
}
然后就是调用SweetView类的show方法,弹出动画的弹性效果就是该方法中实现的:
public void show() {
mStatus = Status.STATUS_SMOOTH_UP;
if (mAnimationListener != null) {
mAnimationListener.onStart();
this.postDelayed(new Runnable() {
@Override
public void run() {
mAnimationListener.onContentShow();
}
}, 600);
}
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, mMaxArcHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
mArcHeight = value;
if (value == mMaxArcHeight) {
duang();
}
invalidate();
}
});
valueAnimator.setDuration(800);
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.start();
}
1.这里首相调用了mAnimationListener.onStart();方法,这里主要是设置列表和手势拖动view的隐藏:
@Override
public void onStart() {
mFreeGrowUpParentRelativeLayout.reset();
mStatus = SweetSheet.Status.SHOWING;
sliderIm.setVisibility(View.INVISIBLE);
mRV.setVisibility(View.GONE);
}
2.延迟600毫秒后就开始调用mAnimationListener.onContentShow();主要是将列表控制设置显示并设置适配器,并执行动画。实现如下:
@Override
public void onContentShow() {
mRV.setVisibility(View.VISIBLE);
mRV.setAdapter(mMenuRVAdapter);
mRV.scheduleLayoutAnimation();
}
然后在RecyclerView的布局动画监听器中有做如下的操作,设置mFreeGrowUpParentRelativeLayout.setClipChildren(false);是让动画的时候绘制可以超出view的范围,就是弹性效果:
mRV.setLayoutAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mRV.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
mFreeGrowUpParentRelativeLayout.setClipChildren(false);
}
@Override
public void onAnimationEnd(Animation animation) {
mRV.setOnTouchListener(null);
mFreeGrowUpParentRelativeLayout.setClipChildren(true);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
3.接着就是设置属性动画,并开启:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, mMaxArcHeight);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
mArcHeight = value;
if (value == mMaxArcHeight) {
duang();
}
invalidate();
}
});
valueAnimator.setDuration(800);
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.start();
这里只是设置了一个属性值的计算,通过invalidate();来最终调用drawBG方法,drawBG方法就是通过Path的api来画弧线,最后到达顶部mMaxArcHeight的高度的时候是一个圆环状:
private void drawBG(Canvas canvas) {
mPath.reset();
int currentPointY = 0;
switch (mStatus) {
case NONE:
currentPointY = mMaxArcHeight;
break;
case STATUS_SMOOTH_UP:
case STATUS_UP:
currentPointY = getHeight() - (int) ((getHeight() - mMaxArcHeight) * Math.min(1, (mArcHeight - mMaxArcHeight / 4) * 2.0 / mMaxArcHeight * 1.3));
break;
case STATUS_DOWN:
currentPointY = mMaxArcHeight;
break;
}
mPath.moveTo(0, currentPointY);
mPath.quadTo(getWidth() / 2, currentPointY - mArcHeight, getWidth(), currentPointY);
mPath.lineTo(getWidth(), getHeight());
mPath.lineTo(0, getHeight());
mPath.lineTo(0, currentPointY);
canvas.drawPath(mPath, mPaint);
}
效果:
然后在到达最大属性值的时候,又会开启另外一个属性值的计算,这里间插值设置了OvershootInterpolator,这个是api22中的新类,是一个超出一点然后再回来的间插值,这个无关紧要:
public void duang() {
mStatus = Status.STATUS_DOWN;
ValueAnimator valueAnimator = ValueAnimator.ofInt(mMaxArcHeight, 0);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mArcHeight = (int) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.addListener(new SimpleAnimationListener() {
@Override
public void onAnimationEnd(Animator animation) {
if (mAnimationListener != null) {
mAnimationListener.onEnd();
}
}
});
valueAnimator.setDuration(500);
valueAnimator.setInterpolator(new OvershootInterpolator(4f));
valueAnimator.start();
}
然后在回弹效果结束钱调用了mAnimationListener.onEnd();方法,这个方法的实现主要是判断可拖动控件是否显示:
@Override
public void onEnd() {
if (mIsDragEnable) {
sliderIm.setVisibility(View.VISIBLE);
sliderIm.circularReveal(sliderIm.getWidth() / 2, sliderIm.getHeight() / 2, 0, sliderIm.getWidth());
}
mStatus = SweetSheet.Status.SHOW;
}
这里主要分析了使用列表的情况,另外的模式就不再累赘描述了。