MainActivity.java

    private long backKeyPressedTime = 0;
    private Toast toast;
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {
        	// 웹뷰 History상 이전 페이지가 있을 경우
            if(mWebView.canGoBack()){
                mWebView.goBack(); // 뒤로가기
                return true;
            }
            // 없을 경우 앱 종료 전 Toast로 물어보기
            else{
            	// 토스트메세지 출력
                if (System.currentTimeMillis() > backKeyPressedTime + 2000) {
                    backKeyPressedTime = System.currentTimeMillis();
                    toast = Toast.makeText(this, "뒤로가기 버튼을 한번 더 누르시면 종료됩니다.", Toast.LENGTH_SHORT);
                    toast.show();
                    return true;
                }
                // 토스트메세지가 있는 상태에서 뒤로가기를 한번 더 누르면 앱 종료
                else if (System.currentTimeMillis() <= backKeyPressedTime + 2000) {
                    finish();
                    toast.cancel();
                }
            }
        }
        return super.onKeyDown(keyCode, event);
    }

 

최종 결과물 

Activity에서 처리하는 경우

activity_main.xml

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <WebView
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:id="@+id/mWebview"/>
    </LinearLayout>

MainActivity.java

    WebView mWebView;
    WebSettings mWebSettings;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView = (WebView) findViewById(R.id.mWebview); // 웹뷰 선언
        mWebSettings = mWebView.getSettings();

        mWebView.setWebChromeClient(new WebChromeClient()); // 크롬클라이언트 사용
        mWebView.setWebViewClient(new WebViewClientClass()); // 웹클라이언트 설정 (아래)
        mWebSettings.setJavaScriptEnabled(true); // 자바스크립트 허용
        mWebSettings.setSupportMultipleWindows(false); // 여러 창 또는 탭 열리는 것 비허용
        mWebSettings.setLoadWithOverviewMode(true); // 페이지 내에서만 이동하게끔
        mWebSettings.setUseWideViewPort(true); // 페이지를 웹뷰 width에 맞춤
        mWebSettings.setSupportZoom(false); // 확대 비활성화
        mWebSettings.setBuiltInZoomControls(false); // 확대 비활성화
        mWebSettings.setCacheMode(mWebSettings.LOAD_NO_CACHE); // 캐시 사용안함 (매번 새로 로딩)
        mWebSettings.setDomStorageEnabled(true); // 로컬스토리지 사용 허용

        mWebView.loadUrl("접속할 URL");
    }

    // 웹클라이언트 세부 설정
    private class WebViewClientClass extends WebViewClient {
        // SSL 인증서 무시
        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
        }

        // 페이지 내에서만 url 이동하게끔 만듬
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    }

AndroidManifest에서 처리하는 경우

AndroidManifest.xml

    <application
        ...
        android:usesCleartextTraffic="true">
        ...

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private FragmentManager fragmentManager;
    private Fragment_Main fragment_main;
    private Fragment_MyPage fragment_my_page;
    private FragmentTransaction fragmentTransaction;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fragmentManager = getSupportFragmentManager();

        fragment_main = new Fragment_Main();
        fragment_my_page = new Fragment_MyPage();

        fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, fragment_my_page).commitNowAllowingStateLoss();
    }

    public void MenuClickHandler(View v){
        fragmentManager = getSupportFragmentManager();
        fragmentTransaction = fragmentManager.beginTransaction();

        switch (v.getId()){
            case R.id.btn_main:
                fragmentTransaction.replace(R.id.frameLayout, fragment_main).commitNowAllowingStateLoss();
                break;
            case R.id.btn_my_page:
                fragmentTransaction.replace(R.id.frameLayout, fragment_my_page).commitNowAllowingStateLoss();
                break;
        }
    }
}

 

activity_main.xml

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/linearLayout"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent">

        <Button
            android:id="@+id/btn_main"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="MenuClickHandler"
            android:layout_weight="1"
            android:text="Main" />

        <Button
            android:id="@+id/btn_my_page"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="MenuClickHandler"
            android:layout_weight="1"
            android:text="MyPage" />
    </LinearLayout>

 

각 Fragment Java

public class Fragment_이름 extends Fragment {
    @Nullable
    @Override
    public View onCreateView(
        @NonNull LayoutInflater inflater,
        @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState
    ){
        return inflater.inflate(R.layout.fragment_이름, container, false);
    }
}

 

테스트용 Fragment Layout xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="This" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="is" />

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="a" />

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Main Page" />
    </LinearLayout>
</LinearLayout>

 

 

결과물

 

타이틀 바 없애기

경로 : res/values/themes.xml , res/values-night/themes.xml

<style name="Theme.앱이름" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
  ...생략

  <!-- Customize your theme here. -->

  <!--타이틀 바 없애기-->
  <item name="windowActionBar">false</item>
  <item name="windowNoTitle">true</item>  
</style>

상태 바 없애기

경로 : MainActivity.java

import android.os.Build;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;

...
public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /* 전체화면 코드 시작 */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      final WindowInsetsController insetsController = getWindow().getInsetsController();
      if (insetsController != null) {
        insetsController.hide(WindowInsets.Type.statusBars());
      }
    } else {
      getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_FULLSCREEN,
        WindowManager.LayoutParams.FLAG_FULLSCREEN
      );
    }
    /* 전체화면 코드 끝 */
  }
  ...
}

Before / After

노치 / 펀치홀 대응 (안드로이드 9 이상)

경로 :res/values-v28/themes.xml,res/values-night-v28/themes.xml

<style name="Theme.앱 이름" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
  ... 중략 ...
  <!-- Customize your theme here. -->

  <!--타이틀 바 없애기-->
  <item name="windowActionBar">false</item>
  <item name="windowNoTitle">true</item>

  <!-- 노치 / 펀치 홀 대응 -->
  <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
  <item name="android:windowTranslucentStatus">true</item>
  <item name="android:windowTranslucentNavigation">true</item>
</style>

최종 결과물

+ Recent posts