Android 仿饿了么底部栏购物车弹出/消失效果
当点击底部栏时,会有一个窗口从下往上弹出来,且背景色变暗。点击背景或按返回键时,窗口会由上往下消失,然后背景色变亮。
看到这点,我们很容易想到使用Dialog来实现效果,下面就来尝试下吧。
方案1:
首先是要自定义Dialog,且给Dialog添加动画效果,如下。
Dialog dialog = new Dialog(MainActivity.this,R.style.Theme_AppCompat_Dialog);
dialog.setContentView(LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog, null));
Window dialogWindow = dialog.getWindow();
dialogWindow.getDecorView().setPadding(0, 0, 0, 0);// 边距设为0
dialogWindow.setBackgroundDrawableResource(android.R.color.transparent);//背景透明,不然会有个白色的东东
dialogWindow.setWindowAnimations(R.style.dialogWindowAnim); //设置窗口弹出动画
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT; // 宽度
lp.height = 300; // 高度
dialogWindow.setAttributes(lp);
dialogWindow.setGravity(Gravity.BOTTOM);
// 弹出dialog
dialog.show();
R.style.dialogWindowAnim里定义了Dialog的弹出、消失动画,如下。
<style name="dialogWindowAnim" parent="android:Animation" >
<item name="android:windowEnterAnimation">@anim/show</item>
<item name="android:windowExitAnimation">@anim/exit</item>
</style>
弹出动画 show.xml,如下。
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="0"
android:fromYDelta="100%"
android:toXDelta="0"
android:toYDelta="0">
</translate>
消失动画 exit.xml,如下。
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="100%"
android:duration="300">
</translate>
因为有些麻烦,所以R.layout.dialog的布局暂不贴出 = =(不贴大家都知道它长什么样~)
Done!我们得到这样的效果。
这个实现的效果和饿了么还有有挺大区别的。饿了么是弹出商品菜单,而底部栏是不动的,而我们实现的效果则是底部栏+商品菜单一起弹出、消失。
因为我们是给整个Dialog添加了动画,而底部栏(绿色)+商品菜单(青蓝色)都在Dialog里面,所以会一起执行动画效果,所以给直接给Dialog添加动画的办法是行不通的。
那么就可以想到,只给商品菜单添加弹出、消失动画。
方案2:
新方案的 R.layout.dialog如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80333333">
<!--半透明遮罩-->
<View
android:id="@+id/v_dimiss"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#00000000"/>
<!--商品菜单-->
<View
android:background="#97FFFF"
android:id="@+id/v_anim"
android:layout_width="match_parent"
android:layout_height="300dp"
/>
<!--底部栏-->
<RelativeLayout
android:id="@+id/rl"
android:background="#fff"
android:layout_width="match_parent"
android:layout_height="70dp">
<View
android:background="#7FFF00"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"/>
<ImageView
android:layout_marginLeft="3dp"
android:src="@mipmap/a7"
android:layout_width="40dp"
android:layout_height="40dp" />
</RelativeLayout>
</LinearLayout>
所以Dialog的界面结构应该是这样的,Dialog要占据整个屏幕。
Q:为什么要我们自己做1个遮罩?而不用Dialog自带的效果?
A:首先Dialog里的底部栏和Activity界面的底部栏应该一模一样的且把Activity界面的底部栏给盖住了。而当Dialog弹出来时,Activity界面的底部栏实际上会变暗(只是我们看不到)。然后但Dialog消失时,屏幕就会由暗变亮,所以肉眼看到的效果就是底部栏会闪一下。
因此为了避免底部栏闪一下这个效果,我们要禁用Dialog弹出时,外部变暗的这个效果。并且为了模拟Dialog的效果,我们自己要画1个半透明遮罩。
<style name="myDialog" parent="Theme.AppCompat.Dialog">
<item name="android:backgroundDimEnabled">false</item><!--activity不变暗-->
</style>
先拿到Dialog里面的各种View,如下。
// 部分代码
dialog = new CustomDialog(context, R.style.myDialog);// 风格,activity不变暗
view = LayoutInflater.from(context).inflate(R.layout.dialog, null); // dialog布局
dismissView = view.findViewById(R.id.v_dimiss);// 半透明遮罩布局
animView = view.findViewById(R.id.v_anim); // 商品菜单布局
dialog.setContentView(view);
接下来我们就可以操作商品菜单的动画效果了。首先是弹出动画。
// 部分代码
public void showAnim() {
TranslateAnimation animation = new TranslateAnimation(0, 0, DensityUtil.dp2px(context, 300), 0);// 位移动画,DensityUtil是尺寸转换工具。因为布局填了300dp,所以这里要用300dp转px
animation.setDuration(300);
animView.startAnimation(animation);
}
那么动画什么时候执行呢?应该当Dialog弹出后才执行,如下。
// 部分代码
dialog.show();
showAnim();
然后是商品菜单的消失动画,那么消失动画什么时候执行呢?我们很容易想到在Dialog.onDismiss()的回调里执行,但这个是行不通的。因为执行onDismiss回调时,dialog已经消失了,所以商品菜单的消失动画也是看不到了。那么我们只能在dialog消失之前执行商品菜单的消失动画,遗憾的是Dialog并没有提供消失前的回调方法,所以我们只能进入Dialog的源码,看下怎么样能在Dialog消失前,执行商品菜单的消失动画。
Dialog消失,会执行Dialog.dimiss()方法,进去看看。
// dialog源码
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
dismiss方法的声明是public的,意味着可以重写,这样就好办了。
自定义Diloag, CustomDialog的完整代码
public class CustomDialog extends Dialog {
IBeforeDismiss iBeforeDismiss;
public CustomDialog(Context context, int themeResId) {
super(context, themeResId);
}
@Override
public void dismiss(){
iBeforeDismiss.onBeforeDismiss();
}
// 真正让dialog消失
public void myDismiss() {
super.dismiss();// dialog消失
}
// dismiss前执行
interface IBeforeDismiss {
void onBeforeDismiss();
}
public void setBeforeDismiss(IBeforeDismiss iBeforeDismiss) {
this.iBeforeDismiss = iBeforeDismiss;
}
}
这样一来,当Dialog执行dismiss时,并不会让Dialog消失,而是执行IBeforeDismiss回调,然后我们在IBeforeDismiss回调里执行商品菜单消失动画,当消失动画执行完后,才让Dialog真正消失,即执行myDismiss方法。
// 部分代码
dialog.setBeforeDismiss(new CustomDialog.IBeforeDismiss() {
@Override
public void onBeforeDismiss() {
dismissAnim();// 商品菜单消失动画
}
});
// 部分代码
// 消失动画
private void dismissAnim() {
TranslateAnimation animation = new TranslateAnimation(0, 0, 0, DensityUtil.dp2px(context, 300));
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
dialog.myDismiss();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
animation.setDuration(300);
animView.startAnimation(animation);
}
当然,别忘了给半透明遮罩添加点击事件,因为一般点击遮罩时,dialog都会消失。
// 部分代码
dismissView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
Done!我们得到了如下的效果。
可以看到和饿了么的效果是一样的。
自定义 Dilaog完整代码如下:
package com.yj.yttr;
import android.content.Context;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
public class MyDialog {
CustomDialog dialog;
View view; // dialog布局
View animView; // 商品菜单布局
View dismissView; // 半透明遮罩布局
Context context;
public MyDialog(Context context) {
this.context = context;
dialog = new CustomDialog(context, R.style.myDialog);
// dialog的样式
view = LayoutInflater.from(context).inflate(R.layout.dialog, null);
animView = view.findViewById(R.id.v_anim);
dialog.setContentView(view);
dismissView = view.findViewById(R.id.v_dimiss);
dismissView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.setBeforeDismiss(new CustomDialog.IBeforeDismiss() {
@Override
public void onBeforeDismiss() {
dismissAnim();
}
});
// 设置dialog的位置
Window dialogWindow = dialog.getWindow();
dialogWindow.getDecorView().setPadding(0, 0, 0, 0);
dialogWindow.setBackgroundDrawableResource(android.R.color.transparent);//背景透明,不然会有个白色的东东
// dialogWindow.setWindowAnimations(R.style.dialogWindowAnim); //不使用窗口弹出动画
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT; // 宽度
lp.height = WindowManager.LayoutParams.MATCH_PARENT; // 高度
dialogWindow.setAttributes(lp);
// 设置dialog为底部
dialogWindow.setGravity(Gravity.BOTTOM);
}
public void show() {
dialog.show();
showAnim();
}
// 出现动画
private void showAnim() {
TranslateAnimation animation = new TranslateAnimation(0, 0, DensityUtil.dp2px(context, 300), 0);
animation.setDuration(300);
animView.startAnimation(animation);
}
// 消失动画
private void dismissAnim() {
TranslateAnimation animation = new TranslateAnimation(0, 0, 0, DensityUtil.dp2px(context, 300));
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
dialog.myDismiss();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
animation.setDuration(300);
animView.startAnimation(animation);
}
}
这种实现方式成本很低,只需要继承Dialog,重写dismiss方法,为其添加一个消失前事件监听即可。
这里只是抛砖引玉,有更好更方便的实现方式欢迎留言~