반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

[Android] Settings - 1 -

2016. 9. 26. 04:39 | Posted by 솔웅


반응형

Settings

앱에는 사용자가 기능 선택을 할 수 있는 세팅 메뉴를 가지고 있는 경우가 많다. 예를 들어 어떤 앱은 notification 가능 여부를 사용자가 선택할 수 있도록 하기도 하고 클라우드에 동기화 되는 주가 등을 설정하도록 하기도 합니다.
이 세팅 메뉴를 넣으려면 안드로이드의 Preference API를 사용해서 만들어야 합니다.

Setting Design


세팅을 어떤 모양으로 개발하느냐 하는 것은 Settings design guide를 살펴 보세요.





Figure 1. Screenshots from the Android Messaging app's settings. Selecting an item defined by a Preference opens an interface to change the setting.


안드로이드의 메세지 앱의 세팅 화면 입니다. 아이템을 누르면 지정된 Preference 의 세팅 화면이 열립니다.



Overview



User Interface를 위해 View object를 사용하는 대신 Settings는 XML 파일에 정의하도록 하는 이미 만들어진 Preference 클래스의 여러가지 subclass 등을 사용해서 표현 합니다.
Preference 객체는 한개의 세팅에 대한 building block 입니다. 각각의 Preference는 리스의 한 아이템으로 나타나고 사용자에게 해당 UI를 제공해 사용자가 setting을 modify 할 수 있도록 합니다.
예를 들어 CheckBoxPreference 는 체크박스를 표시하는 리스트 아이템을 생성합니다. 그리고 ListPreference는 여러개 중에 하나를 선택할 수 있는 리스트와 함께 dialog를 여는 아이템을 만듭니다.

Preference는 key-value 의 쌍으로 이뤄져 있고 이를 default SharedPreference file에 세팅된 내용을 저장하도록 합니다.
사용자가 세팅을 바꾸면 시스템은 이 SharedPreference 파일의 해당 내용을 업데이트 합니다. 이 SharedPreferences 파일을 직접 접근해서 다루는 것은 세팅된 내용을 read 해야 할 때 입니다.

각 세팅에 대해 SharedPreferences에 저장된 값들은 아래 data type들로 이뤄져 있습니다.

    Boolean
    Float
    Int
    Long
    String
    String Set

여러분 앱의 세팅 UI는 Preference 객체를 사용해 만들어 졌기 때문에 (View 객체가 아니라) 특별한 Activity나 Fragment subclass를 사용해서 이 리스트 세팅을 표시해야 합니다.

- 안드로이드 버전 3.0 미만 (API level 10 미만) 에서는 액티비티를 반드시 PreferenceActivity 클래스의 extension으로 build 해야 합니다.
- 안드로이드 버전 3.0 이상 에서는 PreferenceFragment를 가지고 있는 traditional Activity를 사용해서 앱 세팅을 표시해야 합니다. 여러 그룹의 세팅이 있다면 큰 화면을 둘로 나눠 이를 표시하기 위해 PreferenceActivity를 사용할 수도 있습니다.

어떻게 PreferenceActivityPreferenceFragment를 세팅하느냐 하는것은 Creating a Preference Activity Using Preference Fragment 에 대해 설명할 때 다를 겁니다.



Preference



앱에서 사용되는 세팅은 Preference 클래스의 subclass 를 사용해서 표현합니다. 각각의 sub 클래스는 property들의 세트로 이뤄져 있어 세팅의 타이틀과 디폴트값 같은 것들을 특정할 수 있도록 합니다.
각각의 subclass는 각자 자신들의 특징을 나타내는 프로퍼티와 인터페이스들을 제공합니다. 예를 들어 figure 1은 메세징 앱의 세팅에서 화면을 캡쳐한 것입니다 세팅 화면에 있는 각 리스트 아이템은 각기 다른 Preference 객체들을 지원하고 있습니다.

일반적으로 사용하는 preference들 몇개를 보면

 
CheckBoxPreference
    Shows an item with a checkbox for a setting that is either enabled or disabled. The saved value is a boolean (true if it's checked).


ListPreference
    Opens a dialog with a list of radio buttons. The saved value can be any one of the supported value types (listed above).


EditTextPreference
    Opens a dialog with an EditText widget. The saved value is a String.
   
   
다른 subclass들과 거기에 속하는 프로퍼티들을 보시려면 Preference 클래스를 참조하세요.

물론 이렇게 제공되는 클래스들이 모든것들을 커버하지는 못합니다. 어떤 경우에는 다른 특정한 형태가 필요할 수 있습니다. 예를 들어 숫자나 날짜를 picking 하는 preference 클래스는 제공되지 않습니다. 그래서 여러분만의 Preference Subclass를 만들어야 될 수도 있습니다. 이것에 대해 알고 싶으시면 Building a Custom Preference 섹션을 보세요.



Defining Preferences in XML



runtime에 Preference object를 instantiate 할수도 있지만 세팅 리스트는 XML에 Preference objects 의 hierarchy 와 함께 정의 해 놔야 합니다.
settings의 collection을 정의하기 위해 XML 파일을 사용하는 것이 더 좋습니다. 왜냐하면 이렇게 하면 쉽게 읽을 수 있고 update 하기도 간단하기 때문이죠.
또한 세팅은 일반적으로 pre-determined 됩니다. 그리고 runtime 때 이 collection들을 수정할 수도 있습니다.

Preference subclass는 클래스 이름을 사용해서 XML element로 정의 될 수 있습니다. i.e. <CheckBoxPreference>
이 XML 파일은 res/xml 디렉토리에 저장해야만 합니다. 파일 이름은 마음대로 해도 되지만 일반적으로 preferences.xml 로 합니다.
대개 1개의 파일만 필요합니다. 왜냐하면 hierarchy의 branch 들은 PreferenceScreen 대신 nested instance들에 의해 정의되기 때문입니다.

Note : 세팅에 multi-pane layout을 만드려면 각 fragment당 별도의 XML 파일을 말들어야 합니다.

XML 파일의 root node는 반드시 element 여야 합니다. 이 element 안에 각각의 Preference를 추가 합니다. <PreferenceScreen> element 안에 추가한 각 child는 세팅 리스트안의 각 아이템으로 표시됩니다.




예제



<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference
        android:key="pref_sync"
        android:title="@string/pref_sync"
        android:summary="@string/pref_sync_summ"
        android:defaultValue="true" />
    <ListPreference
        android:dependency="pref_sync"
        android:key="pref_syncConnectionType"
        android:title="@string/pref_syncConnectionType"
        android:dialogTitle="@string/pref_syncConnectionType"
        android:entries="@array/pref_syncConnectionTypes_entries"
        android:entryValues="@array/pref_syncConnectionTypes_values"
        android:defaultValue="@string/pref_syncConnectionTypes_default" />
</PreferenceScreen>




이 예제에서는 CheckBoxPreference ListPreference 가 있습니다. 각 아이템들은 아래의 3가지 attribute들을 포함하고 있습니다.


android:key
    This attribute is required for preferences that persist a data value. It specifies the unique key (a string) the system uses when saving this setting's value in the SharedPreferences.

    The only instances in which this attribute is not required is when the preference is a PreferenceCategory or PreferenceScreen, or the preference specifies an Intent to invoke (with an <intent> element) or a Fragment to display (with an android:fragment attribute).


android:title
    This provides a user-visible name for the setting.


android:defaultValue
    This specifies the initial value that the system should set in the SharedPreferences file. You should supply a default value for all settings.
   
attribute에 대한 정보를 얻으려면 Preference (and respective subclass) 문서를 참조하세요.

세팅 리스트가 10 아이템을 초과하면 세팅의 그룹별로 title을 추가하고 싶을 겁니다. 혹은 그룹을 별도의 화면에 포시하던가요. 이러한 기능은 아래 섹션을 보세요.



Creating Setting Groups




Figure 2. Setting categories with titles.
1. The category is specified by the <PreferenceCategory> element.
2. The title is specified with the android:title attribute.



10개 이상의 세팅 리스트가 있으면 사용자가 보기 어려워 질 겁니다. 이럴 경우 세팅 리스트를 그룹화 시키면 도움이 될 겁니다. 이렇게 세팅을 그룹화 시키려면 아래 두가지 방법 중 하나를 사용하시면 됩니다.

   
    Using titles
    Using subscreens

이 중 하나만 사용해도 되고 둘 다 사용해도 됩니다. 어떤 걸 사용할 것인지는 어떻게 구분할 지에 따라 결정됩니다. 자세한 것은 Android Design's Settings guide를 참조하세요.



Using titles



세팅내 그룹들 사이에 divider와 heading을 제공하려면 (figure 2) PreferenceCategory 안에 Preference 객체들을 각 그룹으로 묶어 놓습니다.



<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="@string/pref_sms_storage_title"
        android:key="pref_key_storage_settings">
        <CheckBoxPreference
            android:key="pref_key_auto_delete"
            android:summary="@string/pref_summary_auto_delete"
            android:title="@string/pref_title_auto_delete"
            android:defaultValue="false"... />
        <Preference
            android:key="pref_key_sms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_sms_delete"... />
        <Preference
            android:key="pref_key_mms_delete_limit"
            android:dependency="pref_key_auto_delete"
            android:summary="@string/pref_summary_delete_limit"
            android:title="@string/pref_title_mms_delete" ... />
    </PreferenceCategory>
    ...
</PreferenceScreen>




Using subscreens



subscreen으로 세팅 그룹을 나누고 싶으면 (figure 3) PreferenceScreen 안에 Preference 객체들의 그룹을 묶어 놓습니다.



Figure 3. Setting subscreens. The <PreferenceScreen> element creates an item that, when selected, opens a separate list to display the nested settings.



예제



<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- opens a subscreen of settings -->
    <PreferenceScreen
        android:key="button_voicemail_category_key"
        android:title="@string/voicemail"
        android:persistent="false">
        <ListPreference
            android:key="button_voicemail_provider_key"
            android:title="@string/voicemail_provider" ... />
        <!-- opens another nested subscreen -->
        <PreferenceScreen
            android:key="button_voicemail_setting_key"
            android:title="@string/voicemail_settings"
            android:persistent="false">
            ...
        </PreferenceScreen>
        <RingtonePreference
            android:key="button_voicemail_ringtone_key"
            android:title="@string/voicemail_ringtone_title"
            android:ringtoneType="notification" ... />
        ...
    </PreferenceScreen>
    ...
</PreferenceScreen>




Using intents



어떤 경우는 preference item을 settings screen이 아니라 다른 activity에서 열기를 원할 때도 있을 수 있습니다.
예를 들어 웹페이지를 보기 위한 웹브라우저 같은 경우.
사용자가 preference item을 선택했을 때 Intent 를 invoke 하려면 <intent> element를 해당 <preference> element의 child로 추가 합니다.

예를 들어 웹 페이지를 열 경우 이렇게 합니다.



<Preference android:title="@string/prefs_web_page" >
    <intent android:action="android.intent.action.VIEW"
            android:data="http://www.example.com" />
</Preference>



다음의 attribute들을 사용해서 implicit/explicit intent를 사용할 수 있습니다.


android:action
    The action to assign, as per the setAction() method.


android:data
    The data to assign, as per the setData() method.


android:mimeType
    The MIME type to assign, as per the setType() method.


android:targetClass
    The class part of the component name, as per the setComponent() method.


android:targetPackage
    The package part of the component name, as per the setComponent() method.

   
Creating a Preference Activity



settings를 activity 안에 표시하려면 PreferenceActivity class를 extend 합니다.
Preference objects 의 hierarchy에 기반해 settings 리스트를 표시하는 Activity class의 extension 입니다.
PreferenceActivity는 사용자가 변경을 했을 때 각 preference와 연관된 세팅들을 자동적으로 persist 시켜 줍니다.

Note : Android 3.0 이상이면 대신에 PreferenceFragment를 사용해야 합니다. 사용법은 Using Preference Fragments section에서 설명 됩니다.



기억해야 할 가장 중요한 부분은 onCreate() callback 동안 view들의 레이아웃을 로딩하지 않는다는 겁니다. 대신에 addPreferencesFromResource() 를 호출해서 XML 파일에 선언한 preference를 Activity에 추가하게 됩니다.
예를 들어 PreferenceActivity를 위한 아주 간단한 코드는 아래와 같습니다.



public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}



이 정도면 어떤 경우에는 충분할 겁니다. 왜냐하면 사용자가 preference를 수정하면 시스템은 이 변경된 값을 default SharedPreferences 파일에 저장을 할 것이기 때문입ㅂ니다.
이렇게 되면 다른 어플리케이션 컴포넌트들에서 이 사용자의 세팅값을 읽어서 사용할 수 있게 됩니다.
하지만 대부분의 경우에는 좀 더 코딩이 필요할 겁니다. 변경이 될 떄 listen 하기 위한 경우 일 텐데요. 이와 관련해서는 Reading Preferences section을 참조하세요.



Using Preference Fragments



Android 3.0 (API level11) 이상에서는 Preference objects의 리스트를 표시하기 위해 PreferenceFragment를 사용해야 합니다.
어느 activity에나 이 PreferenceFragment를 추가할 수 있습니다. (PreferenceActivity를 사용할 필요가 없습니다.)

onCreate() 메소드에서 addPreferencesFromResource() 를 사용하면 아주 간단하게 PreferenceFragment를 implement 할 수 있습니다.



예제



public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.preferences);
    }
    ...
}



이렇게 한 후 이 fragment를 Activity에 추가하려면 다른 Fragment를 추가하는 것과 똑 같이 하시면 됩니다.




예제



public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }
}



Note : PreferenceFragment는 Context object를 가지고 있지 않습니다. 만약에 Context 객체가 필요하면 getActivity()를 call 하실 수 있습니다.
이 경우 주의해야 할 사항은 activity에 fragment가 attach됐을 때 사용해야 한다는 겁니다. attach 되지 않았을 경우나 lifecycle이 종료되서 detached 됐을 경우 getActivity() 는 null을 return 하게 됩니다.



Setting Default Values



생성된 preferences들은 앱에서 중요한 behavior들이 정의 돼 있을 겁니다. 그래서 관련된 SharedPreferences 파일을 default value들과 함께 initialize 시키는 것이 중요합니다.
이 작업은 사용자가 앱을 처음 open 할 떄 이뤄져야 합니다.

이를 위해 첫번째로 해야 할 것은 XML 파일에 각 Preference objects 마다 default value를 정해줘야 한다는 겁니다. 이 경우 android:defaultValue attribute를 사용합니다.
이 값은 어떤 data type도 될 수 있습니다.



예제



<!-- default value is a boolean -->
<CheckBoxPreference
    android:defaultValue="true"
    ... />

<!-- default value is a string -->
<ListPreference
    android:defaultValue="@string/pref_syncConnectionTypes_default"
    ... />

그런 다음 main activity의 onCreate() 메소드에서 setDefaultValues();를 호출합니다.



PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);



onCreate()에서 호출 할 때 앱에는 디폴트 세팅이 제대로 초기화 되어 있어야 합니다. 예를 들어 cellular network 에서 다운로드를 할 것인지 여부 같은 것들이 제대로 세팅 되어 있어야 할 겁니다.



이 메소드는 세개의 arguments를 가지고 있습니다.



    Your application Context.
    The resource ID for the preference XML file for which you want to set the default values.
    A boolean indicating whether the default values should be set more than once.


    false일 경우 시스템은 이 메소드가 이전에 한번도 호출되지 않았을 경우에만 디폴트 값들을 세팅합니다. (or the KEY_HAS_SET_DEFAULT_VALUES in the default value shared preferences file is false).

3번째 argument를 false로 세팅하면 사용자가 저장한 preference를 디폴트 값으로 overriding 하지 않고 reseting 함으로서 activity가 시작될 때마다 안전하게 호출하도록 할 수 있습니다.
이 값을 true로 설정하면 이전의 저장된 값이 디폴트 값으로 override 하게 됩니다.

반응형


반응형

요즘 대체에너지에 관심이 많이 가서 직접 사용해 보기로 했습니다.
그래서 일단 휴대용 태양광 발전기를 수소문 하다가 Goal Zero사의 제품을 구입했습니다.




GOAL ZERO YETI 150 Solar Generator 와 NOMAD 20 Solar Panel 입니다.


원래 가격은 Generator가 229.99 달러이고 태양광 패널은 199.99 달러 입니다.
그런데 여기저기 구글링 하다가 온라인 쇼핑몰에서 조금 더 싸게 샀습니다.

제너레이터는 Bed Bath & Beyond에서 159.99 달러에 샀습니다.
Tax 11.64 달러 붙어서 171.63 달러가 지출 했어요.

그리고 패널은 140.48달러에 샀습니다. 세금은 없었습니다.

그래서 총 312.11 불 들었습니다.


한 일주일간 사용해 본 후기는....

충전 시간이 너무 길다.....




제품설명서에 보면 구입한 Solar Panel로 충전을 하려면 엥꼬에서 만땅 채우는데 무려 17~34시간이 걸린 답니다.
그것도 아주 햇볕 조건이 좋을 때....

하루 종일 해가 떠 있는 것도 아니고.. 하루 8시간으로 계산해도 4일정도는 걸리는데...
그것도 아침 저녁으로는 해가 약하고 또 날씨가 흐리거나 비가 오면 충전 속도가 느리니까...
태양광 패널로 Full Charge 하려면 10일은 걸리겠네요.

가정용 전기로 충전해도 되지만 (이럴 경우 8시간 걸림) 태양광 발전을 직접 체험해 보기 위해 산거니까 패널로만 계속 충전해 보려구요.

이번주는 날씨가 계속 흐렸거든요.
차 운전석 앞에 패널을 놓고 일주일 내내 남쪽을 향해 주차를 해 놓고 있었는데...
계속 40% 에서 60% 로 가는 눈금만 깜빡입니다.

밧데리 눈금이 올라가지 않아요.

다행히 다음주엔 해 쩅쩅인 날이 많은것 같아서 기대하고 있습니다.




과연 만땅을 채울 수 있을 지......


용도는 자동차로 돌아다니거나 여행 다닐 때 모바일 기기 충전용으로 좋은 것 같습니다.
자동차 12 볼트 어댑터에서 충전할 수도 있지만..
거긴 네비게이션이 꽃혀있어야 해서 모바일 기기랑 서로 번갈아 가며 충전을 했었는데 좀 불편하더라구요.
더군다나 가족도 핸드폰이 있어서 그거 하나로는 많이 부족했는데...

일단 이 제너레이터와 태양광 패널은 크기가 작아 차에 놓고 다니기 편해서 유용하게 사용할 수 있습니다.

태양광 발전으로 충전을 하려면 Solar Panel, Charge Controller, Inverter, Battery 이렇게 4가지가 기본적으로 필요한데.
이 제너레이터에는 Charge Controller, Inverter 그리고 Battery가 하나의 통속에 들어있어 따로 구입해서 연결하는 작업을 할 필요가 없습니다.
그래서 따로 구입하는 것 보다 공간도 덜 차지하고 각각 구입하는 것보다 가격도 저렴하게 구입할 수 있습니다.

저는 그냥 자동차에 항상 배치해 놓고 있습니다. 집안으로 들여놨다가 다시 차로 가져가고 하는 것도 일이더라구요.
더군다나 다른 짐들도 있고...

30여만원 주고 샀으니 단지 핸드폰 충전용으로는 좀 비싸긴 하지만 대체 에너지에 대해 체험하고 배우는 과정으로 생각하면서 위안을 삼고 있어요.

다음엔 인력을 이용해 발전을 할 수 있는 장난감을 만들어 볼 생각입니다.
그리고 풍력발전도 체험해 보고 싶고...

그런거 만들려면 여러가지 Tool들이 필요하겠더라구요.
그래서 요즘 열심히 Garage Sale 을 돌아다니고 있습니다. :)



반응형


반응형

3과에서는 날씨 리스트에서 한 라인을 클릭하면 그 날의 자세한 날씨가 표시되도록 할 겁니다.
클릭하면 다른 화면으로 넘어가는데요. 이것은 또 다른 Activity를 생성하고 Main Activity 에서 이 새로운 Activity로 전환할 수 있는 기능이 필요합니다.
그리고 새로운 화면 (Activity)로 넘어간 후 그곳에서 표시해 줄 정보를 출력해서 보여주는 UI를 만드는 일도 해야 하구요.

대충 이런 기능들을 구현할 것 같습니다.

리스트에서 아이템을 클릭했을 경우 작동하는 메소드는 setOnItemClickListener 입니다.
https://developer.android.com/reference/android/widget/ListView.html

이 메소드를 기존 코드에서 구현할 겁니다.

ForecastFragment class의 onCreateView 로 가서 맨 아래에 아래와 같이 코드를 추가합니다.



       // Get a reference to the ListView, and attach this adapter to it.
        ListView listView = (ListView) rootView.findViewById(R.id.listview_forecast);
        listView.setAdapter(mForecastAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                String forecast = mForecastAdapter.getItem(position);
                Toast.makeText(getActivity(), forecast, Toast.LENGTH_SHORT).show();
            }


listView.setAdapter(mForecastAdapter); 까지는 이전에 있었습니다.

그 이후에 setOnItemClickListener() 를 구현했는데요.

이 메소드 안의 onItemClick()을 Override 했습니다.


이 안에서는 클릭한 아이템의 포지션을 Toast로 보이도록 했습니다.



이렇게 하면 위에 보이는 것처럼 아이템을 클릭했을 시 그 아이템 내용이 Toast에 잠깐 보였다가 사라집니다.


이렇게 되면 onItemClick 까지 제대로 잘 작동 되고 있는 것을 알 수 있습니다.



이 다음에는 클릭한 날의 자세한 날씨를 보여주는 새로운 페이지로 이동해야 합니다.


그러려면 이동할 페이지를 만들어야 하고 이 새로운 페이지를 AndroidManifest파일에 등록해야 합니다.


이 새로운 페이지에서는 이전 화면으로 돌아가는 버튼을 만들겁니다.

그리고 그 아래에 해당 날짜의 자세한 정보를 보여 줄것이구요.


우선 DetailActivity를 생성합니다.


New-Activity-Blank Activity 를 선택합니다.



Activity 이름은 DetailActivity로 하고 Hierarchical Parent는 MainActivity로 합니다.

그리고 Use a Fragment도 선택을 합니다.



그러면 DetailActivity클래스와 함께 DetailActivityFragment 클래스가 자동으로 생성됩니다.

그리고 관련된 xml파일들도 생성됩니다. (activity_detail.xml, fragment_detail.xml)


일단 DetailActivity는 이렇게 만듭니다.



/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.sunshine.app;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

public class DetailActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment())
                    .commit();
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.detail, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {

            View rootView = inflater.inflate(R.layout.fragment_detail, container, false);
            return rootView;
        }
    }
}


그리고 activity_detail.xml은 이렇게 합니다.


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container"
    android:layout_width="match_parent" android:layout_height="match_parent"
    tools:context="com.example.android.sunshine.app.DetailActivity" tools:ignore="MergeRootFrame" />


그리고 menu_detail.xml은 detail.xml로 이름을 바꿉니다.

해당 xml에서 오른쪽 마우스 클릭을 한 후 Refactor - Rename을 선택하면 이름을 바꿀 수 있습니다.



그리고 DetailActivityFragment.java는 delete합니다.

이건 현재 DetailActivity.java에 있는 PlaceholderFragment.java로 replace될 겁니다.


이렇게 된 코드는 3.02_create_detail_activity  에서 받아 볼 수 있습니다.





Activity들 간에 이동을 하려면 startActivity(Intent)를 사용하면 됩니다.


위에서 Toast를 구현했던 부분에 이 startActivity()를 구현할 겁니다.


setOnItemClick()을 아래와 같이 바꿉니다.


        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                String forecast = mForecastAdapter.getItem(position);
                //Toast.makeText(getActivity(), forecast, Toast.LENGTH_LONG).show();

Intent intent = new Intent(getActivity(), DetailActivity.class).putExtra(Intent.EXTRA_TEXT, forcast);

startActivity(intent)

            } 


이렇게 하면 DetailActivity 클래스로 이동합니다.


현재 상태에서는 DetailActivity로 넘어가면 아무것도 안 보일 겁니다.


fragment_detail.xml 에 있는 내용이 뿌려질건데 현재 그 내용은 아래와 같습니다.


<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.android.sunshine.app.DetailActivityFragment">


</RelativeLayout>


아무것도 내용이 없습니다.


여기에 해당 날짜의 forcast data를 읽어서 뿌려 주도록 하겠습니다.


DetailActivity의 onCreateView() 안에 아래와 같이 코등합니다.


        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {

            View rootView = inflater.inflate(R.layout.fragment_detail, container, false);

            // The detail Activity called via intent.  Inspect the intent for forecast data.
            Intent intent = getActivity().getIntent();
            if (intent != null && intent.hasExtra(Intent.EXTRA_TEXT)) {
                String forecastStr = intent.getStringExtra(Intent.EXTRA_TEXT);
                ((TextView) rootView.findViewById(R.id.detail_text))
                        .setText(forecastStr);
            }

            return rootView;
        }


그리고 fragment_detail.xml은 아래와 같이 합니다.


<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.android.sunshine.app.DetailActivity.DetailFragment">

    <TextView android:text="@string/hello_world" android:layout_width="wrap_content"
        android:id="@+id/detail_text"
        android:layout_height="wrap_content" />

</RelativeLayout>


이제 리스트 아이템을 클릭하면 Toast가 나오는 대신 아래와 같이 DetailActivity가 표시 될 겁니다.




여기까지 진행된 소스코드는 이곳에서 받으실 수 있습니다.


https://github.com/udacity/Sunshine-Version-2/tree/3.04_populate_detail_text




이제 DetailActivity에 해당 날짜의 자세한 날씨를 표시하는 화면을 구성할 차례입니다.


이 작업은 다음 글에서 이어 나가겠습니다.




반응형


반응형




DIY 태양광 발전을 하려고 하는데 Solar Panel 도 여러 종류이고 배터리도 다양하고 Inverter도 한두가지가 아니라서 진도가 잘 안 나갑니다.


그리고 전기 제품들 마다 Amp, Watts, Wh, Volts 등등 잘 모르는 단위들이 막 있고 이걸 잘 못 연결하면 이상한 일이 벌어질 것 도 같구요.


한국전자제품은 다 220 볼트용이라서 미국에 갖고 오면 꽂을수도 없고 설사 연결한다고 해도 제대로 작동이 안 되듯이 패널, Charge Controller, Battery, Inverter 등등이 서로 궁합이 잘 맞아야 될 것도 같은데 거기에 대한 지식은 없고....


그래서 여기 저기 검색을 하다 보니까 유투브에 이런 좋은 정보가 있더라구요.




내용을 보면 우선 태양광 시스템으로 어떤 제품을 돌리려고 하는지 그 대상을 정해야 합니다.

예를 들어 노트북이나 작은 냉장고, TV 같은 걸 돌릴건지 아니면 휴대폰이나 각종 USB 연결해서 사용하는 제품, 사진기 배터리 충전 등등 작은 휴대용 제품들이 대상인지 정해야 합니다.


일단 정하고 나면 각 전기 제품들 마다 있는 Power Consumption Rating (소비전력) 들을 파악합니다. 


여러가지 내용들이 있는데 그 중 Output Wattage를 살펴 봅니다.


제품들을 보면 와트 (W) 표시가 있는데 이 정보가 바로 DIY 태양광 발전 계획을 세울 때 필요한 정보 입니다.




위의 것은 60W 아래 것은 2.5 W 입니다.


가끔 가다 이런 W 정보가 없고 볼트 (V) 정보와 암페어 (A) 정보만 있는 경우가 있습니다.



위의 경우 8.4 볼트와 500 밀리암페어 정보만 있습니다.

이럴 경우 이 둘을 곱하면 됩니다. 볼트 X 암페어를 하면 와트가 됩니다.

8.4 X 0.5 = 4.2

이 제품의 와트는 4.2 입니다.




이 비디오 에서는 아래 전기 제품들을 사용하는 경우를 상정했습니다.

예)
Laptop 1 = 60 watts
Laptop 2 = 45 watts
Battery Charger = 4.2 watts
iPod Touch = 4 watts
USB Fan = 2.5 watts

Total = 115.7 watts (around 120w)


대략 필요한 모든 전기 제품들의 Output Wattage는 대략 Total 120 와트 입니다.


그 다음 할 일은 필요한 시스템 사이즈를 계산해야 합니다.

이 계산은 아래 싸이트에 가면 쉽게 할 수 있습니다.

Solar Sizing Calculator : http://www.renogy.com/calculators



필요한 정보를 입력하면 시스템 사이즈와 배터리 사이즈 정보를 얻을 수 있습니다.


이것을 근거로 Panel과 Battery를 선택할 수 있습니다.


AH란?

 Ampere Hour의 약자로 Ampere는 전류의 기본 단위이고 Hour은 시간 표시입니다.

즉 50AH란 1시간에 전류 50A를 흘릴 수 있다는 의미입니다.

예를 들어 50A 배터리에 12V/10A용 전구를 달았다면 이때 배터리가 견딜 수 있는 시간은 5시간(50A ÷ 10A = 5H)이 됩니다.

그러므로 설치하는데 문제가 없는 싸이즈라면 그 정도(12V/60A) 차이는 큰 문제가 없습니다.

너무 차이가 나면 알터네이터 용량하고도 연관이 되므로 곤란(이론적인 설명 생략)합니다.



그 다음은 Inverter size 를 정해면 됩니다.

인버터는 Maximum Wattage 가 필요한 Wattage (이 비디오의 예에서는 120 W) 만 커버할 수 있다면 크기는 작을 수록 좋습니다.



위의 예에서는 120 와트만 커버하면 되니까 이 500 와트짜리 인버터면 아주 충분할 겁니다.




아직도 좀 감이 덜 잡히기는 하지만 그래도 많이 공부한 것 같습니다.



반응형

Android Toast 정리

2016. 9. 7. 22:32 | Posted by 솔웅


반응형

Toasts



Toast 는 LogCat 과 함께 Android App을 개발하면서 간단하게 테스트 할 때 유용하게 사용되는 기능입니다.
관련된 Android API Guide 는 이곳에서 보실 수 있습니다.


https://developer.android.com/guide/topics/ui/notifiers/toasts.html#Basics



If user response to a status message is required, consider instead using a Notification 



The Basics



makeText() 메소드를 사용해 Toast 객체를 초기화합니다. 이 메소드는 3개의 파라미터를 갖습니다. (the application Context, the text message, and the duration for the toast)
show()를 사용해서 toast notification을 display 할 수 있습니다.

Context context = getApplicationContext();
CharSequence text = "Hello toast!";
int duration = Toast.LENGTH_SHORT;

Toast toast = Toast.makeText(context, text, duration);
toast
.show();



이 예제 형식을 가장 많이 사용합니다. 다른 형식은 거의 사용할 일이 없을 겁니다. Toast 메세지의 표시 위치를 다르게 한다거나 Toast 메세지의 layout 을 여러분이 원하는 대로 꾸미고 싶을 경우 추가적으로 코딩을 하면 됩니다.

아래와 같이 한줄로 처리할 수 있습니다.

Toast.makeText(context, text, duration).show();



Positioning your Toast



기본적으로 Toast notification은 화면의 아랫쪽 중앙에 표시됩니다. 이 위치를 바꿀 수 있는데요. setGravity(int, int, int) 메소드를 사용하시면 됩니다.
이 메소드는 3개의 파라미터를 갖습니다. (a Gravity constant, an x-position offset, and a y-position offset)

예를 들어 toast가 top-left 에 표시되기를 원하시면 아래와 같이 하면 됩니다.

toast.setGravity(Gravity.TOP|Gravity.LEFT, 0, 0);



오른쪽으로 좀 옮기고 싶으면 두번째 파라미터의 값을 높이면 됩니다. 아랫쪽으로 옮기고 싶으면 마지막 파라미터의 값을 올리면 됩니다.



Creating a Custom Toast View



메세지가 간단하지 않으면 layout을 customize 할 수 있습니다. custom layout을 만들려면  View layout을 XML 파일에 정의해야 합니다. 혹은 어플리케이션 안에 정의 할 수도 있ㅅ브니다. 그리고 root ViewsetView(View) 메소드로 pass 합니다.

예제 (saved as layout/custom_toast.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
             
android:id="@+id/custom_toast_container"
             
android:orientation="horizontal"
             
android:layout_width="fill_parent"
             
android:layout_height="fill_parent"
             
android:padding="8dp"
             
android:background="#DAAA"
             
>
   
<ImageView android:src="@drawable/droid"
               
android:layout_width="wrap_content"
               
android:layout_height="wrap_content"
               
android:layout_marginRight="8dp"
               
/>
   
<TextView android:id="@+id/text"
             
android:layout_width="wrap_content"
             
android:layout_height="wrap_content"
             
android:textColor="#FFF"
             
/>
</LinearLayout>



LinearLayout element의 ID는 "custom_toast_container" 입니다. 이 xml 파일의 이름은 custom_toast 이구요.




LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast,
               
(ViewGroup) findViewById(R.id.custom_toast_container));

TextView text = (TextView) layout.findViewById(R.id.text);
text
.setText("This is a custom toast");

Toast toast = new Toast(getApplicationContext());
toast
.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast
.setDuration(Toast.LENGTH_LONG);
toast
.setView(layout);
toast
.show();



우선 이 xml layout 파일을 inflate 시켰습니다. 그리고 infalte 시킬때 위에 언급한 ID 인 custom_toast_container 를 사용했구요.
이러면 해당 LinearLayout 이 성공적으로 inflate 되게 됩니다.
getLayoutflater() (혹은 getSystemService()) 를 사용해 LayoutInflater를 retrieve 합니다. 그리고 inflate(int, ViewGroup) 을 사용해서 layout 을 inflate 시키구요.
첫번째 파라미터는 layout resource의 ID 이구요. 두번째는 root View 입니다. 이 inflate 된 layout 에 다른 View 객체들을 넣을 수 있는데요. 여기서는 TextView를 넣었습니다. Image View를 넣을 수도 있습니다.

마지막으로 Toast(Context)를 사용해서 새로운 Toast를 생성하고 property들을 세팅합니다. 여기서는 Gravity 와 Duration을 세팅했습니다. 그 다음은 setView(View) 를 호출하고 이것을 inflate 된 layout에 pass 합니다. 이렇게 하면 show() 를 사용해서 customize 된 layout으로 toast를 표시할 수 있게 됩니다.

Note: Do not use the public constructor for a Toast unless you are going to define the layout with setView(View). If you do not have a custom layout to use, you must use makeText(Context, int, int) to create the Toast.



반응형


반응형

요즘 태양광 발전에 관심이 있어서 공부하고 있다.

이것 저것 몇가지 유투브를 봤는데 가장 기본적인 부품 4가지는 아래와 같은게 있는 것 같다.

지금부터 하나 하나 공부해서 필요한 부품들을 따로 사서 집에서 혹은 휴대용으로 사용할 수 있는 태양광 발전 시설을 내 손으로 직접 제작하는게 목표다.


1. Solar Panel



태양광 발전 하면 제일 먼저 떠오르는 패널이다.




2. Solar charge controller



이건 태양광 패널에서 배터리 사이에 들어가는 부품으로 배터리가 over charge 되지 않도록 하는 것 같다.




3. Battery



태양광용 배터리가 따로 있는지는 모르겠다.

아마 일반 배터리를 사용해도 되지 않나 싶다.

그냥 월마트나 홈데포에서도 구입할 수 있는 것 같다.



4. Power inverter

이 인버터는 배터리에 저장돼 있는 전기를 일반 가정제품에 연결해 사용할 수 있도록 하는 장치이다.



인터넷 써핑을 해 봤더니 이 웹싸이트에서 좀 배울게 있는 것 같다.


https://www.renogy.com/learn#tab_solar-basics 


시간 나는 대로 배워서 올해 안으로 간단한 태양광 발전 장치를 만들어 봐야겠다.

반응형


반응형

오늘은 Debug mode로 돌리면서 2과 소스코드의 실행 순서를 하나하나 따라가 보기로 하겠습니다.


2과 소스코드는  아래 GitHub 에 가시면 받을 수 있습니다.


https://github.com/udacity/Sunshine-Version-2/tree/2.09_display_data

 



Break point 를 MainActivity.java 의 onCreate() 메소드의 if 문에 찍어 놨습니다.

보시듯이 이 앱을 실행하면 MainActivity 의 onCreate()메소드가 실행됩니다.

savedInstanceState는 Null 이구요 즉 처음 실행되는 거란 얘기죠.

그러면 if문의 조건을 만족하니까 그 내부의 코드들이 실행 될 겁니다.


그 전에 setContentView()에서 activity_main.xml에서 설정된 layout을 세팅합니다.

이 xml에는 한개의 FrameLayout이 세팅돼 있습니다.



여기서 그 다음 step 으로 넘어가면 if 문 안의 것이 실행될 겁니다.

안에는 위 xml에서 container라고 정의된 id 에 ForecastFragment() 클래스를 세팅합니다.



이제 ForecastFragment.java 파일로 넘어 갑니다.


제일 먼저 생성자인 ForecastFragment()로 갑니다. 일단 여기는 empty 입니다.

그 다음은 다시 MainActivity 로 넘어가서 if 문 안의 commit()부분이 실행됩니다.


그 다음 step over를 해 보면 안드로이드 자체 클래스들에 있는 메소드들이 실행 됩니다.

Activity.java > performCreate() 를 거쳐서 ActivityThread.java  에 있는 코드들이 실행 됩니다.


그 이후 다시 ForecastFragment클래스의 onCreate() 메소드로 가게 됩니다.



여기서는 setHasOptionsMenu(true); 를 설정해 놨죠.

옵션메뉴를 사용하겠다는 건데요.


그 다음 step over를 해 보면 Fragment.java > performCreate()를 거쳐 FragmentManager.java 클래스의 여러 코드들이 실행 됩니다.


그 다음에 가는 것이 ForecastFragment 클래스의 onCreateView() 메소드로 가게 됩니다.


아직 까지는 에뮬레이터에 아무것도 표시되지 않는데요. 이 onCreateView()를 지나게 되면 view가 표시 될 겁니다.





이 onCreateView()에서는 날씨 정보를 하드코딩해서 리스트에 넣고 ArrayAdapter를 사용해서 listview에 뿌릴 준비를 했습니다.


그리고 inflater를 사용해서 fragment_main.xml에 있는 layout을 로딩했죠.




이 Layout에는 ListView를 사용하도록 해 놨습니다.


보시면 이 ListView의 id 는 listview_forecast 인데요. onCreateView()에서는 이 아이디에 방금전에 사용했던 ArrayAdapter 를 대입해서 하드코딩한 데이터 정보들을 ListVIew에 넣었습니다.

그리고 이 ListView를 rootView에 넣어서 return 하게 됩니다.



그 다음 단계는 Fragment.java > performCreateView() 를 거쳐 FragmentManager, BackStackRecord , Instrumentation 클래스의  callActivityOnStart() 메소드, Activity, ActivityThread, Handler, Looper 클래스 등 여러 코드들을 실행합니다.


그러면 드디어 에뮬레이터에 데이터가 뿌려지게 됩니다.




그런데 잘 보시면 이 화면에는 옵션 메뉴가 없습니다.

아직 옵션메뉴 관련된 부분은 실행이 되지 않은 겁니다.


현재 디버거는 MainActivity 클래스의 onCreateOptionMenu()메소드에 와 있습니다.




여기서는 MenuInflater를 사용해서 manu에 있는 main.xml을 처리했습니다.



그 다음엔 안드로이드의 아래와 같은 클래스들을 거치면서 많은 메소드들이 실행됩니다.


Activity.java > onCreatePanelMenu() >>> FragmentActivity.java > onCreatePanelMenu() >>> ForecastFragment.java > onCreateOptionsMenu() >>> FragmentManager class >>> ActionBarActivity class >>> ActionBarActivityDelegate class >>> ActionBarActivityDelegateBase class >>> Handler class >>> Looper class 


그러면 옵션 메뉴가 표시됩니다.




위에서 ForecastFragment 클래스의 onCreateOptionMenu() 메소드도 실행 됐는데요.


여기서는 forecastfragment.xml 을 inflate 시켰습니다.


<?xml version="1.0" encoding="utf-8"?>

<
menu xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:app="http://schemas.android.com/apk/res-auto">
    <
item android:id="@+id/action_refresh"
       
android:title="@string/action_refresh"
       
app:showAsAction="never" />
</
menu>


refresh 메뉴를 만든 겁니다.


이제 MainActivity클래스의 onOptionsItemSelected()도 로딩이 될 겁니다.



아직 Setting을 눌렀을 떄는 어떻게 해야 하는지는 코딩을 하지 않았습니다.

대신 Refresh 를 눌렀을 경우에는 ForecastFragment class의 onOptionsItemSelected() 메소드에서 처리 되도록 만들었습니다.


@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
   
int id = item.getItemId();
    if (id == R.id.action_refresh) {
        FetchWeatherTask weatherTask = new FetchWeatherTask();
        weatherTask.execute("55347");
        return true;
    }
    return super.onOptionsItemSelected(item);
}


Refresh 를 누르면 FetchWeatherTask클래스에 55347이라는 파라미터를 던져줘서 그 클래스가 처리 되도록 합니다.


이 클래스에서는 이전 글에서 다뤘듯이 AsyncTask를 사용해서 네트워크 연결 부분을 Background Thread로 돌리고 날씨 웹에 request를 던져서 respons를 JSON형식으로 받은 다음에 이것을 파싱해서 화면에 뿌려주는 작업을 합니다.




그러면 이렇게 제가 사는 지역의 일주일 날씨 예보가 출력되게 됩니다.




자 여기까지 앱을 실행하면 실제로 소스코드가 어떤 순서로 실행되는지 하나하나 살펴 봤습니다.








반응형
이전 1 다음