View - AppWidget



reference: 

https://developer.android.com/guide/topics/appwidgets/





 AppWidget 개념

흔히 홈(런처)에서 아이콘이 아닌 특별한 형태의 앱 모양을 보신 적이 있을 겁니다. 보통 날씨 등의 앱에서 사용되곤 하는데요. 앱에 진입하지 않고도 제공하는 내용을 표시하는데에 주로 사용됩니다. 아래 이미지를 보면 알 수 있습니다. 

appwidget에 대한 이미지 검색결과


AppWidget 사용법

AppWidget을 만들기 위해서는, 다음과 같은 것들이 필요합니다.

AppWidgetProviderInfo 

: AppWidget에 대한 metadata를 표현합니다. layout이나 얼마나 자주 업데이트 할 것인지, 어떤 AppWidgetProvider를 사용할 것인지 등을 XML 파일로 정의합니다.

AppWidgetProvider

: AppWidget에 대한 broadcast event를 다루는 interface입니다.  AppWidget이 updated, enabled, disabled, deleted 됐을 때에 대한 처리를 합니다.

View layout

: AppWidget의 layout이 어떻게 생겼는지 정의하는 XML 파일입니다.


추가적으로, AppWidget configuration Activity를 만들 수 있는데, 사용자가 AppWidget을 추가했을 때 실행되며, 그 때 어떤 설정 값들을 사용할 것인지 정할 수 있습니다.


    AppWidget을 Manifest에 선언하기

먼저, AppWidgetProvider를 AndroidManifest.xml 파일에 선언해야 합니다.

<receiver android:name="ExampleAppWidgetProvider" >
   
<intent-filter>
       
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
   
</intent-filter>
   
<meta-data android:name="android.appwidget.provider"
               
android:resource="@xml/example_appwidget_info" />
</receiver>

AppWidgetProvider는 앞서 설명한 바와 같이 broadcast receiver이기 때문에 <receiver> 태그로 선언됩니다. 또한, ACTION_APPWIDGET_UPDATE 인텐트를 받도록 필터링 해야함을 확인할 수 있습니다. <meta-data> 태그로 AppWidgetProviderInfo를 선언하였습니다.

AppWidgetProviderInfo metadata 추가하기

AppWidgetProviderInfo는 AppWidget의 필수적인 요소들을 정의합니다. 예를 들어 최소 layout dimension이나 최초의 layout resource 혹은 얼마나 자주 업데이트 할 것인지, configuration Activity를 사용할 것인지 등입니다. 아래와 같이 <appwidget-provider> 태그로 XML 파일에 정의됩니다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   
android:minWidth="40dp"
   
android:minHeight="40dp"
   
android:updatePeriodMillis="86400000"
   
android:previewImage="@drawable/preview"
   
android:initialLayout="@layout/example_appwidget"
   
android:configure="com.example.android.ExampleAppWidgetConfigure"
   
android:resizeMode="horizontal|vertical"
   
android:widgetCategory="home_screen">
</appwidget-provider>

configure attribute에서 configuration Activity를 사용하였음을 확인할 수 있습니다.


AppWidget layout 만들기

res/layout 디렉토리에 반드시 AppWidget의 초기 layout을 만들어놔야 합니다. AppWidget은 RemoteView 클래스에 기반하기 때문에 아무 layout이나 사용할 수 없습니다. 다음 네가지 layout만 사용가능합니다.

FrameLayout

LinearLayout

RelativeLayout

GridLayout

그리고 다음 Widget 클래스들만 허용됩니다:

AnalogClock

Button

Chronometer

ImageButton

ImageView

ProgressBar

TextView

ViewFlipper

ListView

GirdView

StackView

AdapterViewFlipper

 위 클래스를 상속한 클래스는 사용할 수 없습니다.

RemoteView는 ViewStub도 지원하는데, ViewStub은 사이즈가 0인 상태에서 런타임에 inflate하는 layout resource입니다.


AppWidget 패딩

AppWidget은 스크린의 가장자리까지 차지하거나 다른 Widget과 섞이면 안됩니다. 그렇기 때문에 Widget frame의 모든 side에 margin이 있어야합니다. Android 4.0 에서부터는 자동적으로 Widget frame과 그 bounding box 사이에 padding을 부여합니다. 그러니 SDK 14버전 이상을 사용하는 것이 좀 더 안전할 것입니다.

그 말인즉, API 14 미만 타겟의 layout에서는 추가적인 margin을 주어야 하고, 그 이상에서는 줄필요가 없다는 이야기입니다.


AppWidgetProvider 사용하기

AppWidgetProvider는 언급한 바와 같이 AppWidget에 관련한 intent를 받는 BroadcastReceiver입니다. 다음과 같은 메소드들을 갖습니다.

onUpdate()

AppWidgetProviderInfo에 선언된 updatePeriodMillis  속성 값만큼의 주기마다 호출됩니다. 또한 사용자가 AppWidget을 추가해도 호출됩니다. 그러므로 이 메소드는 초기 세팅 관련 작업을 수행해야 합니다. 그러나 configuration Activity를 사용한다면 사용자가 추가할 때 이 메소드는 호출되지 않습니다. 그 역할은 configuration Activity가 대신하기 때문이죠.

onAppWidgetOptionsChanged()

AppWidget이 처음 자리 잡거나, 크기가 바뀔 때 호출됩니다. 바뀐 크기에 따라 내용을 감출지 표시할지 정할 수 있습니다. getAppWidgetOptions() 메소드는 AppWidget의 최소/최대 사이즈에 관련된 option 값들을 포함하는 Bundle 객체를 반환합니다. (API 16 이상)

onDeleted(Context, int[])

AppWidget host로부터 AppWidget에 삭제되었을 때 호출됩니다.

onEnabled(Context)

AppWidget 객체가 처음으로 만들어졌을 때 호출됩니다. 만약 사용자가 두개의 객체를 만들어 추가했다면 처음 한번만 호출됩니다. Database에 접근하는 등의 처음 한번만 필요한 세팅 작업을 하기에 적합합니다.

onDisabled(Context)

AppWidget host에 의해 AppWidget이 삭제되었을 때 호출됩니다. onEnabled()에서 수행된 모든 작업에 대한 청소 작업을 수행해야 합니다.

onReceive(Context, Intent)

위 메소드들이 호출되기 전에 호출되는 메소드입니다. BroadcastReceiver의 메소드입니다.

AppWidgetProvider의 메소드 중 가장 중요한 것은 onUpdate() 콜백입니다. 사용하기에 따라서 이 메소드의 구현만으로 나머지를 대체할 수도 있습니다.


Configuration Activity 만들기

<activity android:name=".ExampleAppWidgetConfigure">
   
<intent-filter>
       
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
   
</intent-filter>
</activity>

위와 같이 Activity 정보를 입력해야 합니다. ACTION_APPWIDGET_CONFIGURE intent를 받아야 합니다. 앞서 언급한 바와 같이, AppWidgetProviderInfo에 android:configure 속성 값에 이 Acitivity가 선언되어 있어야합니다. 주의할 점은, 외부에서 접근하는 Acitivity이기 때문에 namespace는 절대 경로로 적어야 합니다.

이 Activity는 AppWidget의 ID를 result로 돌려줘야 합니다. 자세한 내용은 참조 사이트 확인 하시면 됩니다.


Conclusion

AppWidget은 Activity에 진입하지 않고도 홈에서 중요한 정보를 표시하는데에 사용됩니다. AppWidgetProviderInfo, AppWigetProvider, layout resource 세가지를 구현합니다. Configuration Activity를 선언할 수도 있습니다.



'programming > android' 카테고리의 다른 글

View - RecyclerView  (0) 2018.08.26
ViewGroup  (0) 2018.08.24
View - Widget  (0) 2018.08.23
Companion Objects in Kotlin  (0) 2018.04.08
어떤 Context를 써야 하나요?  (0) 2018.03.26

View - Widget



reference:

https://developer.android.com/reference/android/widget/package-summary 





 Android를 개발하면서 평소에 크게 의문갖지 않았던 부분들에 대해서 정리합니다. Widget에 대해 알아보겠습니다.


 Widget 개념

Widget이란 Android 앱에서 사용자와 상호작용하는 그래픽 요소입니다. 이 Widget은 View 클래스로 구현됩니다. View 클래스는 상호작용 요소로서 기본적인 block으로서 역할을 합니다. 네모난 영역을 차지하며, 그림을 그리고 이벤트 핸들링을 합니다.

android.widget 패키지는 보통 눈에 보이는 UI 요소를 포함하고 있습니다. 나만의 widget을 만들려면, View(혹은 그 하위클래스)를 상속받으면 됩니다. XML안에서 widget을 사용하려면 다음과 같은 파일들이 필요합니다 :

- Java implementation file : widget이 어떻게 동작할 것인지에 대한 java 파일입니다. XML layout으로부터 객체를 만들어낼 수 있다면, XML로부터 attribute 값들을 불러오는 생성자가 정의되어 있어야합니다.

- XML definition file : res/values/ 디렉토리 안의 XML 파일로, widget 및 그 widget의 attributes를 객체화하기 위해 사용되는 파일입니다. 다른 application에서 이 요소와 attributes를 또 다른 요소에서 사용하게 됩니다.

- Layout XML [optional] : res/layout/ 디렉토리 안의 optional한 XML 파일입니다. 이 파일은 widget의 layout을 표현합니다. Java 파일 안에서 이것을 정의할 수 있기 때문에 optional 합니다.

 

Widget 사용법

Widget을 만드는 예제입니다.

1. LableView.java (Java implementation file) : https://android.googlesource.com/platform/development/+/android-6.0.1_r46/samples/ApiDemos/src/com/example/android/apis/view/LabelView.java

코드가 긴 관계로 위 링크로 대체하였습니다. View의 생성자는 네가지가 오버로드 되어있습니다.


public View(Context context)

: 오로지 Java 코드안에서만 생성할때 쓰입니다. 이 생성자만 정의된 경우 XML 안에서 표현될 수 없습니다.

public View(Context context, @Nullable AttributeSet attrs)

: attrs 인자는 위에 설명된 XML definition file로 부터 attributes들을 객체화 시키는데 사용됩니다.

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)

: defStyleAttr 인자는 현재 테마안에서 View의 default 값을 제공하는 style resource에 대한 참조를 갖고있습니다. default 값을 갖지 않으려면 0을 넣어줍니다.

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

: defStyleRes 인자는 뷰에대한 default 값을 제공하는 style 리소스의 identifier입니다. defSytleAttr이 0 일때 혹은 현재 테마에서 찾을 수가 없을 때 사용됩니다. default 값을 갖지 않으려면 0을 넣어줍니다.


보통 생성자에서는 attrs(AttributeSet)을 객체화하여 View를 그릴 때 참고할 수 있게 합니다. 

Context의 obtainStyeldAttributes 메소드를 이용해 XML 파일에 정의된 attribute들을 객체화하여 TypedArray에 담습니다. 이 TypedArray로 부터 각각의 attribute들을 가져온 후 View를 그릴 때 활용 하는 것입니다.

Attribute들을 객체화하는데 성공했다면, 이제 실제로 View를 그려야합니다. View는 그려지는 단계는 세가지 단계입니다: 

Measurement - onMeasure 메소드 : View의 가로와 세로의 크기를 결정합니다. MeasureSpec을 이용해서 bitwise로 연산된 int 값을 이용합니다.

Layout - onLayout 메소드 : 결정된 크기를 기준으로 어느 위치에 그릴 것인지 결정합니다.

Draw - onDraw 메소드 : Canvas 객체를 이용하여 실제로 무엇을 그릴 것인지 결정합니다. 

위 메소드 들을 단계적으로 실행함으로서 비로소 View가 그려집니다.


2. attrs.xml (XML definition file) 

https://android.googlesource.com/platform/development/+/android-6.0.1_r46/samples/ApiDemos/res/values/attrs.xml

위 설명한 바와 같이 Widget이 어떤 attribute들을 사용할 것인지 정의 합니다.  <declare-styleable> 태그를 사용하며,  name 속성 값에 Java 파일의 클래스 이름을 넣어줍니다. 하위 아이템들로 <attr> 태그를 사용하며 name을 정하고 default 값이나 format을 정의해놓을 수도 있습니다. "[클래스 이름]_[속성 이름]"의 스트링으로 TypedArray에서 값을 가져올 수 있습니다.


3. custom_view_1.xml (XML definition file) 

:https://android.googlesource.com/platform/development/+/android-6.0.1_r46/samples/ApiDemos/res/layout/custom_view_1.xml

1, 2번을 통해 만들어진 Widget을 다른 layout 파일에서 사용한 예시입니다. 


Widget을 왜 사용하나?

Android application 안에서 어떤 UI가 표시되기 위한 각각 요소 단위로서 이해할 수 있습니다. Android를 처음 접할 때 무심코 사용했던 TextView, Button 등의 Widget들이 도대체 어떤 식으로 객체화되어 Java 코드 안에서 동작하는지 이해하여야 나만의 Widget을 정의하고 그릴 수 있을 것입니다. 


'programming > android' 카테고리의 다른 글

ViewGroup  (0) 2018.08.24
View - AppWidget  (0) 2018.08.23
Companion Objects in Kotlin  (0) 2018.04.08
어떤 Context를 써야 하나요?  (0) 2018.03.26
기본적인 메모리 관리  (0) 2018.03.06

Companion Objects in Kotlin



reference: 

https://blog.kotlin-academy.com/a-few-facts-about-companion-objects-37e18429b725





 Static in Kotlin

Kotlin에서는 Java의 static 키워드가 없습니다. Static과 유사하게 사용하기 위해서 아마 많은 분들이 companion object를 사용하고 계실 것 같습니다. 이 포스팅에서는 Java static과 Kotlin companion object와 간략하게 비교해보려 합니다.


 Companion objects

Companion object는 기본적으로 그 클래스 안에 존재하는 singleton 객체입니다. Java로 convert된 결과를 보더라도 companion object는 Companion이라는 이름을 가진 static singleton임을 알 수 있습니다. 이말인 즉, companion이라는 키워드는 사실 Companion이라는 이름을 갖는 클래스의 객체를 가리키는 shortcut 역할을 할 뿐입니다. 그리고 더 나아가 굳이 companion이 아니더라도 static 멤버들을 가질 수 있습니다. 아래 코드는 완벽히 동작합니다.

class TopLevelClass {

companion object {
fun doSomeStuff() {
...
}
}

object FakeCompanion {
fun doOtherStuff() {
...
}
}
}

fun testCompanion() {
TopLevelClass.doSomeStuff()
TopLevelClass.Companion.doSomeStuff()
TopLevelClass.FakeCompanion.doOtherStuff()

}


그러면 위의 companion과 FakeCompanion의 차이는 무엇일까요. 

Java로 decompile을 해보면 약간 다른 점이 있습니다. companion은 부모 클래스의 static 멤버 필드로 가지고 있는 반면, FakeCompanion은 INSTANCE라는 이름으로 본인의 객체 레퍼런스를 내부적으로 갖습니다. 즉, 위의 test코드가 Java에서는 아래와 같이 변형된다는 이야기입니다.

public void testCompanion() {
TopLevelClass.Companion.doSomeStuff();
TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}


 @JvmField, @JvmStatic

Annotation을 한번 살펴봅시다. 

@JvmField는 getter와 setter를 생성하지 않도록 명시하고, 그저 Java의 일반적인 필드로서 사용되도록 합니다. Companion 안에서 사용된다면 companion이 아닌 부모 클래스의 static field가 됩니다. 정리하자면, companion object 안의 일반 적인 멤버는 부모 클래스의 private 멤버가 되며 companion의 getter, setter를 통해서만 접근되고, @JvmField가 붙는다면 부모 클래스의 public 멤버가 되어 외부에서 자유롭게 접근됩니다. 물론 둘다 static인 건 같습니다.

@JvmStatic은 부모 클래스에게 해당 멤버에 대한 접근을 제공하는 용도로 사용됩니다. 특히 조심해야할 점은, companion의 멤버 필드는 부모 클래스의 static 멤버로 선언되지만, 멤버 메소드는 companion 안에서 선언된다는 점입니다. 즉, @JvmStatic을 붙여줘야 외부에서 companion object의 메소드를 접근할 수 있습니다. 예를 들어 Dagger를 사용할 때 provide 메소드에 @JvmStatic을 붙여줘야 하는 이유가 그것입니다. 아래는 companion의 getFoo1() 메소드에 @JvmStatic을 붙여줬을 때 생성되는 부모 메소드입니다. 이게 없다면 외부에서는 아예 접근조차 안되겠네요.

public static final int getFoo1() {
return Companion.getFoo1();
}

 

"object" keyword

object 키워드는 앞서 잠깐 언급된 바와 같이 클래스 안의 singleton 객체로서 동작합니다. 즉  예를 들어 Dagger에서 사용할 모듈은 굳이 companion일 이유가 없습니다.

@Module
object MyModule {

@Provides
@Singleton
@JvmStatic
fun provideSomething(anObject: MyObject): MyInterface {
return myObject
}
}

companion 또한 이름을 가지거나 interface를 구현할 수도 있습니다. 아래는 Kotlin에서 Parcelable을 만드는 전통적인 코드입니다. CREATOR가 companion object네요.

class ParcelableClass() : Parcelable {

constructor(parcel: Parcel) : this()

override fun writeToParcel(parcel: Parcel, flags: Int) {}

override fun describeContents() = 0

companion object CREATOR : Parcelable.Creator<ParcelableClass> {
override fun createFromParcel(parcel: Parcel): ParcelableClass = ParcelableClass(parcel)

override fun newArray(size: Int): Array<ParcelableClass?> = arrayOfNulls(size)
}
}


Conclusion

아무래도 Kotlin은 Java의 static을 별로 좋아하지 않는 느낌입니다. 코드 상으로 느껴지는 점은 static한 것들이 companion안에 다 모여있으니 한눈에 들어오지만.. 뭔가 코드가 더러워보이는 건 Java에 익숙해진 탓인 것 같습니다. Kotlin스럽게 코딩하기 어렵네요.

'programming > android' 카테고리의 다른 글

View - AppWidget  (0) 2018.08.23
View - Widget  (0) 2018.08.23
어떤 Context를 써야 하나요?  (0) 2018.03.26
기본적인 메모리 관리  (0) 2018.03.06
Splash 화면 구성하기  (3) 2018.02.20

+ Recent posts