Android strings.xml에 HTML 코드 넣기

의료기 개발을 하면서 측정 결과를 공유하기 위해 

XML 형태로 전송하거나 

HTML 형태로 전송하거나 

혹은 PDF로 전송하는 등 다양한 형태의 파일로 

공유하는 작업을 하는 중에 

HTML형태의 파일로 저장하는 작업을 하면서 소스 코드 상에서 복잡한 HTML 코드를 집어 넣기에는 뭐시기해서 strings.xml을 이용하기 했다.

근데 문제는...


Android의 strings.xml에 HTML 코드를 그대로 넣으면 컴파일 단계에서 에러가 발생한다.


<string name="HtmlCSS">

<!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>Meditab Results</title>

<style type="text/css">

body {

font-family: Arial, Verdana, sans-serif;

font-size: 90%;

color: #666;

background-color: #f8f8f8;}

table {

border-spacing: 0px; }

... 이하 생략 ...


이 문제를 해결할려면 다음과 같이 바꿔 주어야 된다.


   &lt;!DOCTYPE html&gt;

&lt;html&gt;

&lt;head&gt;

      &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;

...이하 생략...


보통 번거로운일이 아니다. ;;;

이런 문제를 한방에 해결할수 있는 방법이 있다. 다음과 같이


<![CDATA[

  이 안에 HTML 코드를 있는 그대로 넣어주면 해결된다.

]]>


이때 string 항목에 속성을 하나 추가해 주어야 하는데 formatted="false"이다.

아래는 전체 예제이다.


<string name="HtmlCSS" formatted="false">

   <![CDATA[

   <!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>Meditab Results</title>

<style type="text/css">

body {

font-family: Arial, Verdana, sans-serif;

font-size: 90%;

color: #666;

background-color: #f8f8f8;}

table {

border-spacing: 0px; }

th, td {

padding: 5px 30px 5px 10px;

border-spacing: 0px;

font-size: 90%;

margin: 0px;

}

th, td {

text-align: left;

background-color: #e0e9f0;

border-top: 1px solid #f1f8fe;

border-bottom: 1px solid #cbd2d8;

border-right: 1px solid #cbd2d8;}

tr.head th {

color: #fff;

background-color: #90b4d6;

border-bottom: 2px solid #547ca0;

border-right: 1px solid #749abe;

border-top: 1px solid #90b4d6;

text-align: center;

text-shadow: -1px -1px 1px #666666;

letter-spacing: 0.15em;}

td {

text-shadow: 1px 1px 1px #ffffff;

}

tr.even td, tr.even th {

background-color: #e8eff5;}

tr.head th:first-child {

-webkit-border-top-left-radius: 8px;

-moz-border-radius-topleft: 8px;

border-top-left-radius: 8px;}

tr.head th:last-child {

-webkit-border-top-right-radius: 8px;

-moz-border-radius-topright: 8px;

border-top-right-radius: 8px;}

fieldset {

width: 310px;

margin-top: 20px;

border: 1px solid #d6d6d6;

background-color: #ffffff;

line-height: 1.6em;}

legend {

font-style: italic;

color: #666666;}

.title {

float: left;

width: 160px;

clear: left;}

.submit {

width: 310px;

text-align: right;}

.results {

float: left;

}

</style>

</head>

<body>

<h1>Meditab Test Results</h1>

<h2>&nbsp;</h2>

<h2>개인정보 </h2>

<table>

<tr class="head">

<th>이름</th>

<th>측정일시</th>

<th>생년월일</th>

<th>성별</th>

<th>E-mail</th>

<th>진료담당</th>

</tr>

<tr> 

<th>홍길동</th>

<td>2016. 3. 5</td>

<td>1978. 5. 17</td>

<td>남</td>

<td>aslsd@naver.com</td>

<td>Dr. Park</td>

</tr>

</table>

<p />

<h2>&nbsp;</h2>

<h2>측정결과</h2>

<div >

<table class="results"> 

<tr class="head">

<th>ECG(심전도)</th>

<th>측정결과</th>

</tr>

<tr>

<td>Heart Rate</td>

<td>84</td>

</tr>

<tr>

<td>Respiratory Rate</td>

<td>19</td>

</tr>

<tr>

<td>ST Level(mV)</td>

<td>18</td>

</tr>

<tr>

<td>Arrythmia</td>

<td>Normal</td>

</tr>

</table>

<p />

<table class="results differ" > 

<tr class="head">

<th>NIBP(혈압)</th>

<th>측정결과</th>

</tr>

<tr>

<td>Systolic</td>

<td>120</td>

</tr>

<tr>

<td>Diastolic</td>

<td>84</td>

</tr>

<tr>

<td>Mean</td>

<td>96</td>

</tr>

<tr>

<td>&nbsp;</td>

<td>&nbsp;</td>

</tr>

</table>

<p />

<table class="results"> <!-- SPO2 -->

<tr class="head">

<th>SPO2(혈중 산소포화도)</th>

<th>측정결과</th>

</tr>

<tr>

<td>Saturation Value</td>

<td>100</td>

</tr>

<tr>

<td>Pulse Rate</td>

<td>84</td>

</tr>

<tr>

<td>&nbsp;</td>

<td>&nbsp;</td>

</tr>

<tr>

<td>&nbsp;</td>

<td>&nbsp;</td>

</tr>

</table>

<p />

<table class="results differ"> <!-- Temp -->

<tr class="head">

<th>Temp(체온)</th>

<th>측정결과</th>

</tr>

<tr>

<td>Temperature</td>

<td>36.3</td>


</tr>

<tr>

<td>&nbsp;</td>

<td>&nbsp;</td>

</tr>

<tr>

<td>&nbsp;</td>

<td>&nbsp;</td>

</tr>

<tr>

<td>&nbsp;</td>

<td>&nbsp;</td>

</tr>

</table>

</div>

<p/>

</body>

</html>

   ]]>

</string>





구글이 제공하는 안드로이드의 

Material design incon들이 

나름 산뜻한 느낌을 주는 디자인들이다.

이런 아이콘들을 앱에 사용하고자 한다면 

다음 사이트에서 해당 아이콘들을 각각 다운로드 받을수 있다.


https://material.io/tools/icons/?style=baseline



이 중에서 특정 아이콘을 다운로드 받고자한다면 해당 아이콘을 클릭하면 다음과 같은 다운로드 선택 창이 하단에 보이게 된다.




위의 이미지에서 파란색 띠가 보이고 원하는 것을 선택하면 된다. 

선택할 수 있는 내용은 


 -. 이미지 크기(위의 경우는 23dp. 18dp, 24dp, 36dp, 48dp의 4종류가 있다)

 -. 이미 색상(black, white)

 -. 파일의 종류(svg, png형태)


예를들어서 Dark Action Bar의 경우는 balck 아이콘을 사용하면 아이콘이 보이지 않을 것이다. 따라서 이럴 경우는 white를 체크해서 다운 받으면 된다.

다운 받는 이미지 파일의 형태도 svg와 png 형태를 선택할 수 있다.







현재의 디바이스가 BLE(Bluetooth Low Energy)를 지원하는지 확인하는 법


일단은 Manifest파일에 아래의 permission을 추가해야한다.


    <uses-permission android:name="android.permission.BLUETOOTH"/>


    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>


    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>


BLE가 지원되는 여부를 파악하기 위해서는 PackageManager의 도움을 받으면 된다.

PackageManager의 객체는 Context 클래스에 있는 아래 메소드로부터 객체를 획득할수 있다.


public abstract PackageManager getPackageManager ()


그리고 PackageManager 클래스에 있는 아래 메소드를 통해 BLE 지원 여부를 확인할 수 있다.


public abstract boolean hasSystemFeature (String name)


아래는 코드 조각이다.


        PackageManager pkgMan = getPackageManager();

        if (pkgMan.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {

            Toast.makeText(this, "BLE가 지원되는 디바이스 입니다.", 1).show();

        } else {

            Toast.makeText(this, "BLE가 지원되지 않습니다.", 1).show();

        }






타이틀바 없애고 전체화면, 화면 가로로(혹은 세로로) 고정하는 법


안드로이드 앱 실행시 전체화면으로 보이게 하고, 타이틀 바 없애고 화면을 가로로 고정할려면

소스 코드 상에서도 처리할 수 있으나 아래와 같이 Manifest 파일에서 설정할 수 있다.

소스 코드 상에서는 실행 시점에 처리한다면 Manifest 파일에서 설정하면 컴파일 단계에서 미리 처리를 한다.


AndroidManifest.xml에 다음을 추가


        <activity

            android:name=".TestActivity"

            android:label="@string/app_name"

            android:configChanges="orientation|keyboardHidden"

            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"

            android:screenOrientation="landscape" >


화면을 전체 화면과 타이틀 바를 없애는 기능이

android:theme="@android:style/Theme.NoTitleBar.Fullscreen"

이고


화면을 가로고 고정하는 방법이 

android:screenOrientation="landscape"

이다.

만일 화면을 세로로 고정하고자 한다면 

android:screenOrientation="portrait"

이다.


혹은 앱만이 아닌 화면 자체를 세로로 뒤집을려면

android:screenOrientation="reversePortrait"

과같이 하면 된다.







callback 메소드가 있는 클래스의 객체 선언 법


abstract 클래스도 아니고 interface도 아닌 

일반 클래스임에도 콜백 메소드(시스템에 의해 자동 호출되는 메소드)가 있을 경우 abstract 클래스나 interface와 유사한 방식으로 객체를 생성을 할 수 있다. 

아래 두 경우를 보면 모두 일반 클래스이다.


public class Handler extends Object

public class PhoneStateListener extends Object


그런데 이들 클래스 안에 콜백 메서드를 사용하기 위해 다음과 같이 mHandler라는 객체를 다음과 같이 생성한다.


 Handler mHandler = new Handler(){ 

     @Override

     public void handleMessage(Message msg){ //이게 콜백 메소드

         //여기서 원하는 기능 수행

     }

  };


위의 handleMessage() 메소드는 핸들러로 메시가 들어올때 즉 이 핸들러 호출이 있을 때 시스템에 의해 이 메소드 호출된다.



PhoneStateListener mPhoneState = new PhoneStateListener(){

    public void onCallStateChanged(int state, String incomingNumber){ //이게 콜백 메소드이다.

    switch(state){

    case TelephonyManager.CALL_STATE_IDLE :

    txt.append("\n☆ 전화 상태 : 대기 상태");

    break;

    case TelephonyManager.CALL_STATE_OFFHOOK :

    txt.append("\n☆ 전화 상태 : 통화 중");

    break;

    case TelephonyManager.CALL_STATE_RINGING :

    txt.append("\n☆ 전화가 왔습니다 : " + incomingNumber);

    break;

    }

    }

    };





한 프로그램에 여러 Activity가 있을 때 처음 실행되는 Activity 지정하는 법


보통 한 프로그램 안에 여러 개의 Activity가 돌아간다. 

이때 어느 것을 Main으로 설정하는지 다음과 같이 Menifest 파일에 등록해 주면 된다.


아래 적색 부분을 포함하고 있는 Activity가 프로그램 실행 시 화면에 맨 처음 뜨게 된다.

여기서 AndExam.java가 Main으로 등록되어 있다.



        <activity android:name=".AndExam"

                  android:label="@string/app_name">

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>





안드로이드 6.0에서 Apche의 HttpClient가 import안되는 문제 해결


안드로이드가 6.0으로 업데이트 되면서 기존에 안드로이드와 서버와의 통신, 데이터 송, 수신에

사용되던 Apache의 HttpClient를 구글이 원천적으로 사용을 못하게 해 놨다. 짜증~


아래 클래스들이 서버로의 송, 수신시 필요로 하는 것들인데 원천적으로 import가 안된다.

org.apache.http.HttpEntity;

org.apache.http.HttpResponse;

org.apache.http.client.ClientProtocolException;

org.apache.http.client.HttpClient;

org.apache.http.client.methods.HttpPost;

org.apache.http.entity.StringEntity;

org.apache.http.impl.client.DefaultHttpClient;

org.apache.http.util.EntityUtils;


https://developer.android.com/intl/ko/about/versions/marshmallow/android-6.0-changes.html#behavior-apache-http-client


따라서 HttpClient를 사용해야할 상황에서는 좀 난감해 진다.

구글 API Reference상에서 설명조차 제공이 안된다.

안드로이드 스튜디오를 사용하는 경우라면 해당 해법들이 검색하면 제법 나오는데

이클립스 상황에서는 땀난다...


해법은 다음 3개의 jar 파일을 libs폴더에 복사해 두면 이제부터 Ctrl-Shift-O로 정상적으로 import가 된다.


httpclient-4.4.1.jar

commons-logging-1.2.jar

httpcore-4.4.1.jar


혹은 구글 API Reference 문서에서 추천하는 방식인 HttpURLConnection을 사용하는 방식도 있겠다.





안드로이드에서 외부 Font를 사용할려면 assets/fonts라는 폴더를 만들어서 여기에 글꼴을 복사해 놓아야 가능하다.

TextView 3개를 만들어 여기에 각각 다른 글꼴을 적용할려면 Typeface(android.graphics.Typeface) 클래스를 이용하면 간단히 구현된다.


Typeface 클래스의 객체를 구하는 방식은 아래의 static 메소드들 중 하나의 방식으로 처리된다.


static Typefacecreate(String familyName, int style)
Create a typeface object given a family name, and option style information.
static Typefacecreate(Typeface family, int style)
Create a typeface object that best matches the specified existing typeface and the specified Style.
static TypefacecreateFromAsset(AssetManager mgr, String path)
Create a new typeface from the specified font data.
static TypefacecreateFromFile(String path)
Create a new typeface from the specified font file.
static TypefacecreateFromFile(File path)
Create a new typeface from the specified font file.
static TypefacedefaultFromStyle(int style)
Returns one of the default typeface objects, based on the specified style


        TextView txt = (TextView)findViewById(R.id.myFont);

        Typeface face = Typeface.createFromAsset(getAssets(), "fonts/arial.ttf");

        txt.setTypeface(face);

        

        TextView txt2 = (TextView)findViewById(R.id.myFont2);

        Typeface face2 = Typeface.createFromAsset(getAssets(), "fonts/FORTE.TTF");

        txt2.setTypeface(face2);

        

        TextView txt3 = (TextView)findViewById(R.id.fontKOR);

        Typeface face3 = Typeface.createFromAsset(getAssets(), "fonts/HYPORM.TTF");

        txt3.setTypeface(face3);

        

Typeface를 이용한 글꼴 적용은 View의 paint에서도 사용이 가능하다.




FragmentTransaction의 replace() 메소드를 통해 동적으로 Fragment 교체하기

교체하는 코드는 다음과 같다.


FragmentManager fragmentManager =
                                       getFragmentManager();
FragmentTransaction fragTransaction = 

                          fragManager.beginTransaction();
MyFragment mFrag = new MyFragment();

fragTransaction.add(R.id.layout, mFrag);

//아래 코드 실행되는 시점에 

//MyFragment가 비로소 실행된다.

fragTransaction.commit(); 



☞ FragmentManager

⇒ Activity안에 있는 Fragment와 상호 작용 및 관리(Activity에 추가, 교체...)를 위한 클래스

이 클래스의 객체는 Activity의 메소드 중 getFragmentManager()를 통해 얻을 수 있다.


☞ fragTransaction.add(R.id.layout, mFrag);

⇒ 새로운 Fragment인 mFrag를 R.id.layout이라는 곳에 추가하는 기능.

R.id.layout이 들어있는 xml파일이 다음과 같다면


<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"

    android:id="@+id/layout"

    android:orientation="vertical" >


    <Button

        android:id="@+id/btn"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:textSize="30sp"

        android:text="확 인"

        android:onClick="mClick"

        />

</LinearLayout>



fragTransaction.add(R.id.layout, mFrag)과 유사한 것이 replace() 메소드이다.

아래 코드는 R.id.content_frame에 있는 기존의 Fragment를 제거한 후 

두 번째 매개인자인 fragment를 R.id.content_frame에 집어 넣는 기능이다.


fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();




Fragment를 사용하기 위해서 개념을 정리해 보았다.


Fragment를 사용하는 건 사용자에겐 편리할지 모르나 개발자에겐 좀 복잡한 작업이다.
특히 Fragment와 ViewPager와 ActionBar와
DrawerLayout이 함께 엮이어 돌아갈 경우는 더더욱 머리가 복잡해 진다. 따라서 전체적인 개념이 정리가 필요하다. 

본 포스트는 Fragment를 기본적으로 사용해 본 경험이 있는 분들이라야 의미가 있으리라고 생각된다.

아래는 Fragment의 동작 메커니즘을 정리해 본 내용이다.


-. Activity가 Fragment를 담기 위해서는 일반 Activity로는 안되고 android.support.v4.app.FragmentActivity를 extends해야한다.

   public class ExViewPagerActivity extends FragmentActivity { ... }


-. 여기서 Fragment간의 이동을 부드럽게 되도록 해 주는 용도로 ViewPager를 채용하면된다.
   즉 FragmentActivity에 ViewPager를 담으면 된다.

   ViewPager는 Layout manager that allows the user to flip left and right through pages of data의
   역할을 한다.


-. ViewPager에 담을 데이터와 ViewPager에 보여줄 View를 생성하기 위해서는 adapter 클래스가 있어야 한다.
   마치 ListView에 표시할 데이터와 표현하는 방식(View)를 제공하는 역할의 adapter를 필요로 하는 것과 같다. 


-. ViewPager가 사용할 adapter 클래스는 두 가지 종류가 있는데 FragmentPagerAdapter와
    FragmentStatePagerAdapter가 있다.


-. FragmentPagerAdapter는 표시할 page의 수가 고정되어 있고 몇개 되지 않을 때 사용하는 용도이고
   (이 adapter를 사용하면 모든 page를 메모리에 다 로드해 두고서 page 이동을 하는 방식이다)


-. FragmentStatePagerAdapter는 표시할 page의 수가 고정되어 있지않고 또 많을 경우에 사용하는
   adapter이다. 이 경우의 page 이동은 매번 현재 page를 destroy한 후 새 page를 생성하는 식이다. 


-. 이 두 adapter 클래스를 상속받은 클래스는
   public int getCount()과
   public Fragment getItem(int position)를 필요에 맞게 재정의해서 사용하면된다.


-. ViewPager를 setAdapter() 할때 getCount()와 getItem()이 차례로 호출된다.

   public void setAdapter (PagerAdapter adapter)


-. getCount()는 page의 갯수가 몇 개인지를 알려주는 기능이고 getItem()은 adapter에게 ViewPager에
   공급할 Fragment를 반환(제공)해 주는 역할을 한다.




+ Recent posts