NewBuiltBottomSheetDialog【新建底部对话框】

版权声明:本文为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 APP适配全面屏手机的技术要点

android检测导航栏是否存在的方法

Android判断手机时候有导航栏的方法

Android ContextThemeWrapper cannot be cast to android.app.Activity

Android View坐标系详解(getTop()、getX、getTranslationX...)

项目demo下载地址

https://github.com/haiyuKing/NewBuiltBottomSheetDialogDemo

posted @ 2018-06-10 21:09  HaiyuKing  阅读(828)  评论(0编辑  收藏  举报