一,前期基礎知識儲備
最近在為幾個應用接入積分系統,其中使用到彈窗較為頻繁,比如“登入彈窗”,“轉盤彈窗”,“商城彈窗”等等,樣式各有不同,是以就把此前使用的自定義彈窗在修改了一下,然後統一使用該彈窗,為了追求較好的應用内展示表現和穩定的性能。
解鎖資源的彈窗
每日登入彈窗
展示彈窗的地方較多,為了追求統一性,會對彈窗做一些改善。
二,上代碼,具體展示
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.requestWindowFeature(Window.FEATURE_NO_TITLE);
templateDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
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.requestWindowFeature(Window.FEATURE_NO_TITLE);
templateDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
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;
另外,有些機型可能還是會無法實作全屏,建議加上:
templateDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
templateDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
②控制按鈕的點選事件,一般來說,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();
}
});
在動畫執行完畢,在執行按鈕對應的事件。
另外提一個注意的地方,因為部落客的應用中接入了視訊廣告,而積分系統和視訊廣告挂鈎,是以彈窗的表現和視訊廣告的表現也挂鈎,是以,建議将彈窗執行個體寫為成員變量,這樣在視訊廣告的回調中可以控制彈窗的表現。這樣會友善很多。