Android今日头条适配方案对AlertDialog的影响
前言
前段时间维护公司老项目时发现系统弹框显示不全,右边总是会少一部分,手机比较窄的直接只显示一半.然后找了半天才发现是使用的今日头适配方案。
今日头适配方案
先来说今日头适配方案,简单来说就是直接修改Activity的density值。但是我们在调用AlertDialog弹框时是不会去使用的这个density值,就会导致我的弹框超出屏幕。
使用:
在Application的onCreate方法中调用setDensity,并自己设定一个width,width是一个UI图的参照值,单位是dp,例如参照的是1280*720像素的XHDPI图,width就是720/2 = 360,这个根据你拿到的实际设计图来计算。
然后我们来看下px与dp的转换
不过现在我们可以不用直接去写Density类了,有大佬给你封装好了,拿去直接用,而且提供了取消适配的接口。传送门,具体的使用方法可以参考。
Density类
private static float appDensity;
private static float appScaledDensity;
private static DisplayMetrics appDisplayMetrics;
/**
* 用来参照的的width
*/
private static float WIDTH;
public static void setDensity(@NonNull final Application application, float width) {
appDisplayMetrics = application.getResources().getDisplayMetrics();
WIDTH = width;
registerActivityLifecycleCallbacks(application);
if (appDensity == 0) {
//初始化的时候赋值
appDensity = appDisplayMetrics.density;
appScaledDensity = appDisplayMetrics.scaledDensity;
//添加字体变化的监听
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字体改变后,将appScaledDensity重新赋值
if (newConfig != null && newConfig.fontScale > 0) {
appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
}
private static void setDefault(Activity activity) {
setAppOrientation(activity);
}
private static void setAppOrientation(@Nullable Activity activity) {
float targetDensity = 0;
try {
targetDensity = appDisplayMetrics.widthPixels / WIDTH;
} catch (NumberFormatException e) {
e.printStackTrace();
}
float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
int targetDensityDpi = (int) (160 * targetDensity);
/**
*
* 最后在这里将修改过后的值赋给系统参数
*
* 只修改Activity的density值
*/
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
private static void registerActivityLifecycleCallbacks(Application application) {
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
setDefault(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
AlertDialog
简单介绍一下AlertDialog,它是其他 Dialog的的父类!比如ProgressDialog,TimePickerDialog等,而AlertDialog的父类是:Dialog! 另外AlertDialog并不能直接new出来,如果你打开 AlertDialog的源码,会发现构造方法是protected的,如果我们要创建AlertDialog的话,我们 需要使用到该类中的一个静态内部类:public static class Builder,然后来调用AlertDialog 里的相关方法,来对AlertDialog进行定制,最后调用show()方法来显示我们的AlertDialog对话框。
回到我们的问题:调用AlertDialog弹框时没有使用的Avtiaity的density值,那么问题又来了,那自定义的Dialog,是不是也是这样呢?
我们先写一个AlertDialog
public static AlertDialog.Builder getDialog(Context context) {
return new AlertDialog.Builder(context, R.style.App_Theme_Dialog_Alert);
}
public static AlertDialog.Builder getMessageDialog(
Context context,
String title,
String message,
boolean cancelable) {
return getDialog(context)
.setCancelable(cancelable)
.setTitle(title)
.setMessage(message)
.setPositiveButton("确定", null);
}
在写一个自定义的Dialog
public class ConfirmDialog extends Dialog {
private Context context;
private String content;
private View sLine;
private View inflate;
private TextView dialog_textView, dialog_sure, dialog_cancel;
private OnDialogInterfaceClickListener sureClickListener, cancelClickListener;
private boolean outSide = true;
private String strSureText = "确定",strCancelText = "取消";
private boolean isShowSureView = true,isShowCancelView = true,isShowLineView = true;
public ConfirmDialog(@NonNull Context context) {
super(context, R.style.ActionSheetDialogStyle);
this.context = context;
}
public ConfirmDialog setOnSureClickListener(OnDialogInterfaceClickListener sureClickListener){
this.sureClickListener = sureClickListener;
return this;
}
public ConfirmDialog setCanOutSide(boolean outSide){
this.outSide = outSide;
return this;
}
public ConfirmDialog setOnCancelClickListener(OnDialogInterfaceClickListener cancelClickListener){
this.cancelClickListener = cancelClickListener;
return this;
}
public ConfirmDialog setMessage(String message){
this.content = message;
return this;
}
public ConfirmDialog setSureText(String strSureText){
this.strSureText = strSureText;
return this;
}
public ConfirmDialog setCancelText(String strCancelText){
this.strCancelText = strCancelText;
return this;
}
public ConfirmDialog setShowSureView(boolean isShowSureView){
this.isShowSureView = isShowSureView;
this.isShowLineView = this.isShowSureView;
return this;
}
public ConfirmDialog setShowCancelView(boolean isShowCancelView){
this.isShowCancelView = isShowCancelView;
this.isShowLineView = this.isShowCancelView;
return this;
}
public ConfirmDialog builder() {
initView();
return this;
}
private void initView() {
inflate = LayoutInflater.from(context).inflate(
R.layout.sure_cancel_dialog, null);
// 初始化控件
dialog_textView = inflate.findViewById(R.id.dialog_textView);
dialog_sure = inflate.findViewById(R.id.dialog_sure);
dialog_cancel = inflate.findViewById(R.id.dialog_cancel);
sLine = inflate.findViewById(R.id.s_line);
dialog_sure.setText(strSureText);
dialog_cancel.setText(strCancelText);
if(!isShowLineView)
sLine.setVisibility(View.GONE);
if(!isShowCancelView)
dialog_cancel.setVisibility(View.GONE);
if(!isShowSureView)
dialog_sure.setVisibility(View.GONE);
if (sureClickListener != null) {
dialog_sure.setOnClickListener(v -> {
dismiss();
sureClickListener.onDialogClick(this);
});
} else {
dialog_sure.setOnClickListener(v -> dismiss());
}
if (cancelClickListener != null) {
dialog_cancel.setOnClickListener(v -> {
dismiss();
cancelClickListener.onDialogClick(this);
});
} else
dialog_cancel.setOnClickListener(v -> dismiss());
dialog_textView.setText(content);
setCanceledOnTouchOutside(outSide);
setContentView(inflate);
}
}
结果如下
????????为啥呢
看了一下代码,找到了自动定义Dialog中的setContentView(inflate),我们set了一个我们的自定义的一个View,这个View是我们使用适配方案后的产物,而AlertDialog是使用的原生系统的View,好吧破案了。
inflate = LayoutInflater.from(context).inflate(
R.layout.sure_cancel_dialog, null);
setContentView(inflate);
解决方案
public static void getMessageDialog(
Context context,
String message,
boolean cancelable, DialogInterface.OnClickListener positiveListener) {
AlertDialog dialog = new AlertDialog.Builder(context).create();
dialog.setCancelable(cancelable);
dialog.setMessage(message);
dialog.setButton(AlertDialog.BUTTON_POSITIVE, "确定", positiveListener);
dialog.show();
WindowManager.LayoutParams lp = dialog.getWindow().getAttributes();
//设置宽高,高度默认是自适应的,宽度根据屏幕宽度比例设置
lp.width = ScreenUtils.getScreenWidth(context) * 9 / 10;
//这里设置居中
lp.gravity = Gravity.CENTER;
dialog.getWindow().setAttributes(lp);
}
我们在AlertDialog 的show()方法后设置dialog的弹出位置,这样我们就可以不管系统的原生View就可以把他固定到当前Avtivity上了,当然Dialog也可以这么做。ScreenUtils.getScreenWidth(context)是获取当前Activity的宽,好吧ScreenUtils工具类也贴出来吧
ScreenUtils工具类
public class ScreenUtils {
private ScreenUtils()
{
/* cannot be instantiated */
throw new UnsupportedOperationException("cannot be instantiated");
}
/**
* 获得屏幕宽度
*
* @param context
* @return
*/
public static int getScreenWidth(Context context)
{
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
/**
* 获得屏幕高度
*
* @param context
* @return
*/
public static int getScreenHeight(Context context)
{
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.heightPixels;
}
/**
* 获得状态栏的高度
*
* @param context
* @return
*/
public static int getStatusHeight(Context context)
{
int statusHeight = -1;
try
{
Class<?> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e)
{
e.printStackTrace();
}
return statusHeight;
}
/**
* 获取当前屏幕截图,包含状态栏
*
* @param activity
* @return
*/
public static Bitmap snapShotWithStatusBar(Activity activity)
{
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bmp = view.getDrawingCache();
int width = getScreenWidth(activity);
int height = getScreenHeight(activity);
Bitmap bp = null;
bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
view.destroyDrawingCache();
return bp;
}
/**
* 获取当前屏幕截图,不包含状态栏
*
* @param activity
* @return
*/
public static Bitmap snapShotWithoutStatusBar(Activity activity)
{
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bmp = view.getDrawingCache();
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
int width = getScreenWidth(activity);
int height = getScreenHeight(activity);
Bitmap bp = null;
bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
- statusBarHeight);
view.destroyDrawingCache();
return bp;
}
}