본문 바로가기

책/오준석의 안드로이드 생존코딩 코틀린편

7. 나만의 웹 브라우저

프로젝트명 MyWebBrowser
기능
  • 웹 페이지를 탐색한다.
  • 홈 메뉴를 클릭하여 첫 페이지로 온다.
  • 메뉴에는 검색 사이트와 개발자 정보가 표시된다.
  • 페이지를 문자나 메일로 공유할 수 있다.
핵심 구성요소
  • WebView : 웹 페이지를 표시하는 뷰
  • 옵션 메뉴 : 상단 툴바에 표시하는 메뉴
  • 컨텍스트 메뉴 : 뷰를 롱클릭하면 표시되는 메뉴
  • 암시적 인텐트 : 문자 보내기, 이메일로 보내기와 같이 미리 정의된 인텐트
라이브러리 설정
  • Anko : 인텐트, 다이얼로그, 로그 등을 구현하는데 도움이 되는 라이브러리
  1. 준비하기 : 프로젝트 생성 및 안드로이드 설정
  2. 스텝1 : 화면 작성
  3. 스텝2 : 기본 웹 브라우저 기능 작성
  4. 스텝3 : 옵션 메뉴 사용하기
  5. 스텝4 : 컨텍스트 메뉴 사용하기
  6. 스텝5 : 암시적 인텐트

준비하기

./app/build.gradle 파일에 anko 의존성을 추가한다.

dependencies {
....
    implementation 'org.jetbrains.anko:anko:0.10.5'
....
}

스탭1 화면 작성

테마 수정

책의 내용과는 달리 android studio 4.0 에서는 Theme Editor는 따로 화면에 보이지 않았다. 대신 찾아낸 방법은 다음과 같다.

  1. styles.xml 파일을 연다.
  2. 5번 라인에서 마우스 오른쪽 버튼을 클릭한다. 
  3. 새로 띄워진 팝업에서 Browse를 클릭한다. 
  4. app 탭에 colorAccent를 클릭한다
  5. ok를 누른다.

 

전체 동작 알아보기

  1. URL 주소 입력
  2. 검색버튼을 클릭하면 웹페이지 표시
  3. 홈 아이콘을 클릭하면 미리 지정한 홈페이지 표시
  4. 옵션메뉴는 하위메뉴로 검색사이트, 개발자정보로 구성
  5. 개발자 정보에서는 전화걸기, 문자보내기 등을 할 수 있음
  6. 표시된 웹 페이지를 길게 클릭하면 컨텐스트 메뉴 표시

검색창 EditText의 배치

배치 Autoconnect 모드로 Plain Text를 레이아웃의 상단 중앙에 배치
ID urlEditText
layout_width match_constraint
좌,우,상단 여백 제약 8
inputType textUri
hint http://
text 빈공백
imeOption actionSearch 선택 ( 해당선택으로 키보드의 Enter 키의 아이콘이 돋보기로 변경)

 

WebView의 배치

배치 Autoconnect 모드 끔. WebView를 레이아웃의 중앙에 배치
ID webView
layout_width match_constraint
layout_height match_constraint
layout_constraintTop_toBottomOf @+id/urlEditText
좌,우,상,하 여백 제약 0

스텝2 기본 웹 브라우저 기능작성

인터넷 권한 설정

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="abstractask.example.mywebbrowser">

    <uses-permission android:name="android.permission.INTERNET" />
    
    <application
        ..... >
</manifest>

웹뷰에 웹 페이지 표시하기

  1. javaScriptEnabled 기능을 켠다.
  2. 앱의 자체 브라우저가 동작하지 않도록 webViewClient 클래스를 지정한다.
  3. loadUrl메서드를 이용 google.com 페이지가 로딩되도록 한다.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //웹뷰 기본 설정
        webView.apply {
            settings.javaScriptEnabled = true
            webViewClient = WebViewClient()
        }
        webView.loadUrl("https://www.google.com")
    }
}​

키보드의 검색 버튼 동작 정의하기

  1. 글자기 입력될 때마다 호출되는데, 매개변수로는 반응한 뷰, 액션ID, 이벤트 세가지 이고 액션ID만 사용하므로 나머지는 _ 로 대치함
  2. actionId의 값이 검색 버튼인지 확인
  3. 검색창에 입력한 주소를 웹뷰에 전달하여 로딩하고 true를 리턴하도록 한다.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //웹뷰 기본 설정
        ....
        
        //키보드의 검색 버튼 동작 정의하기
        urlEditText.setOnEditorActionListener { _, actionId, _ ->  // 1
            if ( actionId == EditorInfo.IME_ACTION_SEARCH) {       // 2
                webView.loadUrl(urlEditText.text.toString())       // 3
                true
            } else {
                false
            }
        }       
    }
}

뒤로가기 동작 재정의 

  1. 웹뷰가 이전으로 갈수 있으면
  2. 웹뷰의 이전 페이지로 이동
  3. 아니면 원래 동작을 수행
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //웹뷰 기본 설정
        ....
        //키보드의 검색 버튼 동작 정의하기
        ....   
    }
    // 뒤로가기 동작 재정의
    override fun onBackPressed() {
        if( webView.canGoBack()) {     // 1
            webView.goBack()           // 2
        } else {
            super.onBackPressed()      // 3
        }
    }
}

스텝3 옵션 메뉴 사용하기

메뉴 리소스 파일 생성 및 벡터 이미지 준비

File -> New -> Android Resource Directory 선택

menu 디렉터리를 선택한 상태에서 File -> New -> Menu Resource File 선택

res 폴더를 선택후 File -> New -> Vector Asset을 선택

Clip Art를 선택 home 을 검색후 선택

메뉴 작성

main.xml 파일을 클릭 하면 메뉴 에디터가 표시되는데 팔레트 창에서 Menu Item을 선택하여 컴포넌트 트리 창 Menu 아래에 드레그 하여 배치한다.

위의 방법을 반복하여 다음과 같이 설정한다. 

menu(title) menu item(title) 속성명 속성값
검색 사이트 네이버 id action_naver
구글 id action_google
다음 id action_daum
개발자 정보 전화하기 id action_call
문자보내기 id action_sen_text
이메일 보내기 id action_email
Home id action_home
icon ic_baseline_home_24
showAsAction ifRoom

참고:
  • never : 밖으로 절대 노출시키지 않습니다.
  • ifRoom : 툴바에 여유가 았으면 노출합니다.
  • always : 항상 노출합니다.
  • withText : 글자와 아이콘을 함께 표시합니다.
  • collapseActionView : 액션 뷰와 결합하면 축소되는 메뉴를 만들 수 있습니다.

옵션 메뉴를 액티비티에 표시하기

  1. menuInflater 객체의 inflate() 메서드를 사용하여 메뉴 리소스를 지정한다.
  2. true를 반환하면 액티비티에 메뉴가 있다고 인식한다.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //웹뷰 기본 설정
        ....
        //키보드의 검색 버튼 동작 정의하기
        ....   
    }
    // 뒤로가기 동작 재정의
    ....
    
    // 옵션 메뉴를 액티비티에 표시하기
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.main, menu)
        return true
    }
}

옵션 메뉴 클릭 이벤트 처리

R.id.action_call 분기에서는 연락처를 클릭하면 암시적 인텐트를 통해 전화 앱을 열고 전화를 걸어주게 된다. 

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

        //웹뷰 기본 설정
        ....
        //키보드의 검색 버튼 동작 정의하기
        ....   
    }
    // 뒤로가기 동작 재정의
    ....
    
    // 옵션 메뉴를 액티비티에 표시하기
    ....
    
    // 옵션 메뉴 클릭 이벤트 처리
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item?.itemId) {
            R.id.action_google, R.id.action_home -> {
                webView.loadUrl("http://www.google.com")
                return true
            }
            R.id.action_naver -> {
                webView.loadUrl("http://www.naver.com")
                return true
            }
            R.id.action_call -> {
                var intent = Intent(Intent.ACTION_DIAL)
                intent.data = Uri.parse("tel:031-123-4567")
                if(intent.resolveActivity(packageManager) != null) {
                    startActivity(intent)
                }
                return true
            }
            R.id.action_send_text -> {
                // 문자 보내기
                return true
            }
            R.id.action_email -> {
                // 이메일 보내기
                return true
            }
        }

        return super.onOptionsItemSelected(item)
    }
}

스텝4 컨텍스트 메뉴 사용하기

메뉴 리소스 파일 생성

menu폴더를 선택한 상태에서 File -> New -> Menu Resource File을 선택하고 다음 그림과 같이 작성하고 OK를 클릭한다.

메뉴 작성

menu(title) 속성 속성값
페이지 공유 id action_share
기본 브라우저에서 열기 id action_browser

컨텍스트 메뉴 등록 및 클릭 이벤트 처리

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

        //웹뷰 기본 설정
        ....
        //키보드의 검색 버튼 동작 정의하기
        ....   
        
        //컨텍스트의 메뉴 등록
        registerForContextMenu(webView)
    }
    // 뒤로가기 동작 재정의
    ....
    
    // 옵션 메뉴를 액티비티에 표시하기
    ....
    
    // 옵션 메뉴 클릭 이벤트 처리
    ....
    
    // 컨텍스트 메뉴 등록하기
    override fun onCreateContextMenu(
        menu: ContextMenu?,
        v: View?,
        menuInfo: ContextMenu.ContextMenuInfo?
    ) {
        super.onCreateContextMenu(menu, v, menuInfo)
        menuInflater.inflate(R.menu.context, menu)
    }
    
    // 컨텍스트 메뉴 클릭 이벤트 처리
    override fun onContextItemSelected(item: MenuItem): Boolean {
        when( item?.itemId ){
            R.id.action_share -> {
                // 페이지 공유
                return true
            }
            R.id.action_browser -> {
                // 기본 웹 브라우저에서 열기
                return true
            }
        }
        return super.onContextItemSelected(item)
    }    
}

스텝5 암시적 인텐트

암시적 인텐트의 종류

암시적 인텐트 출처: https://developer.android.com/guide/components/intents-common 

 

공통 인텐트  |  Android 개발자  |  Android Developers

An intent allows you to start an activity in another app by describing a simple action you'd like to perform (such as "view a map" or "take a picture") in an Intent object. This type of intent is called an implicit intent because…

developer.android.com

종류 코드
전화걸기
var intent = Intent(Intent.ACTION_DIAL) 
intent.data = Uri.parse("tel:031-123-4567") 
if(intent.resolveActivity(packageManager) != null) { 
    startActivity(intent) 
}
문자 보내기
var intent = Intent(Intent.ACTION_SEND) 
intent.apply{
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "보낼 문자열")
    var chooser = Intent.createChooser(intent, null)
    if( intent. resolveActivity(packageManager) != null ) {
        startActivity(intent)
    }
}
웹 브라우저 띄우기
var intent = Intent(Intent.ACTION_DIAL) 
intent.data = Uri.parse("http://www.example.com") 
if(intent.resolveActivity(packageManager) != null) { 
    startActivity(intent) 
}

Anko를 활용한 암시적 인텐트

  • Anko라이브러리를 이용하면 한 줄로 암시적 인텐트 사용 코드를 줄일 수 있다.
  • Anko에서 지원하는 암시적 인텐트, []는 옵션
종류 코드
전화걸기 makeCall( 전화번호 )
문자 보내기 sendSms( 전화번호 [, 문자열] )
웹 브라우저에서 열기 browse( url )
문자열 공유 share( 문자열 [, 제목] )
이메일 보내기 email ( 받는 이메일주소 [, 제목] [, 내용] )

메뉴와 암시적 인텐트 연동

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

        //웹뷰 기본 설정
        ....
        //키보드의 검색 버튼 동작 정의하기
        ....   
        //컨텍스트의 메뉴 등록
        ....
    }
    // 뒤로가기 동작 재정의
    ....
    
    // 옵션 메뉴를 액티비티에 표시하기
    ....
    
    // 옵션 메뉴 클릭 이벤트 처리
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item?.itemId) {
            R.id.action_google, R.id.action_home -> {
                webView.loadUrl("http://www.google.com")
                return true
            }
            R.id.action_naver -> {
                webView.loadUrl("http://www.naver.com")
                return true
            }
            R.id.action_call -> {
                makeCall("031-123-4567")
                return true
            }
            R.id.action_send_text -> {
                // 문자 보내기
                sendSMS("031-123-4567", webView.url)
                return true
            }
            R.id.action_email -> {
                // 이메일 보내기
                email("test@example.com","좋은 사이트", webView.url)
                return true
            }
        }

        return super.onOptionsItemSelected(item)
    }
    
    // 컨텍스트 메뉴 등록하기
    ....
    
    // 컨텍스트 메뉴 클릭 이벤트 처리
    override fun onContextItemSelected(item: MenuItem): Boolean {
        when( item?.itemId ){
            R.id.action_share -> {
                // 페이지 공유
                share(webView.url)
                return true
            }
            R.id.action_browser -> {
                // 기본 웹 브라우저에서 열기
                browse(webView.url)
                return true
            }
        }
        return super.onContextItemSelected(item)
    }
}

' > 오준석의 안드로이드 생존코딩 코틀린편' 카테고리의 다른 글

10. 지도와 GPS  (0) 2020.06.21
8. 수평 측정기  (0) 2020.06.16
6. 스톱워치  (0) 2020.06.11
5. 비만도 계산기  (0) 2020.06.09
4. 코틀린  (0) 2020.06.08