版权声明:本文为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...)