안드로이드/앱개발(Android)

(코틀린 kotlin) 웹뷰 앱

김염인 2022. 1. 25. 16:00

목차

  1. 인트로, 프로젝트 셋업
  2. 기본 UI 구성하기
  3. URL 로딩 기능 구현하기
  4. 네비게이션 기능 구현하기
  5. 완성도 높이기

결과화면

간단한 웹뷰를 구현하는 앱이다. 

1. 기본 UI 구성하기

크게 ConstraintLayout과 밑에 WebView가 뜰 수 있는 SwipeRefreshLayout으로 나누어 준다.
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/refreshLayout"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@id/toolbar">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

Swiperefreshlayout은 사용자가 수동으로 업데이트 를 요청할 수 있도록 한다. 

Swiperefreshlayout이 적용되어 있는 Activity에 수직으로 pull하면 업데이트가 트리거된다.

 build.gradle (Module: app) 수정을 하여 implements를 추가 해준다.

dependencies {
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
}

 

그리고 가장 하단부에 ContentLoadingProgressBar를 추가해준다. 이 Bar는 웹뷰가 로딩 되는 동안 ProgressBar가 보이도록 한다.

<androidx.core.widget.ContentLoadingProgressBar
    android:id="@+id/progressBar"
    style="@style/Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="0dp"
    android:layout_height="2dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@id/toolbar" />

ContentLoadingProgressBar

https://developer.android.com/reference/androidx/core/widget/ContentLoadingProgressBar

로딩바가 떳을때 이게 떳다가 사라지는걸 사용자가 인지할수 있게 (가변적인 로딩바에 대해서) 해주는 위젯.

ProgressBar를 상속받는다.

 


2. WebView 세팅 하기

     val webView = findViewById(R.id.webview) // 웹뷰 셋팅
    webView.apply {
        webViewClient = WebViewClient() // 하이퍼링크 클릭시 새창 띄우기 방지
        webChromeClient = WebChromeClient() // 크롬환경에 맞는 세팅을 해줌. 특히, 알람등을 받기위해서는 꼭 선언해주어야함 (alert같은 경우)
        settings.javaScriptEnabled = true // 자바스크립트 허용
        settings.javaScriptCanOpenWindowsAutomatically = false
        // 팝업창을 띄울 경우가 있는데, 해당 속성을 추가해야 window.open() 이 제대로 작동 , 자바스크립트 새창도 띄우기 허용여부
        settings.setSupportMultipleWindows(false) // 새창 띄우기 허용 여부 (멀티뷰)
        settings.loadsImagesAutomatically = true // 웹뷰가 앱에 등록되어 있는 이미지 리소스를 자동으로 로드하도록 설정하는 속성
        settings.useWideViewPort = true // 화면 사이즈 맞추기 허용 여부
        settings.loadWithOverviewMode = true // 메타태그 허용 여부
        settings.setSupportZoom(true) // 화면 줌 허용여부
        settings.builtInZoomControls = false // 화면 확대 축소 허용여부
        settings.displayZoomControls = false // 줌 컨트롤 없애기.
        settings.cacheMode = WebSettings.LOAD_NO_CACHE // 웹뷰의 캐시 모드를 설정하는 속성으로써 5가지 모드
        /*
        (1) LOAD_CACHE_ELSE_NETWORK 기간이 만료돼 캐시를 사용할 수 없을 경우 네트워크를 사용합니다.
        (2) LOAD_CACHE_ONLY 네트워크를 사용하지 않고 캐시를 불러옵니다.
        (3) LOAD_DEFAULT 기본적인 모드로 캐시를 사용하고 만료된 경우 네트워크를 사용해 로드합니다.
        (4) LOAD_NORMAL 기본적인 모드로 캐시를 사용합니다.
        (5) LOAD_NO_CACHE 캐시모드를 사용하지 않고 네트워크를 통해서만 호출합니다.
         */
        settings.setAppCacheEnabled(false) // 앱 내부 캐시 사용 여부 설정 (Deprecated )
        settings.domStorageEnabled = true // 로컬 스토리지 사용 여부를 설정하는 속성으로 팝업창등을 '하루동안 보지 않기' 기능 사용에 필요
        settings.allowContentAccess // 웹뷰 내에서 파일 액세스 활성화 여부
        settings.userAgentString = "app" // 웹에서 해당 속성을 통해 앱에서 띄운 웹뷰로 인지 할 수 있도록 합니다.
        settings.defaultTextEncodingName = "UTF-8" // 인코딩 설정
        settings.databaseEnabled = true //Database Storage API 사용 여부 설정
    }
    webView.loadUrl("https://www.naver.co.kr/") // url 주소 가져오기
}

 

@SuppressLint("SetJavaScriptEnabled")
private fun initViews(){
    webView.apply {
        webViewClient = WebViewClient()
        webChromeClient = webChromeClient()
        settings.javaScriptEnabled = true // 자바 스크립트도 사용하겠다는 명시도 해야
        loadUrl("${DEFAULT_URL}")
    }
}
// 아래식을 위처럼 apply형태로 변경 가능하다.
//    webView.webViewClient = WebViewClient()
//    webView.settings.javaScriptEnabled = true // 자바 스크립트도 사용하겠다는 명시도 해야
//    webView.loadUrl("Https://google.com")

웹뷰 초기 세팅 initViews()를 구현 해준다.

companion object{
    private const val DEFAULT_URL = "http://www.google.com"
}

Default URL을 companion object로 구현 해준다.

 

private fun bindViews(){
    addressBar.setOnEditorActionListener { v, i, _ ->
        if(i == EditorInfo.IME_ACTION_DONE){
            val loadingUrl = v.text.toString()
            if(URLUtil.isNetworkUrl(loadingUrl)){
                webView.loadUrl(loadingUrl)
            }
            else {
                webView.loadUrl("http://$loadingUrl")
            }
        }

        return@setOnEditorActionListener false
    }

    goBackButton.setOnClickListener {
        webView.goBack()
    }

    goFowardButton.setOnClickListener {
        webView.goForward()
    }

    goHomeButton.setOnClickListener {
        webView.loadUrl("${DEFAULT_URL}")
    }

    refreshLayOut.setOnRefreshListener {
        webView.reload()
    } // 밑으로 당기면 reflesh 새로고침이 된다.
}

상단 검색어 입력창에 주소를 입력하면 그 해당 주소로 이동 할 수 있는 bindView()를 구현해준다.

Edittext 입력완료 후 엔터(Enter) 이벤트 처리를 위해 setOnEditorActionListener 사용해준다.

IME.ACTION_DONE => Enter클릭시 를 뜻한다.

 


ProgressBar 설정

inner class WebViewClient : android.webkit.WebViewClient(){ // inner를 붙여줌으로써 상위 클래스에 접근 가능

    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
        super.onPageStarted(view, url, favicon)

        progressBar.show()
    }

    override fun onPageFinished(view: WebView?, url: String?) { // Page가 전부 로딩 됐을 때 !
        super.onPageFinished(view, url)

        refreshLayOut.isRefreshing = false
        progressBar.hide()
        goBackButton.isEnabled = webView.canGoBack() // 백 이있을때만 백버튼 누를 수 있다.
        goFowardButton.isEnabled = webView.canGoForward() // forward가 있을 때만 누를 수 있다.

        addressBar.setText(url) // 페이지 이동시 m.naver.com 같은 url을 보여 주고 싶을 때
    }
}
inner class webChromeClient : android.webkit.WebChromeClient(){
    override fun onProgressChanged(view: WebView?, newProgress: Int) {
        super.onProgressChanged(view, newProgress)

        progressBar.progress = newProgress
    }
}

안드로이드 webview 페이지 로딩 확인

(webview onProgressChanged함수)

 

-> 안드로이드의 페이지 로딩의 되었는지  0 - 100으로 나타내는 함수.




( 정리 최종코드)

package com.example.aop_part2_chaptor08

import android.annotation.SuppressLint
import android.graphics.Bitmap
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.inputmethod.EditorInfo
import android.webkit.URLUtil
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import androidx.core.widget.ContentLoadingProgressBar
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout

class MainActivity : AppCompatActivity() {

    private val progressBar : ContentLoadingProgressBar by lazy {
        findViewById(R.id.progressBar)
    }

    private val refreshLayOut : SwipeRefreshLayout by lazy {
        findViewById(R.id.refreshLayout)
    }

    private val goBackButton : ImageButton by lazy {
        findViewById(R.id.goBackButton)
    }

    private val goHomeButton : ImageButton by lazy{
        findViewById(R.id.goHomeButton)
    }

    private val goFowardButton : ImageButton by lazy{
        findViewById(R.id.goFowardButton)
    }

    private val addressBar : EditText by lazy {
        findViewById(R.id.addressBar)
    }

    private val webView : WebView by lazy {
        findViewById(R.id.webView)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initViews()
        bindViews()
    }

    override fun onBackPressed() {
        if(webView.canGoBack()){ // 뒤로 갈 수 있다면
            webView.goBack()
        }
        else {
            super.onBackPressed() // 홈 이면 그냥 꺼버림 !!
        }
    }

    @SuppressLint("SetJavaScriptEnabled")
    private fun initViews(){
        webView.apply {
            webViewClient = WebViewClient()
            webChromeClient = webChromeClient()
            settings.javaScriptEnabled = true // 자바 스크립트도 사용하겠다는 명시도 해야
            loadUrl("${DEFAULT_URL}")
        }
    }
    // 아래식을 위처럼 apply형태로 변경 가능하다.
    //    webView.webViewClient = WebViewClient()
    //    webView.settings.javaScriptEnabled = true // 자바 스크립트도 사용하겠다는 명시도 해야
    //    webView.loadUrl("Https://google.com")

    private fun bindViews(){
        addressBar.setOnEditorActionListener { v, i, _ ->
            if(i == EditorInfo.IME_ACTION_DONE){
                val loadingUrl = v.text.toString()
                if(URLUtil.isNetworkUrl(loadingUrl)){
                    webView.loadUrl(loadingUrl)
                }
                else {
                    webView.loadUrl("http://$loadingUrl")
                }
            }

            return@setOnEditorActionListener false
        }

        goBackButton.setOnClickListener {
            webView.goBack()
        }

        goFowardButton.setOnClickListener {
            webView.goForward()
        }

        goHomeButton.setOnClickListener {
            webView.loadUrl("${DEFAULT_URL}")
        }

        refreshLayOut.setOnRefreshListener {
            webView.reload()
        } // 밑으로 당기면 reflesh 새로고침이 된다.
    }

    inner class WebViewClient : android.webkit.WebViewClient(){ // inner를 붙여줌으로써 상위 클래스에 접근 가능

        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            super.onPageStarted(view, url, favicon)

            progressBar.show()
        }

        override fun onPageFinished(view: WebView?, url: String?) { // Page가 전부 로딩 됐을 때 !
            super.onPageFinished(view, url)

            refreshLayOut.isRefreshing = false
            progressBar.hide()
            goBackButton.isEnabled = webView.canGoBack() // 백 이있을때만 백버튼 누를 수 있다.
            goFowardButton.isEnabled = webView.canGoForward() // forward가 있을 때만 누를 수 있다.

            addressBar.setText(url) // 페이지 이동시 m.naver.com 같은 url을 보여 주고 싶을 때
        }
    }


    inner class webChromeClient : android.webkit.WebChromeClient(){
        override fun onProgressChanged(view: WebView?, newProgress: Int) {
            super.onProgressChanged(view, newProgress)

            progressBar.progress = newProgress
        }
    }

    companion object{
        private const val DEFAULT_URL = "http://www.google.com"
    }
}

MainAcitiviy.kt

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/white"
        android:elevation="4dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageButton
            android:id="@+id/goHomeButton"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="?attr/selectableItemBackground"
            android:src="@drawable/ic_home"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintDimensionRatio="1:1"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="ContentDescription" />
        <!--리플 버튼은 background를 수정하여 변경가능 누르면 번쩍이는 기-->

        <EditText
            android:id="@+id/addressBar"
            android:layout_width="0dp"
            android:layout_height="32dp"
            android:background="@drawable/shape_address_bar"
            android:imeOptions="actionDone"
            android:importantForAutofill="no"
            android:inputType="textUri"
            android:paddingHorizontal="16dp"
            android:selectAllOnFocus="true"
            android:textSize="14sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toRightOf="@id/goHomeButton"
            app:layout_constraintRight_toLeftOf="@id/goBackButton"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="LabelFor,TextFields" />
        <!--인풋타입은 무슨 키보드 형식을 주는지 !!-->

        <ImageButton
            android:id="@+id/goBackButton"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="?attr/selectableItemBackground"
            android:src="@drawable/ic_back"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintDimensionRatio="1:1"
            app:layout_constraintRight_toLeftOf="@id/goFowardButton"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="ContentDescription" />


        <ImageButton
            android:id="@+id/goFowardButton"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="?attr/selectableItemBackground"
            android:src="@drawable/ic_forward"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintDimensionRatio="1:1"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="ContentDescription" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/refreshLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar">

        <WebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    <androidx.core.widget.ContentLoadingProgressBar
        android:id="@+id/progressBar"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="0dp"
        android:layout_height="2dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />

</androidx.constraintlayout.widget.ConstraintLayout>

activity_main.xml