版权声明:本文为HaiyuKing原创文章,转载请注明出处!
前言
演示在底部选项卡上方弹出底部对话框效果。
效果图
代码分析
NewBuiltBottomSheetDialog继承BottomSheetDialog;
适配华为手机手动隐藏虚拟导航栏,监听屏幕高度变化;
使用步骤
一、项目组织结构图
注意事项:
1、 导入类文件后需要change包名以及重新import R文件路径
2、 Values目录下的文件(strings.xml、dimens.xml、colors.xml等),如果项目中存在,则复制里面的内容,不要整个覆盖
二、导入步骤
在APP中的bundle.gradle文件中添加以下代码,引入design【版本号跟appcompat-v7的保持一致】
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.why.project.newbuiltbottomsheetdialogdemo"
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//BottomSheetDialog
compile "com.android.support:design:27.1.1"
}
首页界面底部选项卡区域布局文件
需要指定底部选项卡区域的id值:@+id/bottom_layout,用于在监听屏幕高度变化中获取屏幕的实际高度值;
需要制定底部选项卡区域高度值:@dimen/tab_bottom_height,用于在监听屏幕高度变化中获取屏幕的实际高度值;
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- 中间fragment切换区域 --> <FrameLayout android:id="@+id/id_floating_dragger_center_layout" android:layout_width="match_parent" android:layout_height="0.0dp" android:layout_weight="1"></FrameLayout> <!-- 阴影部分 --> <View android:layout_width="match_parent" android:layout_height="3dp" android:background="@drawable/home_tab_bottom_line"/> <!-- 底部选项卡区域 --> <LinearLayout android:id="@+id/bottom_layout" android:layout_width="match_parent" android:layout_height="@dimen/tab_bottom_height" android:orientation="horizontal" android:gravity="center" android:background="#ffffff"> <!-- 添加 --> <Button android:id="@+id/btn_add" android:layout_width="46dp" android:layout_height="46dp" android:background="@drawable/home_tab_add" android:gravity="center" android:layout_gravity="center" /> </LinearLayout> </LinearLayout>
首页界面监听屏幕高度变化,获取屏幕实际高度值的方法
声明一个变量,存储屏幕的实际高度值,并传入NewBuiltBottomSheetDialog中。
package com.why.project.newbuiltbottomsheetdialogdemo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.ViewTreeObserver; import android.widget.Button; import android.widget.Toast; import com.why.project.newbuiltbottomsheetdialogdemo.dialog.NewBuiltBottomSheetDialog; public class MainActivity extends AppCompatActivity { private Button btn_add; private int displayHeight = 0;//屏幕显示的高度值(不包括虚拟导航栏的高度) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initEvents(); } private void initViews() { btn_add = findViewById(R.id.btn_add); } private void initEvents() { //监听屏幕高度变化 View rootView = this.getWindow().getDecorView(); rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { //https://blog.csdn.net/u013872857/article/details/53750682 int[] loc = new int[2]; findViewById(R.id.bottom_layout).getLocationOnScreen(loc); displayHeight = loc[1] + getResources().getDimensionPixelSize(R.dimen.tab_bottom_height);//底部区域+底部的高度值=显示区域的高度值 } }); btn_add.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { NewBuiltBottomSheetDialog bottomSheetDialog = new NewBuiltBottomSheetDialog(MainActivity.this,displayHeight); bottomSheetDialog.setOnCustomButtonClickListener(new NewBuiltBottomSheetDialog.OnCustomButtonClickListener() { @Override public void onAddNoteButtonClick() { //打开新建笔记界面 Toast.makeText(MainActivity.this,"新建笔记",Toast.LENGTH_SHORT).show(); } @Override public void onAddFileButtonClick() { //打开新建文件界面 Toast.makeText(MainActivity.this,"新建文件",Toast.LENGTH_SHORT).show(); } @Override public void onAddPhotoButtonClick() { //打开新建图片界面 Toast.makeText(MainActivity.this,"新建图片",Toast.LENGTH_SHORT).show(); } @Override public void onAddVideoButtonClick() { //打开新建视频界面 Toast.makeText(MainActivity.this,"新建视频",Toast.LENGTH_SHORT).show(); } }); bottomSheetDialog.show(); } }); } }
打开的新建底部对话框布局文件
关键在于需要指定内边距的下方值android:paddingBottom="@dimen/tab_bottom_height",高度值就是首页的底部选项卡区域的高度值。
<?xml version="1.0" encoding="utf-8"?> <!-- 首页底部的添加按钮打开的底部对话框 --> <!-- android:paddingBottom="@dimen/tab_bottom_height"是关键 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rootlayout" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/tab_bottom_height" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:background="@drawable/home_popwidow_bg" android:gravity="center" android:paddingBottom="5dp" > <!-- 新建笔记 --> <LinearLayout android:layout_width="0.0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/tv_addNote" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="新建笔记" android:textSize="14sp" android:textColor="#555556" android:gravity="center" android:layout_gravity="center" android:drawablePadding="5dp"/> </LinearLayout> <!-- 新建文件 --> <LinearLayout android:layout_width="0.0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/tv_addFile" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="新建文件" android:textSize="14sp" android:textColor="#555556" android:gravity="center" android:layout_gravity="center" android:drawablePadding="5dp"/> </LinearLayout> <!-- 新建图片 --> <LinearLayout android:layout_width="0.0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/tv_addPhoto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="新建图片" android:textSize="14sp" android:textColor="#555556" android:gravity="center" android:layout_gravity="center" android:drawablePadding="5dp"/> </LinearLayout> <!-- 新建视频 --> <LinearLayout android:layout_width="0.0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/tv_addVideo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="新建视频" android:textSize="14sp" android:textColor="#555556" android:gravity="center" android:layout_gravity="center" android:drawablePadding="5dp"/> </LinearLayout> </LinearLayout> </LinearLayout>
打开的新建底部对话框
package com.why.project.newbuiltbottomsheetdialogdemo.dialog; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.design.widget.BottomSheetDialog; import android.util.DisplayMetrics; import android.view.Display; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.TextView; import com.why.project.newbuiltbottomsheetdialogdemo.R; import java.lang.reflect.Method; /** * Created by HaiyuKing * Used */ public class NewBuiltBottomSheetDialog extends BottomSheetDialog { private static final String TAG = NewBuiltBottomSheetDialog.class.getSimpleName(); private Context mContext; private int displayHeight_build;//屏幕显示的高度值,从activity中传入,用于判断是否存在虚拟导航栏 private TextView tv_addNote; private TextView tv_addFile; private TextView tv_addPhoto; private TextView tv_addVideo; public NewBuiltBottomSheetDialog(@NonNull Context context, int displayHeight) { super(context); mContext = context; this.displayHeight_build = displayHeight; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.dialog_bottomsheet_new_built); //可以变相实现底部外边距效果 int screenHeight = getScreenHeight(scanForActivity(mContext)); int statusBarHeight = getStatusBarHeight(getContext()); int navigationBarHeight = getNavigationBarHeight(getContext());//底部虚拟导航高度 //如果传入的displayHeight_build == 0,那么就使用默认的方法(存在的问题是,显示虚拟导航栏打开APP后,使用过程中隐藏虚拟导航栏,再打开对话框的时候,显示的位置不正确) int dialogHeight = screenHeight - navigationBarHeight - dip2px(mContext,0);//dip2px(mContext,0)预留在这里,如果以后想要调整距离的话 if(displayHeight_build > 0){ dialogHeight = displayHeight_build - navigationBarHeight - dip2px(mContext,0);//dip2px(mContext,0)预留在这里,如果以后想要调整距离的话 } // getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, dialogHeight == 0 ? ViewGroup.LayoutParams.MATCH_PARENT : dialogHeight); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);//红米6pro适配 //设置透明 getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundResource(android.R.color.transparent); initViews(); initEvents(); } /**获取实际屏幕高度,不包括虚拟功能高度*/ private int getScreenHeight(Activity activity) { DisplayMetrics displaymetrics = new DisplayMetrics(); Display d = activity.getWindowManager().getDefaultDisplay(); d.getMetrics(displaymetrics); return displaymetrics.heightPixels; } /**获取状态栏高度值*/ private int getStatusBarHeight(Context context) { int statusBarHeight = 0; Resources res = context.getResources(); int resourceId = res.getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = res.getDimensionPixelSize(resourceId); } return statusBarHeight; } /** * 获取底部虚拟导航栏高度 */ public int getNavigationBarHeight(Context activity) { boolean hasNavigationBar = navigationBarExist(scanForActivity(activity)) && !vivoNavigationGestureEnabled(activity); if (!hasNavigationBar) {//如果不含有虚拟导航栏,则返回高度值0 return 0; } Resources resources = activity.getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); //获取NavigationBar的高度 int height = resources.getDimensionPixelSize(resourceId); return height; } /*========================================方法1======================================================*/ /** * 通过获取不同状态的屏幕高度对比判断是否有NavigationBar * https://blog.csdn.net/u010042660/article/details/51491572 * https://blog.csdn.net/android_zhengyongbo/article/details/68941464*/ public boolean navigationBarExist(Activity activity) { WindowManager windowManager = activity.getWindowManager(); Display d = windowManager.getDefaultDisplay(); DisplayMetrics realDisplayMetrics = new DisplayMetrics(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { d.getRealMetrics(realDisplayMetrics); } int realHeight = realDisplayMetrics.heightPixels; int realWidth = realDisplayMetrics.widthPixels; DisplayMetrics displayMetrics = new DisplayMetrics(); d.getMetrics(displayMetrics); int displayHeight = displayMetrics.heightPixels; int displayWidth = displayMetrics.widthPixels; if(this.displayHeight_build > 0){ displayHeight = displayHeight_build; } return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0; } /*========================================方法2======================================================*/ /** * 检测是否有底部虚拟导航栏【有点儿问题,当隐藏虚拟导航栏后,打开APP,仍然判断显示了虚拟导航栏】 * @param context * @return */ public boolean checkDeviceHasNavigationBar(Context context) { boolean hasNavigationBar = false; Resources rs = context.getResources(); int id = rs.getIdentifier("config_showNavigationBar", "bool", "android"); if (id > 0) { hasNavigationBar = rs.getBoolean(id); } try { Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); Method m = systemPropertiesClass.getMethod("get", String.class); String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); if ("1".equals(navBarOverride)) { hasNavigationBar = false; } else if ("0".equals(navBarOverride)) { hasNavigationBar = true; } } catch (Exception e) { } return hasNavigationBar; } /** * 获取vivo手机设置中的"navigation_gesture_on"值,判断当前系统是使用导航键还是手势导航操作 * @param context app Context * @return false 表示使用的是虚拟导航键(NavigationBar), true 表示使用的是手势, 默认是false * https://blog.csdn.net/weelyy/article/details/79284332#更换部分被拉伸的图片资源文件 * 由于全面屏手机都没有底部的Home,Back等实体按键,因此,大多数全面屏手机都是支持虚拟导航键,即通过上面的方法checkDeviceHasNavigationBar获取的返回值都是true。 */ public boolean vivoNavigationGestureEnabled(Context context) { int val = Settings.Secure.getInt(context.getContentResolver(), "navigation_gesture_on", 0); return val != 0; } /**解决java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activity问题 * https://blog.csdn.net/yaphetzhao/article/details/49639097*/ private Activity scanForActivity(Context cont) { if (cont == null) return null; else if (cont instanceof Activity) return (Activity)cont; else if (cont instanceof ContextWrapper) return scanForActivity(((ContextWrapper)cont).getBaseContext()); return null; } /** * 获取dp的px值*/ public int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } private void initViews() { tv_addNote = (TextView) findViewById(R.id.tv_addNote); tv_addFile = (TextView) findViewById(R.id.tv_addFile); tv_addPhoto = (TextView) findViewById(R.id.tv_addPhoto); tv_addVideo = (TextView) findViewById(R.id.tv_addVideo); } private void initEvents() { tv_addNote.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(mOnCustomButtonClickListener != null){ mOnCustomButtonClickListener.onAddNoteButtonClick(); } dismiss(); } }); tv_addFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(mOnCustomButtonClickListener != null){ mOnCustomButtonClickListener.onAddFileButtonClick(); } dismiss(); } }); tv_addPhoto.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(mOnCustomButtonClickListener != null){ mOnCustomButtonClickListener.onAddPhotoButtonClick(); } dismiss(); } }); tv_addVideo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(mOnCustomButtonClickListener != null){ mOnCustomButtonClickListener.onAddVideoButtonClick(); } dismiss(); } }); } public static abstract interface OnCustomButtonClickListener { //新建笔记按钮的点击事件接口 public abstract void onAddNoteButtonClick(); //新建文件按钮的点击事件接口 public abstract void onAddFileButtonClick(); //新建图集按钮的点击事件接口 public abstract void onAddPhotoButtonClick(); //新建视频按钮的点击事件接口 public abstract void onAddVideoButtonClick(); } private OnCustomButtonClickListener mOnCustomButtonClickListener; public void setOnCustomButtonClickListener(OnCustomButtonClickListener mOnCustomButtonClickListener) { this.mOnCustomButtonClickListener = mOnCustomButtonClickListener; } }
首页底部选项卡的高度值
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- 底部选项卡高度值 --> <dimen name="tab_bottom_height">52dp</dimen> </resources>
三、使用方法
package com.why.project.newbuiltbottomsheetdialogdemo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.ViewTreeObserver; import android.widget.Button; import android.widget.Toast; import com.why.project.newbuiltbottomsheetdialogdemo.dialog.NewBuiltBottomSheetDialog; public class MainActivity extends AppCompatActivity { private Button btn_add; private int displayHeight = 0;//屏幕显示的高度值(不包括虚拟导航栏的高度) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initEvents(); } private void initViews() { btn_add = findViewById(R.id.btn_add); } private void initEvents() { //监听屏幕高度变化 View rootView = this.getWindow().getDecorView(); rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { //https://blog.csdn.net/u013872857/article/details/53750682 int[] loc = new int[2]; findViewById(R.id.bottom_layout).getLocationOnScreen(loc); displayHeight = loc[1] + getResources().getDimensionPixelSize(R.dimen.tab_bottom_height);//底部区域+底部的高度值=显示区域的高度值 } }); btn_add.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { NewBuiltBottomSheetDialog bottomSheetDialog = new NewBuiltBottomSheetDialog(MainActivity.this,displayHeight); bottomSheetDialog.setOnCustomButtonClickListener(new NewBuiltBottomSheetDialog.OnCustomButtonClickListener() { @Override public void onAddNoteButtonClick() { //打开新建笔记界面 Toast.makeText(MainActivity.this,"新建笔记",Toast.LENGTH_SHORT).show(); } @Override public void onAddFileButtonClick() { //打开新建文件界面 Toast.makeText(MainActivity.this,"新建文件",Toast.LENGTH_SHORT).show(); } @Override public void onAddPhotoButtonClick() { //打开新建图片界面 Toast.makeText(MainActivity.this,"新建图片",Toast.LENGTH_SHORT).show(); } @Override public void onAddVideoButtonClick() { //打开新建视频界面 Toast.makeText(MainActivity.this,"新建视频",Toast.LENGTH_SHORT).show(); } }); bottomSheetDialog.show(); } }); } }
混淆配置
无
参考资料
Android ContextThemeWrapper cannot be cast to android.app.Activity
Android View坐标系详解(getTop()、getX、getTranslationX...)