안드로이드 With Java #30 Preference (환경 설정)
안드로이드에서는 Preference
라는 개념을 이용하여 앱의 환경 설정을 조정할 수 있다. 공식문서는 여기에 있는데 한글 번역을 보면 내가 난독증인지 발번역인지 이해하기가 조금 어렵다.
기본적인 개념은 이전에 배웠던 Fragment
를 이용하는 것이다. preference
를 사용하기 위한 xml
을 작성하고, Fragment
로 Activity
를 덮어씌워주면 된다. 기본적으로는 Fragment
를 위한 xml
을 작성하고 로직을 담고 있는 Java 파일을 작성하면 된다.
xml
파일은 다음과 같은 양식으로 작성해주면 된다.
<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:key="notifications"
app:title="Enable message notifications"/>
<Preference
app:key="feedback"
app:title="Send feedback"
app:summary="Report technical issues or suggest new features"/>
</PreferenceScreen>
그러면 대략 아래와 같은 생김새의 화면이 생성된다.
(그림이 너무 크긴 한데) 우리가 앱에서 흔히 보는 환경설정 화면이다.
JAVA 소스는 대략 위와 같은 방식으로 구현하는 것을 권하고 있다.
Preference
라는 이름과 같이 설정사항들은 SharedPreference
에 저장한다고 한다.
일단은, Fragment
로 구현한다는 사실에서 알 수 있듯 기존 Fragment
를 이용한 화면 구현과 매우 비슷하다.
Fragment
를 보여줄 Activity
하나를 생성하고, onCreate()
에 Fragment
를 보여주는 코드를 작성하면 된다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.some_activity);
fragmentManager = getSupportFragmentManager();
fragmentManager
.beginTransaction()
.replace(R.id.container_in_some_activity, new SettingsFragment())
.commit();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
위와 같이 작성하면 된다.
기본적으로 Activity
를 위한 Layout
을 하나 만들고 그 안에 Fragment
가 들어갈 컨테이너를 하나 만드는 개념이다.
위와 같은 절차만 진행해도 이렇게 화면은 보인다.
다른 Fragment
로 이동하며 설정 화면을 옮긴다는 것은 위의 스크린샷의 내용과 같다. 세부 설정을 하기 위해 다른 Fragment
로 옮겨야 하는 상황일 때 구현한다.
방법은 여기 공식문서에 잘 나와있다.
나의 경우에는 본인인증을 3가지 방식 중 하나를 택하는 방식으로 구현했어야 했다.
@Override
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
// Instantiate the new Fragment
final Bundle args = pref.getExtras();
final Fragment fragment = getSupportFragmentManager().getFragmentFactory().instantiate(
getClassLoader(),
pref.getFragment());
fragment.setArguments(args);
fragment.setTargetFragment(caller, 0);
// Replace the existing Fragment with the new Fragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.container_in_some_activity, fragment)
.addToBackStack(null)
.commit();
return true;
}
위와 같이 설정 Fragment
에 onPreferenceStartFragment()
콜백함수를 작성해준다.
연결할 Fragment
도 미리 xml
을 이용하여 PreferenceScreen
형식으로 만들어둔다. 그리고 환경설정 Fragment
를 처음 설정할 때 처럼 Activity
에 연결해두면 된다.
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.auth_preferences, rootKey);
}
그리고 Fragment
가 잘 인식되도록 빈 생성자 하나를 만들어두는 것이 좋다고 한다.
나의 경우에는
public AuthSettingsFragment() {
}
위와 같은 생성자 하나를 만들어두었다.
참고로 Fragment는 절대 Activity 클래스 내부나 다른 자바파일에 내장되는 방식으로 만들어선 안된다. 연결될 Fragment는 반드시 다른 파일로 나뉘어 있어야 한다.
연결할 xml
은 아래와 같은 방식으로 설정하면 된다. com.패키지명.Fragment명
이렇게 넣어놓으면 잘 연결된다.
<Preference
app:fragment="com.....AuthSettingsFragment"
/>
여기서 말하는 뒤로가기 버튼이란
위 버튼을 말한다.
사용자는 세부 설정을 한 뒤에 기존 설정창으로 나가고 싶을 것이다. 안드로이드 기본 UI에도 뒤로가기 버튼이 있지만 저 버튼을 이용하여 뒤로 가려고 하는 사용자도 분명 있을 것이다.
저 버튼에 대한 콜백함수는 onSupportNavigateUp()
이다.
여기에 아래와 같이 코딩한다.
@Override
public boolean onSupportNavigateUp() {
if(fragmentManager.getBackStackEntryCount() == 0) {
finish();
}else {
fragmentManager.popBackStack();
}
return super.onSupportNavigateUp();
}
위와 같이 코딩하면, Fragment
스택이 있을 때는 나를 pop()
하여 이전 스택으로 가고, Fragment
스택이 없을 때는 Activity
를 종료한다.
import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
public class SettingsActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback{
FragmentManager fragmentManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
fragmentManager = getSupportFragmentManager();
fragmentManager
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
@Override
public boolean onSupportNavigateUp() {
if(fragmentManager.getBackStackEntryCount() == 0) {
finish();
}else {
fragmentManager.popBackStack();
}
return super.onSupportNavigateUp();
}
@Override
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
// Instantiate the new Fragment
final Bundle args = pref.getExtras();
final Fragment fragment = getSupportFragmentManager().getFragmentFactory().instantiate(
getClassLoader(),
pref.getFragment());
fragment.setArguments(args);
fragment.setTargetFragment(caller, 0);
// Replace the existing Fragment with the new Fragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.settings, fragment)
.addToBackStack(null)
.commit();
return true;
}
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
}
}
}
여기엔 3개의 체크상자중 하나의 체크상자를 클릭했을 때, 다이얼로그 메세지와 함께 단 하나의 체크상자만 체크하는 로직도 들어있다.
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
public class AuthSettingsFragment extends PreferenceFragmentCompat{
AlertDialog.Builder adb;
boolean dialogResult;
CheckBoxPreference authPasswordCheckBoxPreference;
CheckBoxPreference authFingerprintCheckBoxPreference;
CheckBoxPreference authPinCheckBoxPreference;
CheckBoxPreference selectedCheckBoxPreference;
public AuthSettingsFragment() {
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.auth_preferences, rootKey);
adb = new AlertDialog.Builder(getActivity());
adb.setTitle("정말로 변경하시겠습니까? 변경하면 이전 인증수단을 초기화합니다.");
adb.setView(getView());
adb.setPositiveButton("변경하기", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
authPasswordCheckBoxPreference.setChecked(false);
authPinCheckBoxPreference.setChecked(false);
authFingerprintCheckBoxPreference.setChecked(false);
selectedCheckBoxPreference.setChecked(true);
}
});
adb.setNegativeButton("취소", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogResult = false;
}
});
Preference.OnPreferenceChangeListener onPreferenceChangeListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
selectedCheckBoxPreference = (CheckBoxPreference) preference;
if(((CheckBoxPreference) preference).isChecked()){
Toast.makeText(getContext(), "현재 사용중인 인증수단입니다.", Toast.LENGTH_SHORT).show();
return false;
}
adb.show();
return false;
}
};
authPasswordCheckBoxPreference = findPreference("pref_auth_password");
authPasswordCheckBoxPreference.setOnPreferenceChangeListener(onPreferenceChangeListener);
authPinCheckBoxPreference = findPreference("pref_auth_pin");
authPinCheckBoxPreference.setOnPreferenceChangeListener(onPreferenceChangeListener);
authFingerprintCheckBoxPreference = findPreference("pref_auth_fingerprint");
authFingerprintCheckBoxPreference.setOnPreferenceChangeListener(onPreferenceChangeListener);
}
}
클릭 시
화면 표출
변경하기 클릭 시에
체크된 것이 바뀜.
끝.