Android Widget - 위젯 생성
위젯
홈 화면에서 쉽게 정보를 제공하거나 어플리케이션을 제어할 수 있는 기능이다. 사용자가 쉽게 접근할 수 있지만 어플리케이션이 아닌 홈 화면에서 제공받기 때문에 제약이 따른다.
기본사항
AppWidgetProviderInfo
레이아웃, 업데이트 빈도, 크기 등 위젯에 대한 메타 데이터를 XML로 정의한다.
메터 데이터 설명 (링크)
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_covid"
android:configure="com.taetae98.widget.ui.activity.CovidWidgetConfigureActivity"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="360000"
android:minWidth="220dp"
android:minHeight="146dp" />
AppWidgetProvider
BroadcastReceiver를 상속 받았으며 이벤트 형식으로 위젯을 관리할 수 있다.
@AndroidEntryPoint
class CovidWidgetProvider : AppWidgetProvider() {
@Inject
lateinit var covidStatusManager: CovidStatusManager
@Inject
lateinit var covidWidgetRepository: CovidWidgetRepository
companion object {
fun getLargeRemoteViews(context: Context, covidWidget: CovidWidget, covidStatusResponse: CovidStatusResponse): RemoteViews {
val format = NumberFormat.getInstance()
return RemoteViews(context.packageName, R.layout.widget_covid).apply {
setInt(R.id.layout, "setBackgroundColor", covidWidget.backgroundColor)
setTextColor(R.id.city_text_view, covidWidget.textColor)
setTextColor(R.id.total_title_text_view, covidWidget.textColor)
setTextColor(R.id.positive_title_text_view, covidWidget.textColor)
setTextColor(R.id.death_title_text_view, covidWidget.textColor)
setTextColor(R.id.total_text_view, covidWidget.textColor)
setTextColor(R.id.positive_text_view, covidWidget.textColor)
setTextColor(R.id.death_text_view, covidWidget.textColor)
setTextViewText(R.id.city_text_view, covidWidget.city)
setTextViewText(R.id.total_text_view, "${format.format(covidStatusResponse.body[covidWidget.city]?.totalPositive)}명")
setTextViewText(R.id.positive_text_view, "${format.format(covidStatusResponse.body[covidWidget.city]?.positive)}명")
setTextViewText(R.id.death_text_view, "${format.format(covidStatusResponse.body[covidWidget.city]?.death)}명")
}
}
private fun updateWidget(context: Context, covidWidget: CovidWidget, covidStatusResponse: CovidStatusResponse) {
AppWidgetManager.getInstance(context).updateAppWidget(
covidWidget.id, getLargeRemoteViews(context, covidWidget, covidStatusResponse)
)
}
suspend fun updateWidget(context: Context, appWidgetIds: IntArray, covidStatusResponse: CovidStatusResponse) {
appWidgetIds.forEach { id ->
AppDatabase.getInstance(context).covidWidgetDao().findById(id)?.let { widget ->
updateWidget(context, widget, covidStatusResponse)
}
}
}
}
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
Logger.intent("Widget onReceive", intent)
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
super.onDeleted(context, appWidgetIds)
CoroutineScope(Dispatchers.IO).launch {
covidWidgetRepository.deleteByIds(appWidgetIds)
}
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
covidStatusManager.onLatestCovidStatus().enqueue(
object : Callback<CovidStatusResponse> {
override fun onResponse(call: Call<CovidStatusResponse>, response: Response<CovidStatusResponse>) {
Logger.response("Widget onReceive", response)
val covidStatusResponse = response.body() ?: return
CoroutineScope(Dispatchers.IO).launch {
updateWidget(context, appWidgetIds, covidStatusResponse)
}
}
override fun onFailure(call: Call<CovidStatusResponse>, t: Throwable) {
Logger.e("Widget onReceive", t)
}
}
)
}
}
Layout
위젯에 대한 레이아웃을 설정한다. 위젯은 홈 화면에서 제공하기 때문에 RemoteViews로 관리되는데 RemoteViews에 사용할 수 있는 View가 제한적이다.
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:rowCount="3"
android:columnCount="3"
android:background="#80000000"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/city_text_view"
android:text="도시"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_columnSpan="3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/total_title_text_view"
android:text="총\n확진자"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:textColor="@color/white"/>
<TextView
android:id="@+id/positive_title_text_view"
android:text="일일\n확진자"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:textColor="@color/white"/>
<TextView
android:id="@+id/death_title_text_view"
android:text="사망자"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:textColor="@color/white"/>
<TextView
android:id="@+id/total_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:gravity="center"
android:text="0"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/positive_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:gravity="center"
android:text="0"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/death_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:gravity="center"
android:text="0"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
</GridLayout>
Manifest
AppWidgetProvider가 BroadcastReceiver 기반이기 때문에 Manifest에 등록해야 하며, AppWidgetProvider와 AppWidgetProviderInfo를 Manifest에서 연결합니다.
exported : 위젯이 홈 화면을 통해 제공되므로 true로 설정
APPWIDGET_UPDATE : 위젯 업데이트 Action을 수신하기 위해 설정
meta-data : AppWidgetProviderInfo와 연결
<receiver
android:name=".widget.CovidWidgetProvider"
android:exported="true"
android:label="@string/covid_widget" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/provider_covid_widget" />
</receiver>
Git (예제 코드)
https://github.com/KangTaeJong98/TaeTae98/tree/main/Android/Widget
GitHub - KangTaeJong98/TaeTae98: Study Repository
Study Repository. Contribute to KangTaeJong98/TaeTae98 development by creating an account on GitHub.
github.com