一,前期基础知识储备
最近在为几个应用接入积分系统,其中使用到弹窗较为频繁,比如“登录弹窗”,“转盘弹窗”,“商城弹窗”等等,样式各有不同,所以就把此前使用的自定义弹窗在修改了一下,然后统一使用该弹窗,为了追求较好的应用内展示表现和稳定的性能。
解锁资源的弹窗
每日登录弹窗
展示弹窗的地方较多,为了追求统一性,会对弹窗做一些改善。
二,上代码,具体展示
1)写入布局,
注意需要①控制所有弹窗居中显示;②为所有弹窗写入一个颜色更深的透明弹窗,以突出弹窗内容;
以“解锁资源”弹窗为例,布局结构如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/trans70_black">
<LinearLayout
android:id="@+id/template_dialog_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/dialog_done_root"
android:layout_width="330dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
app:cardBackgroundColor="@color/pureWhite"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/pureWhite"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp">
<androidx.cardview.widget.CardView
android:layout_width="300dp"
android:layout_height="300dp"
app:cardBackgroundColor="#d9d9d9"
app:cardCornerRadius="4dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/dialog_show_img"
android:layout_width="295dp"
android:layout_height="295dp"
android:layout_gravity="center"
android:layout_margin="3dp"
android:scaleType="fitCenter" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
android:orientation="horizontal">
<TextView
android:id="@+id/dialog_ad_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/reward_ad_title"
android:textColor="@color/maincolor"
android:textSize="20sp"
android:textStyle="bold" />
<com.yiwent.viewlib.ShiftyTextview
android:id="@+id/temp_store_coin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="1000"
android:textColor="@color/maincolor"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/dialog_ad_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="15dp"
android:gravity="center"
android:text="@string/reward_ad_content"
android:textColor="@color/reward_content_txt"
android:textSize="14sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="22dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/dialog_ad_coins_img"
android:layout_width="135dp"
android:layout_height="40dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/template_coins_btn" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/dialog_ad_watch_img"
android:layout_width="135dp"
android:layout_height="40dp"
android:layout_marginStart="20dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/template_watch_video_btn" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/dialog_show_back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="40dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_done_dialog_back" />
</LinearLayout>
</RelativeLayout>
相对布局作为整个弹窗的父容器,同时设置一个颜色更深的透明黑色背景;然后写入一个线性布局,此布局含纳所有的布局元素,然后居中显示在相对布局的中心位置。这样就满足了前面的需求。布局结构如下图所示:
布局层级
2)代码里写入控制,包括弹窗展示和点击事件控制;
注意需要控制①按钮的点击事件;②弹窗进出界面时的状态栏导航栏的控制;
private Dialog templateDialog;
private void showRewardAdDialog(String imgPath) {
View view = View.inflate(getActivity(), R.layout.dialog_reward_ad_show, null);
AppCompatImageView showImg = view.findViewById(R.id.dialog_show_img);
AppCompatImageView dialogBack = view.findViewById(R.id.dialog_show_back);
RequestOptions options = new RequestOptions().error(R.drawable.ic_no_network_error)
.centerCrop().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).placeholder(R.color.pureBlack);
Glide.with(getActivity()).load(imgPath).apply(options).into(showImg);
TextView adTitle = view.findViewById(R.id.dialog_ad_title);
TextView adContent = view.findViewById(R.id.dialog_ad_content);
ShiftyTextview temp_store_coin = view.findViewById(R.id.temp_store_coin);
temp_store_coin.setDuration(10);
temp_store_coin.setNumberString(String.valueOf(treasure.getCoinNum()));
adTitle.setTypeface(Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Regular.ttf"));
adContent.setTypeface(Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Regular.ttf"));
temp_store_coin.setTypeface(Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Regular.ttf"));
ImageView adWatchLl = view.findViewById(R.id.dialog_ad_watch_img);
ImageView adCoins = view.findViewById(R.id.dialog_ad_coins_img);
templateDialog = new Dialog(getActivity());
templateDialog.setContentView(view);
templateDialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
int divierId = getResources().getIdentifier("android:id/titleDivider", null, null);
View divider = templateDialog.findViewById(divierId);
if (divider != null) {
divider.setBackgroundColor(Color.TRANSPARENT);
}
adWatchLl.setOnClickListener(v -> {
ObjectAnimator scaleAnimatorX = ObjectAnimator.ofFloat(adWatchLl, "scaleX", 1f, 0.9f);
ObjectAnimator scaleAnimatorSX = ObjectAnimator.ofFloat(adWatchLl, "scaleX", 0.9f, 1f);
ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(adWatchLl, "scaleY", 1f, 0.9f);
ObjectAnimator scaleAnimatorSY = ObjectAnimator.ofFloat(adWatchLl, "scaleY", 0.9f, 01f);
scaleAnimatorSY.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (ToastUtil.isFastClick()) {
isPainted_video = false;
isSpin = false;
isDoubleCoins = false;
isNewImg_video = true;
showRewardAd(); //点击观看视频广告按钮
}
}
});
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleAnimatorX).with(scaleAnimatorY).before(scaleAnimatorSX).before(scaleAnimatorSY);
animSet.setDuration(200L);
animSet.start();
});
adCoins.setOnClickListener(v -> {
ObjectAnimator scaleAnimatorX = ObjectAnimator.ofFloat(adCoins, "scaleX", 1f, 0.9f);
ObjectAnimator scaleAnimatorSX = ObjectAnimator.ofFloat(adCoins, "scaleX", 0.9f, 1f);
ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(adCoins, "scaleY", 1f, 0.9f);
ObjectAnimator scaleAnimatorSY = ObjectAnimator.ofFloat(adCoins, "scaleY", 0.9f, 01f);
scaleAnimatorSY.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (ToastUtil.isFastClick()) {
if (treasure.getCoinNum() >= COIN50) {
/*跳转编辑页 不用做额外的处理 因为付费模板进去肯定会编辑一次 只要编辑过 本地就保存了文件 下次即可直接进入*/
Intent data = new Intent(getActivity(), PaintActivity.class);
data.putExtra(Constant.HOME_FRAGMENT_PAINT, svgResGo);
data.putExtra(Constant.HOME_FRAGMENT_PAINT_NAME, svgNameGo);
data.putExtra(Constant.HOME_FRAGMENT_PAINT_PERCENTAGE, percentageGo);
data.putExtra(PaintApplication.HOME_TO_PAINT, false); //展示视频广告 则不展示插页广告
startActivityForResult(data, PAINT);
/*消耗积分 增加道具*/
int addCoins;
int currentCoins = treasure.getCoinNum();
int totalCoins;
addCoins = -COIN50;
totalCoins = addCoins + currentCoins;
treasure.setCoinNum(totalCoins);
CoinsEvent event = new CoinsEvent();
event.setCoins(totalCoins);
event.setcurCoins(currentCoins);
EventBus.getDefault().post(event);
/*弹窗内积分数值变化*/
temp_store_coin.setDuration(1500);
temp_store_coin.setNumberString(String.valueOf(currentCoins), String.valueOf(totalCoins));
templateDialog.dismiss();
} else {
ToastUtil.showToast(getActivity(), R.string.toast_do_not_have_coins);
}
}
}
});
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleAnimatorX).with(scaleAnimatorY).before(scaleAnimatorSX).before(scaleAnimatorSY);
animSet.setDuration(200L);
animSet.start();
});
dialogBack.setOnClickListener(v -> {
ObjectAnimator scaleAnimatorX = ObjectAnimator.ofFloat(dialogBack, "scaleX", 1f, 0.9f);
ObjectAnimator scaleAnimatorSX = ObjectAnimator.ofFloat(dialogBack, "scaleX", 0.9f, 1f);
ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(dialogBack, "scaleY", 1f, 0.9f);
ObjectAnimator scaleAnimatorSY = ObjectAnimator.ofFloat(dialogBack, "scaleY", 0.9f, 01f);
scaleAnimatorSY.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (ToastUtil.isFastClick()) {
templateDialog.dismiss();
}
}
});
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleAnimatorX).with(scaleAnimatorY).before(scaleAnimatorSX).before(scaleAnimatorSY);
animSet.setDuration(200L);
animSet.start();
});
final Window window = templateDialog.getWindow();
try {
Util.focusNotAle(window);
templateDialog.show();
Util.hideNavigationBar(window);
Util.clearFocusNotAle(window);
} catch (Exception e) {
e.printStackTrace();
}
android.view.WindowManager.LayoutParams p = templateDialog.getWindow().getAttributes();
p.width = WindowManager.LayoutParams.MATCH_PARENT;
p.height = WindowManager.LayoutParams.MATCH_PARENT;
p.gravity = Gravity.CENTER;
templateDialog.setCancelable(false);
templateDialog.setCanceledOnTouchOutside(false);
templateDialog.getWindow().setAttributes(p);
}
①首先,抽出控制弹窗展示的代码部分:
View view = View.inflate(context, R.layout.dialog_reward_ad_show, null);
templateDialog = new Dialog(context);
templateDialog.setContentView(view);
templateDialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
int divierId = context.getResources().getIdentifier("android:id/titleDivider", null, null);
View divider = dialog.findViewById(divierId);
if (divider != null) {
divider.setBackgroundColor(Color.TRANSPARENT);
}
final Window window = templateDialog.getWindow();
try {
focusNotAle(window);
dialog.show();
hideNavigationBar(window);
clearFocusNotAle(window);
} catch (Exception e) {
e.printStackTrace();
}
android.view.WindowManager.LayoutParams p = window.getAttributes();
p.width = WindowManager.LayoutParams.MATCH_PARENT;
p.height = WindowManager.LayoutParams.MATCH_PARENT;
p.gravity = Gravity.CENTER;
if (ScreenUtils.getScreenHeight() / ScreenUtils.getScreenWidth() > 1.9) {
p.y = -Math.round(SizeUtils.dp2px(20));
} else {
p.y = -Math.round(SizeUtils.dp2px(45));
}
templateDialog.setCancelable(false);
templateDialog.setCanceledOnTouchOutside(false);
templateDialog.getWindow().setAttributes(p);
其中使用到三个方法,focusNotAle(window) hideNavigationBar(window) clearFocusNotAle(window),可以控制在显示弹窗时,系统导航栏和状态栏不发生变化,对于弹窗的体验很重要。三个方法具体如下:
public static void focusNotAle(Window window) {
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
public static void clearFocusNotAle(Window window) {
window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
/**
* 隐藏虚拟栏 ,显示的时候再隐藏掉
*
* @param window
*/
public static void hideNavigationBar(final Window window) {
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
window.getDecorView().setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
//布局位于状态栏下方
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
//全屏
View.SYSTEM_UI_FLAG_FULLSCREEN |
//隐藏导航栏
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
if (Build.VERSION.SDK_INT >= 19) {
uiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
} else {
uiOptions |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
}
window.getDecorView().setSystemUiVisibility(uiOptions);
}
});
}
另外一个比较关键的地方,就是代码设置弹窗的大小时,要设置为全屏,即,这样配合上面的导航栏限制,体验良好。
p.width = WindowManager.LayoutParams.MATCH_PARENT;
p.height = WindowManager.LayoutParams.MATCH_PARENT;
p.gravity = Gravity.CENTER;
②控制按钮的点击事件,一般来说,UI会做图控制,这里提供一种代码写就的动画 — “放大缩小”;
dialogBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MobclickAgent.onEvent(context, "main_click_level_mode");
ObjectAnimator scaleAnimatorX = ObjectAnimator.ofFloat(dialogBack, "scaleX", 1f, 0.9f);
ObjectAnimator scaleAnimatorSX = ObjectAnimator.ofFloat(dialogBack, "scaleX", 0.9f, 1f);
ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(dialogBack, "scaleY", 1f, 0.9f);
ObjectAnimator scaleAnimatorSY = ObjectAnimator.ofFloat(dialogBack, "scaleY", 0.9f, 01f);
scaleAnimatorSY.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
spinDialog.dismiss();
}
});
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleAnimatorX).with(scaleAnimatorY).before(scaleAnimatorSX).before(scaleAnimatorSY);
animSet.setDuration(200L);
animSet.start();
}
});
在动画执行完毕,在执行按钮对应的事件。
另外提一个注意的地方,因为博主的应用中接入了视频广告,而积分系统和视频广告挂钩,所以弹窗的表现和视频广告的表现也挂钩,因此,建议将弹窗实例写为成员变量,这样在视频广告的回调中可以控制弹窗的表现。这样会方便很多。