티스토리 뷰

Android/Widget

[ Widget #1 ] AOS Widget 만들기

녹색꼬맹이 2024. 5. 30. 14:31
반응형
SMALL

 

반응형

 

SMALL

 

 

안드로이드 위젯 관련해서 만들어볼 기회가 생겨서 해보다가 

너무 이해를 못하고 삽질만 하다가 내가 원하는 기능에 도달되서 기록에 남겨 두려 한다

우선 기초적으로 위젯을 만들기 위한 셋팅에 관련된 것부터 시작 한다

 

 

 

이렇게 쉽게 위젯을 셋탕 할 수 있음.

 

다른 옵션들은 모르겠고 그냥 만들었다.

 

 

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>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

???

 

 

 

 

 

 

 

 

 

 

 

반응형
LIST

'Android > Widget' 카테고리의 다른 글

[ Widget #2 ] AOS Widget 에서 Glide 이미지 로딩  (0) 2024.05.30
댓글