flutter 에서 홈 화면 앱 위젯 2개 만드는 방법을 소개합니다.
순서는 아래와 같이 추가해주면 됩니다.
- AndroidManifest.xml 2번째 위젯 추가
- res/xml/widget_info_large.xml 추가
- res/layout/widget_layout_large.xml 추가
- WidgetProviderLarge.kt 추가
안드로이드 위젯 전체 흐름도
프로젝트 구조
.AndroidManifest.xml 2번째 위젯 추가
<!-- 1번 위젯 Your Background receiver and service goes here -->
<receiver android:name="AppWidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
<!-- //1번 위젯 -->
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver" android:exported="true">
<intent-filter>
<action android:name="es.antonborri.home_widget.action.BACKGROUND" />
</intent-filter>
</receiver>
<service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>
<!-- 2번 위젯 -->
<receiver android:name="AppWidgetProviderLarge" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/widget_info_large" />
</receiver>
<!-- //2번 위젯 -->
Android 홈 화면 위젯 (home widget)을 정의하는 데 사용되는 AndroidManifest.xml 파일의 일부입니다.
위 코드는 두 개의 위젯 (1번 위젯 및 2번 위젯)을 정의하고 각 위젯의 설정 및 동작을 정의합니다.
중요하게 봐야할 코드는 <receiver android:name="AppWidgetProvider" 부분과 android:resource="@xml/widget_info" 입니다.
.res/xml/widget_info.xml (1번위젯)
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_layout"
android:minWidth="240dp"
android:minHeight="110dp"
android:minResizeWidth="240dp"
android:minResizeHeight="110dp"
android:widgetCategory="home_screen" />
.res/xml/widget_info_large.xml (2번위젯)
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_layout_large"
android:minWidth="280dp"
android:minHeight="310dp"
android:minResizeWidth="280dp"
android:minResizeHeight="310dp"
android:widgetCategory="home_screen" />
Android 홈 화면 위젯 (home widget)을 정의하는 데 사용되는 XML 요소인 <appwidget-provider>를 정의합니다.
xmlns:android: XML 파일 내에서 Android 네임스페이스를 사용하기 위한 선언입니다.
android:initialLayout: 위젯의 초기 레이아웃 리소스를 지정합니다.
이 리소스는 홈 화면에 위젯이 추가될 때 초기에 표시되는 레이아웃을 정의합니다.
@layout/widget_layout 와 @layout/widget_layout_large는 홈 위젯의 초기 레이아웃 리소스 파일을 가리킵니다.
android:minWidth 및 android:minHeight: 위젯의 최소 너비와 높이를 정의합니다.
홈 화면에서 위젯의 크기를 조절할 때 최소 크기가 이러한 값보다 작아지지 않습니다.
android:minResizeWidth 및 android:minResizeHeight: 위젯의 크기를 변경할 때 최소 크기를 정의합니다.
사용자가 위젯 크기를 조절할 때 최소 크기가 이러한 값보다 작아지지 않습니다.
android:widgetCategory: 위젯이 어떤 카테고리에 속하는지를 정의합니다.
이 속성은 홈 화면에서 위젯을 그룹화하거나 필터링하는 데 사용됩니다.
"home_screen"은 홈 화면 위젯을 의미합니다.
.res/layout/widget_layout.xml (1번위젯)
<LinearLayout
android:id="@+id/widget_root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="240dp"
android:layout_height="110dp"
android:background="#1f303d"
android:orientation="vertical">
<TextView
android:id="@+id/tv_prdcd_sell"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="--"
android:textColor="@android:color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_prdcd_buy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="--"
android:textColor="@android:color/white"
android:textSize="14sp" />
<Button
android:id="@+id/bt_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="환율 업데이트"
android:textColor="@android:color/holo_blue_dark"
android:textSize="12sp" />
</LinearLayout>
.res/layout/widget_layout_large.xml (2번위젯)
<LinearLayout
android:id="@+id/widget_root_large"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="280dp"
android:layout_height="310dp"
android:background="#1f303d"
android:orientation="vertical">
<TextView
android:id="@+id/tv_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:gravity="center_horizontal"
android:padding="12dp"
android:text="--"
android:textColor="@android:color/white"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_prdcd_sell"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="12dp"
android:text="--"
android:textColor="@android:color/white"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_prdcd_buy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="12dp"
android:text="--"
android:textColor="@android:color/white"
android:textSize="16sp" />
<Button
android:id="@+id/bt_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="환율 업데이트 라지"
android:textColor="@android:color/holo_blue_dark"
android:textSize="16sp" />
</LinearLayout>
위 코드는 Android 위젯의 레이아웃을 정의하는 XML 레이아웃 파일입니다.
이 레이아웃은 위젯이 홈 화면에 어떻게 표시될지를 결정합니다.
<LinearLayout>: 위젯의 루트 컨테이너입니다.
android:id 속성을 사용하여 이 레이아웃을 식별할 수 있습니다.
위 코드는 android:id="@+id/widget_root" 와 android:id="@+id/widget_root_large" 로 식별합니다.
위 식별된 코드는 WidgetProvider.kt WidgetProviderLarge.kt 에서 식별합니다.
android:layout_width 및 android:layout_height: 이 레이아웃의 너비와 높이를 정의합니다.
android:background: 레이아웃의 배경 색상 또는 배경 이미지를 정의합니다.
여기서는 #1f303d로 지정하여 어두운 파란색 배경을 나타냅니다.
android:orientation: vertical로 설정하여 하위 뷰들이 세로 방향으로 배치되도록 합니다.
아래에 있는 하위 뷰들은 이 LinearLayout에 포함됩니다:
<TextView>: 텍스트를 표시하는 위젯입니다.
여기에서는 tv_prdcd_sell 및 tv_prdcd_buy로 두 개의 텍스트 뷰가 정의됩니다.
android:id: 각 텍스트 뷰를 식별하는 고유한 ID를 부여합니다.
android:layout_width 및 android:layout_height: 텍스트 뷰의 너비와 높이를 정의합니다.
android:gravity: 텍스트의 수평 정렬 방향을 정의합니다. center_horizontal로 설정하여 텍스트가 가운데 정렬됩니다.
android:padding: 텍스트 주위의 여백을 정의합니다.
android:text: 초기 텍스트 내용을 정의합니다.
android:textColor: 텍스트의 글꼴 색상을 정의합니다.
android:textSize: 텍스트의 글꼴 크기를 정의합니다.
<Button>: 버튼을 표시하는 위젯입니다. 여기에서는 "환율 업데이트"라는 버튼이 정의됩니다.
android:id: 버튼을 식별하는 고유한 ID를 부여합니다.
android:layout_width 및 android:layout_height: 버튼의 너비와 높이를 정의합니다.
android:text: 버튼에 표시되는 텍스트를 정의합니다.
android:textColor: 버튼 텍스트의 글꼴 색상을 정의합니다.
android:textSize: 버튼 텍스트의 글꼴 크기를 정의합니다.
이 레이아웃은 홈 화면 위젯의 모양과 디자인을 결정하며, 여기에서 정의된 뷰들은 위젯이 화면에 어떻게 표시될지를 결정합니다.
.WidgetProvider.kt (1번위젯)
package com.coforward.webview_flutter_onlyweb_kt
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
class AppWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
// Open App on Widget Click
val pendingIntent = HomeWidgetLaunchIntent.getActivity(context,
MainActivity::class.java)
setOnClickPendingIntent(R.id.widget_root, pendingIntent)
val prdcdSellString = widgetData.getString("_prdcdSell", "0")
val prdcdBuyString = widgetData.getString("_prdcdBuy", "0")
val counterTextSell = "USD/KRW 팔때 $prdcdSellString"
val counterTextBuy = "USD/KRW 살때 $prdcdBuyString"
setTextViewText(R.id.tv_prdcd_sell, counterTextSell)
setTextViewText(R.id.tv_prdcd_buy, counterTextBuy)
// Pending intent to update counter on button click
val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context,
Uri.parse("myAppWidget://updatecounter"))
setOnClickPendingIntent(R.id.bt_update, backgroundIntent)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
.WidgetProviderLarge.kt (2번위젯)
package com.coforward.webview_flutter_onlyweb_kt
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
class AppWidgetProviderLarge : HomeWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.widget_layout_large).apply {
// Open App on Widget Click
val pendingIntent = HomeWidgetLaunchIntent.getActivity(context,
MainActivity::class.java)
setOnClickPendingIntent(R.id.widget_root_large, pendingIntent)
val counter = widgetData.getInt("_counter", 0)
val prdcdSellString = widgetData.getString("_prdcdSell", "0")
val prdcdBuyString = widgetData.getString("_prdcdBuy", "0")
var counterText = "현재 카운터 : $counter"
val counterTextSell = "USD/KRW 팔때 $prdcdSellString"
val counterTextBuy = "USD/KRW 살때 $prdcdBuyString"
setTextViewText(R.id.tv_counter, counterText)
setTextViewText(R.id.tv_prdcd_sell, counterTextSell)
setTextViewText(R.id.tv_prdcd_buy, counterTextBuy)
// Pending intent to update counter on button click
val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context,
Uri.parse("myAppWidget://updatecounterlarge"))
setOnClickPendingIntent(R.id.bt_update, backgroundIntent)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
위 코드는 안드로이드 홈 화면에서 동작하는 위젯을 만들기 위한 코드입니다.
AppWidgetProvider 클래스: HomeWidgetProvider 클래스를 상속하여 위젯을 정의합니다.
1번 위젯 class AppWidgetProvider : HomeWidgetProvider()
2번 위젯 class AppWidgetProviderLarge : HomeWidgetProvider()
onUpdate 메소드: 위젯이 업데이트될 때 실행되는 메소드로, 홈 화면에서 위젯이 업데이트될 때 호출됩니다.
이 메소드에서 위젯의 내용을 업데이트합니다.
appWidgetIds: appWidgetManager에 의해 제공된 위젯 ID 목록입니다.
위젯 ID 목록을 반복하며 각 위젯에 대해 동일한 동작을 수행하게 됩니다.
RemoteViews: 홈 화면에서 위젯을 표시하기 위한 뷰 그룹을 생성합니다.
setOnClickPendingIntent: HomeWidgetLaunchIntent 클래스를 사용하여 홈 화면에서 위젯을 클릭했을 때 실행할 액티비티를 지정합니다.
R.id.widget_root는 위젯의 루트 뷰입니다. res/xml/widget_info.xml 과 연결 되어 있습니다.
1번 위젯 setOnClickPendingIntent(R.id.widget_root, pendingIntent)
2번 위젯 setOnClickPendingIntent(R.id.widget_rootLarge, pendingIntent)
widgetData: SharedPreferences를 사용하여 데이터를 가져옵니다.
widgetData.getString("_prdcdSell", "0")는 "_prdcdSell" 키에 해당하는 값을 가져옵니다.
만약 값이 없을 경우 기본값으로 "0"을 사용합니다.
setTextViewText: RemoteViews를 사용하여 위젯의 텍스트 뷰의 내용을 설정합니다.
위젯의 "tv_prdcd_sell" 및 "tv_prdcd_buy" 텍스트 뷰에 환율 정보를 설정합니다.
HomeWidgetBackgroundIntent: 위젯을 클릭할 때 실행할 백그라운드 인텐트를 설정합니다.
이것을 사용하여 위젯 버튼 클릭에 대한 백그라운드 작업을 처리할 수 있습니다.
appWidgetManager.updateAppWidget(widgetId, views): 위젯의 내용을 업데이트하고 홈 화면에 반영합니다.
즉, 위 코드는 홈 화면에서 동작하는 위젯을 생성하고, 위젯을 클릭하면 액티비티가 실행되며, 위젯에 환율 정보를 표시하도록 설정하는 기능을 수행합니다.
아래코드는 업데이트 버튼 클릭할때 dart 와 연동하는 코드 입니다.
1번 위젯에서 업데이트 버튼 클릭
Uri.parse("myAppWidget://updatecounter"))
2번 위젯에서 업데이트 버튼 클릭
Uri.parse("myAppWidget://updatecounterlarge"))
main.dart
Future<void> main() async {
// 앱 초기화 로직 및 설정
await initialization(null);
// Dynatrace 초기화
Dynatrace().startWithoutWidget();
// 홈위젯 사용
WidgetsFlutterBinding.ensureInitialized();
await HomeWidget.registerBackgroundCallback(backgroundCallback);
runApp(
const MaterialApp(
home: WebViewApp(),
),
);
}
....
....
Future<void> backgroundCallback(Uri? uri) async {
print(">>>>>backgroundCallback");
print(uri?.host);
// 1번 위젯
if (uri?.host == 'updatecounter') {
// 스타루트 페이지 가지고오 C110816
String html = await fetchPrdCd();
// fetch page 를 분석
Map<String, dynamic> parsedPrdcdJson = fetchSplit(html);
var prdcdSell = parsedPrdcdJson['USDKRW']['sell'];
var prdcdBuy = parsedPrdcdJson['USDKRW']['buy'];
print("fetch 숫자 Sell: $prdcdSell");
print("fetch 숫자 Buy : $prdcdBuy");
int _counter = 0;
await HomeWidget.getWidgetData<int>('_counter', defaultValue: 0)
.then((int? value) {
_counter = value ?? 0;
_counter++;
});
await HomeWidget.saveWidgetData<String>('_prdcdSell', prdcdSell);
await HomeWidget.saveWidgetData<String>('_prdcdBuy', prdcdBuy);
await HomeWidget.saveWidgetData<int>('_counter', _counter);
await HomeWidget.updateWidget(name: 'AppWidgetProvider', iOSName: 'AppWidgetProvider');
}
else if (uri?.host == 'updatecounterlarge') {
// 스타루트 페이지 가지고오 C110816
String html = await fetchPrdCd();
// fetch page 를 분석
Map<String, dynamic> parsedPrdcdJson = fetchSplit(html);
var prdcdSell = parsedPrdcdJson['USDKRW']['sell'];
var prdcdBuy = parsedPrdcdJson['USDKRW']['buy'];
print("fetch 숫자 Sell: $prdcdSell");
print("fetch 숫자 Buy : $prdcdBuy");
int _counter = 0;
await HomeWidget.getWidgetData<int>('_counter', defaultValue: 0)
.then((int? value) {
_counter = value ?? 0;
_counter++;
});
await HomeWidget.saveWidgetData<String>('_prdcdSell', prdcdSell);
await HomeWidget.saveWidgetData<String>('_prdcdBuy', prdcdBuy);
await HomeWidget.saveWidgetData<int>('_counter', _counter);
await HomeWidget.updateWidget(name: 'AppWidgetProviderLarge', iOSName: 'AppWidgetProviderLarge');
}
}
/**
* 환율 조회 page fetch
*/
Future<String> fetchPrdCd() async {
final response = await http.get(Uri.parse('http://abcd.com/11'));
if(response.statusCode == 200) {
return response.body;
} else {
throw Exception("Failed to fetchPrdCd");
}
}
/**
* 환율 조회 page fetch html split
*/
Map<String, dynamic> fetchSplit(String html) {
List<String> result = html.split('---kb_fx_auth_info---');
Map<String, dynamic> parsedPrdcdJson = jsonDecode(result[1].trim());
return parsedPrdcdJson;
}
이 코드는 백그라운드 작업을 처리하는 backgroundCallback 함수입니다.
이 함수는 uri 매개변수를 통해 백그라운드 작업의 종류를 확인하고 해당 작업을 수행합니다.
if (uri?.host == 'updatecounter'): 이 부분은 URI의 호스트(host)가 'updatecounter'인 경우를 처리합니다.
즉, 1번 위젯의 업데이트를 수행하는 부분입니다.
fetchPrdCd(): 환율 정보를 가져오기 위한 함수입니다.
HTTP GET 요청을 통해 환율 정보가 있는 웹 페이지를 가져옵니다.
fetchSplit(html): 가져온 웹 페이지의 내용을 분석하여 환율 정보를 추출하는 함수입니다.
이 함수는 JSON 형식의 데이터를 파싱하여 환율 정보를 얻습니다.
HomeWidget.getWidgetData<int>('_counter', defaultValue: 0): '_counter' 키에 저장된 정수 값을 가져오는 코드입니다.
만약 해당 값이 없으면 기본값으로 0을 사용합니다.
HomeWidget.saveWidgetData<String>('_prdcdSell', prdcdSell): '_prdcdSell' 키에 prdcdSell 값을 문자열 형태로 저장합니다.
이렇게 저장된 데이터는 위젯에서 사용됩니다.
HomeWidget.updateWidget(name: 'AppWidgetProvider', iOSName: 'AppWidgetProvider'): 업데이트된 데이터를 1번 위젯에 적용하도록 하는 코드입니다.
'name' 및 'iOSName'은 위젯의 이름을 지정하는 부분입니다.
위 코드는 1번 위젯과 2번 위젯 각각의 업데이트를 처리하며, 각각의 환율 정보를 가져와 위젯 데이터에 저장하고, 해당 데이터를 홈 화면에 업데이트하는 역할을 수행합니다.
참고
https://stackoverflow.com/questions/2570004/how-to-add-multiple-widgets-in-the-same-app
'프로그래밍 > flutter' 카테고리의 다른 글
[flutter] home_widget 0.4.0 이용하여 iOS 홈위젯 생성 방법 (1) | 2023.12.07 |
---|---|
[flutter] Flutter에서 홈 화면 앱 위젯 웹뷰 메뉴 바로가기 방법 (안드로이드) (0) | 2023.11.14 |
[flutter] 인터넷에서 데이터 가져오기 GET/POST (fetch) (0) | 2023.10.25 |
[flutter] Flutter에서 홈 화면 앱 위젯을 만드는 방법 1 (안드로이드) (1) | 2023.10.24 |
[flutter] 안드로이드 apk 만들기 (0) | 2023.10.19 |