NewBuiltBottomSheetDialog【新建底部對話框】

版權聲明:本文爲HaiyuKing原創文章,轉載請註明出處!java

前言

演示在底部選項卡上方彈出底部對話框效果。android

效果圖

代碼分析

NewBuiltBottomSheetDialog繼承BottomSheetDialog;git

適配華爲手機手動隱藏虛擬導航欄,監聽屏幕高度變化;github

使用步驟

1、項目組織結構圖

注意事項:app

一、  導入類文件後須要change包名以及從新import R文件路徑ide

二、  Values目錄下的文件(strings.xml、dimens.xml、colors.xml等),若是項目中存在,則複製裏面的內容,不要整個覆蓋佈局

2、導入步驟

在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,用於在監聽屏幕高度變化中獲取屏幕的實際高度值;gradle

須要制定底部選項卡區域高度值:@dimen/tab_bottom_height,用於在監聽屏幕高度變化中獲取屏幕的實際高度值;ui

<?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中。this

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>

3、使用方法

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

相關文章
相關標籤/搜索