본문 바로가기

안드로이드/코틀린

[안드로이드/Android] 위젯 만들기

반응형

안녕하세요. 이직등의 이유로 오랜만에 포스팅을 하게됐습니다. 이번 포스팅의 주제는 '위젯 만들기'며 위젯을 생성하고 ui를 업데이트 하는 과정까지를 작성 했습니다.

텍스트뷰만 갖고 있는 위젯을 사용했으며, 앱내 엑티비티에서 입력된 숫자 값을 위젯에 업데이트 하는 과정입니다. 위젯을 만들기 위해선 3가지 파일이 필요합니다. 위젯의 메타 데이터를 담은 xml파일, 위젯 layout 파일, 위젯 class파일 

※위젯의 변경사항이 업데이트 됐을때, 기존 위젯을 사용하고 있는 사용자들은 위젯을 지웠다 다시 생성해야만 적용됩니다.

단, 위젯 크기 변경 가능 여부는 바로 적용

 

위젯 관련 다른 포스팅

 

 


Text_widget_provider_info.xml

먼저 위젯의 메타 데이터 파일을 만들기전 res 폴더 하위에 xml 폴더를 생성해주시고, 해당 폴더에 파일을 만들어주세요.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:configure="com.example.widgettest.SettingActivity"
    android:initialLayout="@layout/text_widget"
    android:minWidth="220dp"
    android:minHeight="72dp"/>

 

minWidth, minHeight : 화면에 나타낼 디폴트 크기

셀 크기 db

셀 크기 db
1 40dp
2 110dp
n (70*n - 30)dp

 

resizeMode : 위젯의 크기 변경 가능 여부를 지정합니다.

  • horizontal : 가로 크기 변경 가능 
  • vertical : 세로 크기 변경 가능
  • horizontal|vertical : 가로, 세로 크기 변경 가능 
  • none : 크기 고정

minResizeWidth, minReSizeHeight : 크기 변경 최소 크기를 지정합니다. resizeMode가 none이 아닐때 사용합니다.

 

previewImage : 위젯 목록에서 보여질 미리보기 이미지 입니다.

 

updatePeriodMillis : 위젯 업데이트 주기로 30분부터 설정 가능하고 그 이하는 정상 작동하지 않습니다. ( 단위는 밀리세컨드 ) 

0으로 설정할 경우 갱신 신호를 보내지 않습니다. 더 짧은 주기로 업데이트를 원하실 경우 백그라운드 서비스나 알람 매니저를 사용해야 합니다.

 

initialLayout : 위젯의 레이아웃을 지정해주는 속성으로 별도로 생성한 레이아웃 파일의 이름을 값으로 지정하면 됩니다. 

위젯은 removeViews를 기반으로 하여 일부 클래스만 지원합니다. ( 커스텀 레이아웃 미지원 ) 

FrameLayout,LinearLayout, RelaviteLayout, AnalogClock, Chronometer, ImageButton, ImageVIew, ProgressBar, TextView, ViewFlipper

 

configure : 속성에 지정된 엑티비티는 위젯이 추가할때 실행됩니다. 위젯별 초기 설정 값을 넣어주는 용도로 사용 되며 엑티비티 인텐트 필터에 configure 액션을 등록 해야합니다. 완료될때 setResult를 통해 설정이 완료됐음을 전달합니다.

( ※설정 엑티비티 시작할때 setResult를 RESULT_CANCELD 로 설정하면, 사용자가 설정을 완료하지 않고 종료했을때 위젯을 추가하지 않을 수 있다 )

        <activity android:name=".SettingActivity">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
            </intent-filter>
        </activity>

설정화면에서 설정된 옵션값들로 직접 위젯을 변경합니다.

class SettingActivity : AppCompatActivity() {


    var btChange: Button? = null
    var etNumber: EditText? = null

    var widgetId: Int? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_setting)

        var bundle = intent.extras
        bundle?.let {

            widgetId = it.getInt(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
            )
        }
        setUi()

    }


    private fun setUi() {
        btChange = findViewById(R.id.bt_change)
        etNumber = findViewById(R.id.et_number)


        btChange?.setOnClickListener({
			// 위젯 업데이트 
            val appWidgetManager = AppWidgetManager.getInstance(this)
            val remoteView = RemoteViews(packageName,R.layout.text_widget)
            remoteView.setTextViewText(R.id.tv_text, etNumber?.text)
            appWidgetManager?.updateAppWidget(widgetId!!, remoteView)

            var intent = Intent()
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
            setResult(Activity.RESULT_OK, intent)
            finish()
        })
    }
}

 

widgetCategory : 위젯의 용도를 지정합니다.

  • home_screen : 홈 화면 용도
  • keyguard : 잠금 화면 용도
  • home_screen|keyguard : 홈 화면 및 잠금화면 용도  

initialKeyguardLayout : widgetCategory가 keyguard일때만 유효한 속성 값으로 잠금화면용 레이아웃 입니다.

text_widget.xml

위젯에서 나타낼 레이아웃을 작성합니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_text"
        android:text="기본 텍스트 "
        android:textColor="#000000"
        android:layout_width="wrap_content"
        android:textSize="20dp"
        android:layout_height="wrap_content"/>
</LinearLayout>

TextWidget.kt

onReceive는 전달받은 값을 설정하기 위해 사용했습니다. 이처럼 대게 공통적인 부분을 설정하는 용도로 많이 쓰입니다. 실질적인 위젯의 ui를 변경하거나 클릭 이벤트를 설정하는 부분은 onUpdate에서 주로 작업합니다.

onReceive에서 수신되는 기본적인 액션

  • ACTION_APPWIDGET_ENABLED : 첫번째 위젯이 추가될 때 전달 
  • ACTION_APPWIDGET_DISABLED : 마지막 위젯이 삭제될 때 전달 
  • ACTION_APPWIDGET_DELETED : 위젯이 삭제될 때 전달 ( 다수의 위젯중 하나만 삭제되어도 수신 ) 
  • ACTION_APPWIDGET_UPDATE : 위젯이 갱신될때 마다 수신 ( 단, 설정 엑티비티가 있으면 설정 후에 수신 )
  • ACTION_APPWIDGET_OPTIONS_CHANGED : 위젯의 크기나 옵션이 바뀌었을때 수신
class TextWidget : AppWidgetProvider() {

    var TAG = "tag" 
    var inputValues: String? = null

    override fun onReceive(context: Context?, intent: Intent?) {
        super.onReceive(context, intent)
        Log.d(TAG, "onReceive widget")
        inputValues = intent?.getStringExtra("number") 
        context?.let {
            var appWidgetManager = AppWidgetManager.getInstance(context)
            var widgetName = ComponentName(context.packageName, TextWidget::class.java.name)
            var widgetIds = appWidgetManager.getAppWidgetIds((widgetName)) 
            var actionName = intent?.action

            actionName?.let { 
                if (actionName.equals(AppWidgetManager.ACTION_APPWIDGET_UPDATE)) {
                    widgetIds?.let {
                        if (widgetIds.size > 0) {
                            this.onUpdate(context, appWidgetManager, widgetIds)
                        }
                    } 
                }
            }
        } 
    }

    override fun onUpdate(
        context: Context?,
        appWidgetManager: AppWidgetManager?,
        appWidgetIds: IntArray?
    ) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)


        context?.let {
            var views = RemoteViews(context.packageName, R.layout.text_widget)
            views.setTextViewText(R.id.tv_text, inputValues + "")
            appWidgetManager?.updateAppWidget(appWidgetIds, views)
        }
    }

저는 2가지만 사용했지만 경우에따라 사용할 수 있는 메소드들이 있습니다.

  • onDeleted : widget이 삭제될 때 호출됩니다.
  • onEnabled : 위젯이 처음으로 생성될 때 호출 됩니다. ( 같은 위젯을 추가적으로 생성할 경우는 첫번째만 호출되며, 주로 디비 설정 작업등을 진행합니다. )
  • onDisalbed : 마지막 위젯이 제거될 때 호출 됩니다. ( onEnabled에서 설정한 작업들은 해제하는 작업들을 진행합니다. )

MainActivity.kt

버튼을 클릭했을때 위젯으로 입력된 값을 전달합니다.

        btChange?.setOnClickListener({
            var intent = Intent(this, TextWidget::class.java)
            intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
            intent.putExtra("number", "" + etNumber?.text)
            sendBroadcast(intent)
        })

 

AndroidManifest.xml

Application 태그 사이에 아래와 같이 입력하면 위젯 목록에 '위젯 테스트'라는 이름의 위젯이 생성됩니다.

 <receiver android:name=".TextWidget" android:label="위젯 테스트">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/text_widget_provider_info"/>
        </receiver>

 

끝으로 위젯의 ui를 동적으로 업데이트 하는 방법은 3가지 정도 되는것 같습니다. 상황에 맞게 골라쓰시면 됩니다.

근데 간혹 서비스를 이용해서 업데이트 하는 방법을 사용하시는 분이 있는데 해당 방법은 타겟 28버전 대응에 맞춰 적합하지 않다고 저는 생각합니다. ( startforegroundservice로 인해 지워지지 않는 노티 발생 ) 

  • 브로드캐스트를 이용해 업데이트 하는 방법
  • 직접 버튼 이벤트를 통해 업데이트 하는 방법
  • provider에서 업데이트 주기를 주는 방법

 

반응형