近期看见XX款平板上, 设置中手势导航和虚拟三键导航的切换选项,觉得效果做的非常好,然后想在源码中倒腾一下,仿照写一个效果图出来,本篇文章在android11 Settings源码中做的功能,主要是做的UI效果图,具体逻辑可以根据项目需要去实现,也是对自定义Preference的一个总结。
从上到下第一个Preference有点类似于设置模块中的RadioButtonPreference, 左边是一个标题,右侧是一个RadioButton, 在android11上面没有这种Preference直接拿来用,所以需要稍微改动下。
第二个Preference 的UI布局:上面是一个导航样式图片,下面是一个文本演示,点击会跳转到另一个演示动画效果的界面。在设置中也没有现成的Preference,所以这个也需要自定义
第一个Preference的 layout 布局图, 里面的颜色,字体大小属性值都可以自己去适配
#1. layout布局文件: navigation_title_preference.xml
#2.drawable navigation_preference_bg
#3. drawable navi_pressed_false
#4. drawable navi_pressed_true
第二个Preference的 layout 布局图:
#layout 布局文件:navigation_preference.xml
第三,两个Preference需要用到的属性:自定义标题,自定义图片,自定义文本的属性,需要前期声明, 这样子可以在xml文件直接配置 preference:navigationTitle preference:naviDrawable preference:naviDetail 的值。
#第一个Preference的title属性
#第二个Preference的图片和文本的属性
第一个Preference类似于源生的RadioButtonPreference控件,只是布局不一样,通过继承相同的父类CheckBoxPreference,可以实现自定义的Preference,代码如下:
public class NavigationTitlePreference extends CheckBoxPreference {
//Preference的点击事件自定义接口
public interface OnClickListener {
void onNaviPrefernceClicked(NavigationTitlePreference emiter);
}
private OnClickListener mNaviTitleOnClickListener;
public void setNaviTitleOnClickListener(OnClickListener onClickListener) {
mNaviTitleOnClickListener = onClickListener;
}
private TextView mTextView;
private String mNavigationTitle;
public NavigationTitlePreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.NavigationTitlePreference, 0, 0);
// if these are already set that means they were set dynamically and don't need
// to be loaded from xml
mNavigationTitle = (mNavigationTitle == null) ?
attributes.getString(R.styleable.NavigationTitlePreference_navigationTitle) : mNavigationTitle;
setLayoutResource(R.layout.navigation_title_preference);
attributes.recycle();
}
public NavigationTitlePreference(Context context, AttributeSet attrs) {
this(context, attrs, TypedArrayUtils.getAttr(context,
androidx.preference.R.attr.preferenceStyle,
android.R.attr.preferenceStyle));
}
public NavigationTitlePreference(Context context) {
this(context, null);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
mTextView = (TextView)view.findViewById(R.id.navigation_type_title);
mTextView.setText(mNavigationTitle);
}
@Override
protected void onClick() {
if (null != mNaviTitleOnClickListener) {
mNaviTitleOnClickListener.onNaviPrefernceClicked(this);
}
}
}
, 代码解读:
1. 自定义类是继承于CheckBoxPreference,它里面已经对按钮(比如 RadioButton, SwitchButton)已经做了事件处理,我们只需要设置按钮的id为:android:id="@android:id/checkbox" 即可, 父类代码如下:
# CheckBoxPreference.java
@Override
protected void onBindView(View view) {
super.onBindView(view);
View checkboxView = view.findViewById(com.android.internal.R.id.checkbox);
if (checkboxView != null && checkboxView instanceof Checkable) {
((Checkable) checkboxView).setChecked(mChecked);
}
.......
}
2. 点击Preference的事件自定义接口
public interface OnClickListener {
void onNaviPrefernceClicked(NavigationTitlePreference emiter);
}
第二个Preference的代码,继承Preference, 组成元素为 一张图片和一个文本,点击文本会跳转到相应的动画演示界面, 不需要图片点击事件, 不需要整个Preference的点击事件
public class NavigationPreference extends Preference {
private TextView mTextView;
private ImageView mImageView;
private int mNavigationDrawable;
private String mNavigationDetail;
public NavigationPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public NavigationPreference(Context context) {
super(context);
init(context, null);
}
private void init(Context context, AttributeSet attrs) {
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.NavigationPreference, 0, 0);
// if these are already set that means they were set dynamically and don't need
// to be loaded from xml
mNavigationDrawable = (mNavigationDrawable == 0) ?
attributes.getResourceId(R.styleable.NavigationPreference_naviDrawable, 0) : mNavigationDrawable;
mNavigationDetail = (mNavigationDetail == null) ?
attributes.getString(R.styleable.NavigationPreference_naviDetail) : mNavigationDetail;
setLayoutResource(R.layout.navigation_preference);
attributes.recycle();
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
mTextView = (TextView)view.findViewById(R.id.demonstration_effect);
mTextView.setText(mNavigationDetail);
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getIntent() != null) {
Context context = getContext();
context.startActivity(getIntent());
}
}
});
mImageView = (ImageView)view.findViewById(R.id.navigation_type_icon);
mImageView.setImageResource(mNavigationDrawable);
//点击整个Preference时,无响应事件的关键代码
view.itemView.setClickable(false);
}
}
代码解读:
1. 点击整个自定义Preference时,无响应事件的关键代码
view.itemView.setClickable(false); //设置为不可点击
为什么呢?原理如下:
在 super.onBindViewHolder(view)中
public void onBindViewHolder(PreferenceViewHolder holder) {
View itemView = holder.itemView;
Integer summaryTextColor = null;
//设置 itemView 的点击事件
itemView.setOnClickListener(mClickListener);
.............
}
当点击后,会执行父类Preference.java 中的 performClick方法时,直接return了。
protected void performClick(View view) {
performClick();
}
public void performClick() {
//当设置不可点击,或者不可选择的时候,就直接return了。
if (!isEnabled() || !isSelectable()) {
return;
}
onClick();
if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
return;
}
PreferenceManager preferenceManager = getPreferenceManager();
if (preferenceManager != null) {
PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
.getOnPreferenceTreeClickListener();
if (listener != null && listener.onPreferenceTreeClick(this)) {
return;
}
}
if (mIntent != null) {
Context context = getContext();
context.startActivity(mIntent);
}
}
2. 点击文本的响应代码:
@Override
public void onClick(View v) {
if (getIntent() != null) {
Context context = getContext();
context.startActivity(getIntent());
}
} //这里的intent是通过 xml文件配置的intent参数,从而跳转到目标界面, 如下:
// 举个例子 通过配置intent参数,跳转到指定的界面
自定义的Preference代码完成后,接下来就开始写界面加载的xml文件了
布局文件:navigation_type_settings.xml
界面加载菜单代码:
public class NavigationTypeSettings extends SettingsPreferenceFragment implements
NavigationTitlePreference.OnClickListener{
private static final String KEY_GESTURE = "gesture_navi";
private static final String KEY_VIRTUAL = "virtual_navi";
private NavigationTitlePreference mGestureNaviPref;
private NavigationTitlePreference mVirtualNaviPref;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//加载界面
addPreferencesFromResource(R.xml.navigation_type_settings);
PreferenceScreen root = getPreferenceScreen();
mGestureNaviPref = (NavigationTitlePreference)root.findPreference(KEY_GESTURE);
mVirtualNaviPref = (NavigationTitlePreference)root.findPreference(KEY_VIRTUAL);
//自定义Preference的监听事件
mGestureNaviPref.setNaviTitleOnClickListener(this);
mVirtualNaviPref.setNaviTitleOnClickListener(this);
}
//点击Preference的回调方法处理
@Override
public void onNaviPrefernceClicked(NavigationTitlePreference emiter) {
..........
}
本篇文章是对自定义Preference的一个小结,只写了UI界面和点击响应事件,具体的逻辑看项目需求,只是一个抛砖引玉的Demo,设置模块已经有比较成熟的Preference控件,当有自定义的Preference的需求,根据情况继承类似的Preference去造轮子。对于UI事件的处理,本文中有响应整个Preference的事件处理,也有只需要响应其中一个子控件的事件处理,关键还是要多看和理解源码,面向对象编程继承和多态的灵活应用, 后续在工作项目中再多多总结吧。