티스토리 뷰
안드로이드 위젯 관련해서 만들어볼 기회가 생겨서 해보다가
너무 이해를 못하고 삽질만 하다가 내가 원하는 기능에 도달되서 기록에 남겨 두려 한다
우선 기초적으로 위젯을 만들기 위한 셋팅에 관련된 것부터 시작 한다
App Widget 으로 만들면 파일이 여러개 생성 되면서 Manifest 파일에 브로드캐스트 도 같이 생성되어 위젯이 알아서 셋팅되어 진다
우선 이건 Layout 은 고려되지 않은 상태로 어떻게 위젯을 업데이트 하는가에 대해서만 얘기할 예정
코드 아래 내용이 없으면 그냥 참고만 하길.. 완벽하게 이해 한게 아니라..
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_widget_description"
android:initialKeyguardLayout="@layout/new_app_widget"
android:initialLayout="@layout/new_app_widget"
android:minWidth="160dp"
android:minHeight="200dp"
android:previewImage="@drawable/example_appwidget_preview"
android:previewLayout="@layout/new_app_widget"
android:resizeMode="horizontal|vertical"
android:targetCellWidth="1"
android:targetCellHeight="1"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen" />
class NewAppWidget : AppWidgetProvider() {
companion object {
const val ACTION_WIDGET_REFRESH = "action_widget_refresh"
}
private val job = SupervisorJob()
private val coroutineScope = CoroutineScope(Dispatchers.Main + job)
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
val widgetImg = ConfigModule(context).widgetImg
if (widgetImg.isEmpty()) {
Log.d("NewAppWidget", "onUpdate > widgetImg is Empty")
for (appWidgetId in appWidgetIds) loadingWidget(context, appWidgetManager, appWidgetId)
enqueueWork(context)
} else {
Log.d("NewAppWidget", "onUpdate > widgetImg Update")
for (appWidgetId in appWidgetIds) {
updateLoadImgWidget(context, appWidgetManager, appWidgetId, widgetImg)
}
}
}
override fun onEnabled(context: Context) {}
override fun onDisabled(context: Context) {
job.cancel()
}
override fun onReceive(context: Context, intent: Intent) {
Log.d("NewAppWidget", "onReceive > ${intent.action}")
val appWidgetManager = AppWidgetManager.getInstance(context)
val thisWidget = ComponentName(context, NewAppWidget::class.java)
val appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
when (intent.action) {
ACTION_WIDGET_REFRESH -> {
ConfigModule(context).widgetImg = ""
onUpdate(context, appWidgetManager, appWidgetIds)
}
}
super.onReceive(context, intent)
}
private fun enqueueWork(context: Context) {
/*val inputData = Data.Builder()
.putInt("id", appWidgetId)
.build()*/
val workRequest = OneTimeWorkRequestBuilder<WidgetWork>()
//.setInputData(inputData)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
private fun loadingWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int,) {
val views = RemoteViews(context.packageName, R.layout.new_app_widget)
views.setViewVisibility(R.id.img, View.INVISIBLE)
views.setViewVisibility(R.id.updateTime, View.INVISIBLE)
views.setViewVisibility(R.id.loading, View.VISIBLE)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
private fun updateLoadImgWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
widgetImg: String
) {
Log.d("NewAppWidget", "updateLoadImgWidget > imgURl: ${widgetImg}")
val views = RemoteViews(context.packageName, R.layout.new_app_widget)
views.setTextViewText(R.id.updateTime, getToday())
val target = AppWidgetTarget(context, R.id.img, views, appWidgetId)
if (widgetImg.isEmpty()) {
views.setImageViewResource(R.id.img, R.drawable.ic_launcher_foreground)
} else {
Glide.with(context)
.asBitmap()
.load(widgetImg)
.override(160.toPx, 160.toPx)
.placeholder(R.drawable.ic_launcher_foreground)
.error(R.drawable.ic_launcher_foreground)
.into(target)
}
views.setOnClickPendingIntent(R.id.update, getPendingWorkSelfIntent(context, intArrayOf(appWidgetId)))
views.setOnClickPendingIntent(R.id.appOpen, getPendingOpenIntent(context, appWidgetId))
appWidgetManager.updateAppWidget(appWidgetId, views)
}
private fun getPendingWorkSelfIntent(context: Context, widgetIds: IntArray): PendingIntent {
val intent = Intent(context, NewAppWidget::class.java).apply {
action = ACTION_WIDGET_REFRESH
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds)
}
return PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
private fun getPendingOpenIntent(context: Context, targetId: Int): PendingIntent =
PendingIntent.getActivity(
context,
targetId,
Intent(context, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
우선 코드를 보면 핵심은 onUpdate 와 onRecevie 이 두가지다..
내가 여기서 햇갈렸던건
onRecevie 는 어떤 Actiond을 받아서 UI를 업데이트 하면 되겠구나 라고 생각했는데 그게 아니더라..
온전히 onUpdate 메소드 안에서만 UI를 업데이트 한다고 생각하고 접근 해야 한다
onReceive는 event형태와 데이터를 다룬다고 생각하자.
onReceive 에서 이벤트를 브로드 캐스트로 전달 받는다 ( getPendingWorkSelfIntent 메소드 부분 )
그럼 이 부분으로이 ACTION_WIDGET_REFRESH 를 onReceive로 들어온다
그럼 여기서 데이터를 처리하자
나는 이미지를 바꿀 예정이기 때문에
로컬 저장소에 저장해둔 이미지를 지워서 update를 던진다
그럼 onUpdate에서 이미지가 없으니 통신을 시작하고 loading 이미지로 바꿔둔다
통신이 끝난 후에 다시 브로드캐스트로 (workManager -> newAppWidget ) 던진다
이때 사용한 Action은
AppWidgetManager.ACTION_APPWIDGET_UPDATE
OnReveive에서 해당 ACtion 이 들어오면 자동으로 onUpdate를 태우게 된다
그럼 통신 후에 로컬 저장소에 이미지를 저장해 뒀으니, 이미지를 로드 하기 시작한다 ..
이 삽질만 3일걸렸다 .. 도무지 이해가 안됬는데 갑자기 이해가 되기 시작하면서 기록하게 된다 ( 무서운 코딩 )
onReceive 에서는 UI 업테이트 금지다.......
위젯 생성됨 -> onReceive -> ACTION_APPWIDGET_UPDATE 들어옴 ->
처음엔 저장소에 이미지가 없으므로 통신 시작 및 로닝 이미지그림 -> 통신끝 이미지 로컬저장소에 저장 후 브로드캐스트로 전달 ( ACTION_APPWIDGET_UPDATE )
-> onUpdate -> 저장소에 이미지가 있으니 이미지 로드
class WidgetWork(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val randomIndex = getRandomIndex()
val imgList = listOf(
"https://picsum.photos/id/60/160/160",
"https://picsum.photos/id/61/160/160",
"https://picsum.photos/id/69/160/160",
"https://picsum.photos/id/71/160/160",
"https://picsum.photos/id/74/160/160",
"https://picsum.photos/id/77/160/160",
"https://picsum.photos/id/81/160/160",
"https://picsum.photos/id/84/160/160",
"https://picsum.photos/id/90/160/160",
"https://picsum.photos/id/101/160/160",
"https://picsum.photos/id/102/160/160",
"https://picsum.photos/id/103/160/160",
"https://picsum.photos/id/104/160/160",
)
val imgUrl = imgList.getRandomItem()
Log.d("WidgetWork", "imgURL > ${imgUrl}")
delay(1000L)
val appWidgetManager = AppWidgetManager.getInstance(context)
val thisWidget = ComponentName(context, NewAppWidget::class.java)
val appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
ConfigModule(context).widgetImg = imgUrl
context.sendBroadcast(Intent(context, NewAppWidget::class.java).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
})
return Result.success()
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppWidgetExample3"
tools:targetApi="31">
<receiver
android:name=".NewAppWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/new_app_widget_info" />
</receiver>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.AppWidgetExample3.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/Theme.AppWidgetExample3.AppWidgetContainer">
<TextView
android:id="@+id/updateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dp"
android:layout_above="@id/img"
android:layout_centerHorizontal="true"
android:layout_marginBottom="12dp"
tools:text="24.05.30 13:50"/>
<ImageView
android:id="@+id/img"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_centerInParent="true"
tools:src="@drawable/ic_launcher_foreground"/>
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
<Button
android:id="@+id/update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="이미지 바꾸기"
android:layout_centerHorizontal="true"
android:layout_below="@id/img"/>
<Button
android:id="@+id/appOpen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="앱 열기"
android:layout_marginTop="12dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/update"/>
</RelativeLayout>
???
'Android > Widget' 카테고리의 다른 글
[ Widget #2 ] AOS Widget 에서 Glide 이미지 로딩 (0) | 2024.05.30 |
---|
- Total
- Today
- Yesterday
- 웨어러블
- 쏘렌토 하이브리드
- widget onreceive
- 가중치랜덤
- Fragment data
- 풀배열 키보드
- android widget 만들기
- 랜덤컬러
- 안드로이드 폴드 대응
- aos img url bitmap
- widget 이미지 로딩
- 갤럭시 워치
- Android Wear Os
- flutter web
- 쏘렌토 하이브리드 시그니처
- 역시 키크론이네
- 프래그먼트 데이터 저장
- 안드로이드 플립 대응
- widget glide
- 폴더블 상태 유지
- widget 만들기
- wear os
- widget onupdate
- Dot indicator
- Foldable Event
- Android
- 폰과 워치 페어링
- 맥 전용
- 폴더블 이벤트 받기
- RandomColor
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |