본문 바로가기
개발/Android

[Android] 런처앱(Launcher app)에서 Widget 생성하기

by tempus 2022. 9. 1.
반응형

Widget을 사용하기 위해 알아야 하는 기본  클래스

1) AppWidgetHost

홈 화면과 같이 UI에 앱 위젯을 삽입하고자 하는 AppWidget 서비스와의 상호작용을 제공하는 클래스이다.

2) AppWidgetHostView

위젯이 표시되어야 할 때마다 래핑되는 프레임입니다. 

3) AppWidgetManger

AppWidget의 상태를 업데이트하고 설치된 AppWidget provider에 대한 정보와 그 위젯과 연관된 여러 가지 상태에 대한 정보를 얻을 수 있는 클래스이다.

 

Launcher 앱에 등록된 위젯 생성 및 관리

위젯을 생성하기 위해서는 다음과 같은 과정으로 이루어집니다.

  1. 현재 Launcer 앱에 있는 모든 위젯 가져오기
  2. 해당 widget의 packageName, className을 가지고 ComponetName() 생성
  3. AppWidgetHost로 id 할당 (allocateAppWidgetId())
  4. AppWidgetManager를 통해서 id와 Widget을 bind (앱에 위젯을 추가할 수 있는 권한이 있는지 테스트)
  5. 만약 bind 되지 않았다면 intent를 통해 Activity에게 전달하여 모든 앱 위젯을 추가할 수있는 허용 부여하도록 대화상자 요청 표시 이후 다시 bind
  6. widget을 보여주기 위한 hostview 생성
  7. hostview에 widget을 set 해주기
  8. FrameLayout에 해당 hostView를 add 해주기

 

전체 코드

다음 코드는 Android 4.1 이상에서 App Widget을 바인딩하는 코드입니다.

🍎 모든 위젯을 가져오기 위한 코드

val allWidgets : List<AppWidgetProviderInfo> = appWidgetManager.installedProviders

 

🍎 위젯을 생성하고  hostView를 return하는 createWidget 함수

//host와 manager는 applicationContext로 만들기를 권장한다. 
val appWidgetManager = AppWidgetManager.getInstance(applicationContext)
val appWidgetHost = AppWidgetHost(applicationcontext, 고유ID)

private fun createWidget(context: Context, name: String): AppWidgetHostView {
        
        //name을 통해서 원하는 WidgetApp의 AppProviderInfo를 가져온다.
        val widgetProviderInfo = getWidgetProviderInfo(name)
		
        //ComponentName 생성
        val comp = ComponentName(
            widgetProviderInfo.provider.packageName,
            widgetProviderInfo.provider.className
        )

        //고유Id를 AppWidgetHost로 부터 할당 받는다.
        val widgetId = appWidgetHost.allocateAppWidgetId()
        //해당 id를 특정 위젯의 AppWidgetProvider와 host랑 바인딩한다. 
        val allow = appWidgetManager.bindAppWidgetIdIfAllowed(widgetId, comp)

        //바인딩이 안될 시 앱에서 사용자에게 권한을 부여하도록 요청하는 대화상자를 나타내준다.
        if (!allow) {
            val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND)
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
            intent.putExtra(
                AppWidgetManager.EXTRA_APPWIDGET_PROVIDER,
                widgetProviderInfo.provider
            )
            (context as Activity).startActivityForResult(intent, REQUEST_BIND_WIDGET)
            //이후 다시 바인딩을 해준다.
            appWidgetManager.bindAppWidgetIdIfAllowed(widgetId, comp)
        }

        //앱 위젯을 그려주기 위한 호스트 뷰를 생성한다.
        val hostView =
            appWidgetHost.createView(context.applicationContext, widgetId, widgetProviderInfo)
        //해당 앱의 id와 AppWidgetProviderInfo를 set해줍니다.
        hostView.setAppWidget(widgetId, widgetProviderInfo)

        return hostView
    }

 이렇게 만들어진 hostView를 이제 FrameLayout을 통해서 add 해주면 됩니다.

🍎 ViewModel에서 코드

private val _clockWidget: MutableLiveData<AppWidgetHostView?> = MutableLiveData()
val clockWidget : LiveData<AppWidgetHostView?> = _clockWidget

/*...*/

fun setClockWidget(context : Context, name : String){
	_clockWidget.value = createWidget(context, name)
}

  

🍎 BindingAdapter

@BindingAdapter("app:addWidget")
@JvmStatic
fun addWidget(view: FrameLayout, hostView: AppWidgetHostView) {
      //디폴트 패딩 없애주기
      hostView.setPadding(0, 0, 0, 0)
      view.addView(hostView)
 }

 

🍎 FrameLayout

<FrameLayout
  android:id="@+id/clock_widget"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginStart="@dimen/clock_widget_start_margin"
  android:layout_marginTop="@dimen/clock_widget_top_margin"
  app:addWidget="@{viewModel.clockWidget}"
  app:layout_constraintEnd_toStartOf="@+id/side_widget_section"
  app:layout_constraintHorizontal_bias="0.0"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent" />

하지만 이렇게 하면 아마 위젯이 보이기는 하는데 변경(update)이 제대로 동작을 하지 않을 것입니다. 그러기 위해서는 appWidgetHost가 appWidget이  Change 되었을 때를 받을 수 있게 Listener를 등록해주어야 합니다. 앞서 전체적인 LifeCycle에서 host와 manger를 관리하기 위해서 꼭 만들 때 application context를 사용해주어야 합니다. 

//Activity의 onStart()에서 호출
fun startAppWidgetHost() {
    appWidgetHost.startListening()
}

//Activit의 onStop()에서 호출
fun stopAppWidgetHost() {
    appWidgetHost.startListening()
}

 

참고

 

반응형

댓글


loading