라디오 앱 유저들이 홈 화면에서 라디오를 컨트롤 할 수 있는 위젯을 만들어 달라는 요청이 있어서 이번 기회에 위젯에 관련된 내용들을 정리하고자 한다.
1. 위젯 유형
위젯을 만들때 어떤 종류의 위젯을 만들지 생각해봐야 한다.
크게 위젯은 네가지로 분류할 수 있다.
- 정보 위젯
일반적으로 정보 위젯은 사용자에게 시간에 따른 정보를 알려줄 때 사용한다.
보통 해당 위젯을 터치했을 때 연결된 앱으로 이동된다.
대표적인 정보 위젯은 시계 위젯, 날씨 위젯이다.
- 컬렉션 위젯
컬렉션 위젯은 동일한 유형의 여러 요소를 표시한다. 보통 컬렉션의 요소들을 사용하기 위해 세부 정보 뷰로 열기 기능을 포함하고 세로로 스크롤할 수 있다.
- 관리 위젯
관리 위젯의 기본 목적은 사용자가 앱을 먼저 열 필요 없이 홈 화면에서 바로 트리거할 수 있는 함수를 표시하는 것이다.
관리 위젯의 일반적인 예는 사용자가 실제 음악 앱 외부에서 음악 트랙을 재생, 일시중지 또는 건너뛸 수 있는 음악 앱 위젯이다.
- 하이브리드 위젯
다양한 유형의 요소를 결합한 위젯이다. 음악 플레이어 위젯은 기본적으로 관리 위젯이라고 볼 수 있지만 현재 재생중인 노래 제목과 같이 정보를 알려주어야 하는 경우 관리 위젯 + 정보 위젯의 성격을 가지고 있다.
따라서 라디오 플레이어 위젯은 하이브리드 위젯이라고 볼 수 있다.
2. 위젯 제한사항
위젯은 홈 화면에서 사용하기 때문에 디자인과 기능에 대한 제약이 있다.
- 기능 제한
위젯에 사용할 수 있는 유일한 기능은 터치 및 수직 스와이프이다. 가로 스와이프 같은 경우 홈 화면에서 이미 사용하고 있기 때문에 막아놓은 것으로 보인다.
- 레이아웃 제한
안드로이드 위젯은 RemoteViews 객체를 기반으로 하기 때문에 사용할 수 있는 뷰 객체들이 제한되어 있다.
사용할 수 있는 뷰 객체들은 다음과 같다.
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
- AnalogClock
- Button
- Chronometer
- ImageButton
- ImageView
- ProgressBar
- TextView
- ViewFlipper
- ListView
- GridView
- StackView
- AdapterViewFlipper
3. 위젯 만들기
- 매니페스트에 AppWidgetProvider 등록
AppWidgetProvider는 위젯과 앱이 상호 작용 할 수 있게 도와주는 클래스이다. BroadCastReceiver를 상속 받았기 때문에 onReceive 함수에서 Action에 따른 기능 분할 처리가 가능하다.
그 외에도 onUpdate, onAppWidgetOptionsChanged, onDeleted, onDisabled, onRestored 함수를 오버라이딩할 수 있는데 자세한 내용은 https://developer.android.com/guide/topics/appwidgets?hl=en#ProviderBroadcasts 에서 확인하면 된다.
<receiver android:name=".ExampleAppWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
example_appwidget_info 파일은 앱 위젯의 기본 설정을 위한 파일이다.
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="300dp"
android:minHeight="60dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/ic_launcher_foreground"
android:initialLayout="@layout/layout_widget"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>
- Widget 레이아웃 만들기 (R.layout.layout_widget)
제한된 뷰 객체를 사용하지 않게 주의하자.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="@+id/llRoot"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="horizontal"
android:background="@color/black"
>
<ImageView android:id="@+id/ivIcon"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@mipmap/ic_launcher"
android:layout_gravity="center_vertical"
/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
>
<TextView android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="#ffffff"
tools:text="제목입니다"
/>
<TextView android:id="@+id/tvSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14dp"
android:textColor="#ffffff"
tools:text="현재 재생중인 곡입니다."
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
>
<ImageView android:id="@+id/ivPrev"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_prev"
android:layout_gravity="start|center_vertical"
/>
<ImageView android:id="@+id/ivPlay"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_marginStart="8dp"
android:background="@drawable/ic_notification_play"
android:layout_gravity="center"
/>
<ImageView android:id="@+id/ivNext"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_next"
android:layout_gravity="end|center_vertical"
/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
- AppWidgetProvider 만들기
RemoteViews를 사용해봤다면 어렵지 않은 코드이다.
커스텀 액션을 상수로 만들어 이전 곡 재생, 현재 곡 재생, 다음 곡 재생 버튼을 등록해주었다.
class ExampleAppWidgetProvider : AppWidgetProvider()
{
private val ACTION_BTN_PREV: String = ExampleAppWidgetProvider::class.java.getPackage().name + ".BTN_PREV"
private val ACTION_BTN_PLAY: String = ExampleAppWidgetProvider::class.java.getPackage().name + ".BTN_PLAY"
private val ACTION_BTN_NEXT: String = ExampleAppWidgetProvider::class.java.getPackage().name + ".BTN_NEXT"
override fun onReceive(context: Context, intent: Intent)
{
super.onReceive(context, intent)
Toast.makeText(context, intent.action, Toast.LENGTH_SHORT).show()
}
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
val widget = ComponentName(context, ExampleAppWidgetProvider::class.java)
appWidgetIds.forEach {
val views : RemoteViews = RemoteViews(context.packageName, R.layout.layout_widget)
val pendingIntent = Intent(context, MainActivity::class.java).let {
PendingIntent.getActivity(context, 0, it, 0)
}
views.setOnClickPendingIntent(R.id.llRoot, pendingIntent)
views.setOnClickPendingIntent(R.id.ivPrev, getCustomActPedingIntent(context, ACTION_BTN_PREV))
views.setOnClickPendingIntent(R.id.ivPlay, getCustomActPedingIntent(context, ACTION_BTN_PLAY))
views.setOnClickPendingIntent(R.id.ivNext, getCustomActPedingIntent(context, ACTION_BTN_NEXT))
views.setTextViewText(R.id.tvTitle, "임시 타이틀")
views.setTextViewText(R.id.tvSubTitle, "임시 서브 타이틀")
views.setImageViewResource(R.id.ivIcon, R.mipmap.ic_launcher)
appWidgetManager.updateAppWidget(widget, views)
}
}
override fun onAppWidgetOptionsChanged(
context: Context?,
appWidgetManager: AppWidgetManager?,
appWidgetId: Int,
newOptions: Bundle?
) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
}
override fun onDeleted(context: Context?, appWidgetIds: IntArray?)
{
super.onDeleted(context, appWidgetIds)
}
override fun onDisabled(context: Context?)
{
super.onDisabled(context)
}
override fun onRestored(context: Context?, oldWidgetIds: IntArray?, newWidgetIds: IntArray?)
{
super.onRestored(context, oldWidgetIds, newWidgetIds)
}
private fun getCustomActPedingIntent(context: Context, customAction: String): PendingIntent
{
val intent = Intent(context, javaClass).apply {
action = customAction
}
return PendingIntent.getBroadcast(context, 0, intent, 0)
}
}
4. 결과 확인
재생 버튼을 눌렀을 때 커스텀 액션이 노출되는 것을 확인할 수 있다.
'OS > Android' 카테고리의 다른 글
원스토어 배포 이슈 (0) | 2022.11.29 |
---|---|
Android Media Button 대응 (0) | 2022.01.12 |
Android Bundle(.aab) release crash 삽질 (0) | 2021.08.27 |
삼성폰 HLS 재생 안됨 디버깅 (0) | 2021.06.07 |
안드로이드 패턴.. 어떤걸 쓰지?? (0) | 2021.05.25 |