Android 自定义Preference 讲解
1. 前言
近期看见XX款平板上, 设置中手势导航和虚拟三键导航的切换选项,觉得效果做的非常好,然后想在源码中倒腾一下,仿照写一个效果图出来,本篇文章在android11 Settings源码中做的功能,主要是做的UI效果图,具体逻辑可以根据项目需要去实现,也是对自定义Preference的一个总结。
2. 原机效果图
3. 实现步骤
3.1 UI效果图,见5实现效果图
从上到下第一个Preference有点类似于设置模块中的RadioButtonPreference, 左边是一个标题,右侧是一个RadioButton, 在android11上面没有这种Preference直接拿来用,所以需要稍微改动下。
第二个Preference 的UI布局:上面是一个导航样式图片,下面是一个文本演示,点击会跳转到另一个演示动画效果的界面。在设置中也没有现成的Preference,所以这个也需要自定义
3.2 UI布局代码
第一个Preference的 layout 布局图, 里面的颜色,字体大小属性值都可以自己去适配
#1. layout布局文件: navigation_title_preference.xml<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent" android:layout_height="58dp"android:background="@drawable/navigation_preference_bg"><TextViewandroid:id="@+id/navigation_type_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_marginLeft="16dp"android:layout_centerInParent="true"android:textSize="16sp"android:textColor="#FF000000"/><RadioButtonandroid:id="@android:id/checkbox"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_marginRight="30dp"android:layout_centerInParent="true"android:focusable="false"android:clickable="false"/></RelativeLayout>#2.drawable navigation_preference_bg<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="false" android:drawable="@drawable/navi_pressed_false"/><item android:state_pressed="true"
android:drawable="@drawable/navi_pressed_true"/>
</selector>#3. drawable navi_pressed_false<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle"><solid android:color="@color/search_bar_background" /><corners android:radius="9dp" />
</shape>#4. drawable navi_pressed_true<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle"><solid android:color="#1a000000" /><corners android:radius="9dp" />
</shape>
第二个Preference的 layout 布局图:
#layout 布局文件:navigation_preference.xml<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent" android:layout_height="155dp"android:background="@drawable/navigation_preference_bg"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/navigation_type_icon"android:layout_marginTop="26dp"android:layout_width="240dp"android:layout_height="70dp"android:layout_centerHorizontal="true"/><TextViewandroid:id="@+id/demonstration_effect"android:layout_width="90dp"android:layout_height="34dp"android:layout_below="@id/navigation_type_icon"android:layout_marginTop="16dp"android:layout_marginBottom="9dp"android:background="@drawable/navigation_preference_bg"android:gravity="center"android:layout_centerHorizontal="true"android:textColor="#268AFF"android:textSize="16sp"/></RelativeLayout></RelativeLayout>
第三,两个Preference需要用到的属性:自定义标题,自定义图片,自定义文本的属性,需要前期声明, 这样子可以在xml文件直接配置 preference:navigationTitle preference:naviDrawable preference:naviDetail 的值。
#第一个Preference的title属性<declare-styleable name="NavigationTitlePreference"><attr name="navigationTitle" format="string" /></declare-styleable>#第二个Preference的图片和文本的属性<declare-styleable name="NavigationPreference"><attr name="naviDrawable" format="reference" /><attr name="naviDetail" format="string" /></declare-styleable>
3.3 自定义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 xmlmNavigationTitle = (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);}@Overridepublic void onBindViewHolder(PreferenceViewHolder view) {super.onBindViewHolder(view);mTextView = (TextView)view.findViewById(R.id.navigation_type_title);mTextView.setText(mNavigationTitle);}@Overrideprotected void onClick() {if (null != mNaviTitleOnClickListener) {mNaviTitleOnClickListener.onNaviPrefernceClicked(this);}}
}
, 代码解读:
1. 自定义类是继承于CheckBoxPreference,它里面已经对按钮(比如 RadioButton, SwitchButton)已经做了事件处理,我们只需要设置按钮的id为:android:id="@android:id/checkbox" 即可, 父类代码如下:
# CheckBoxPreference.java @Overrideprotected 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 xmlmNavigationDrawable = (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();}@Overridepublic void onBindViewHolder(PreferenceViewHolder view) {super.onBindViewHolder(view);mTextView = (TextView)view.findViewById(R.id.demonstration_effect);mTextView.setText(mNavigationDetail);mTextView.setOnClickListener(new View.OnClickListener() {@Overridepublic 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参数,从而跳转到目标界面, 如下:
<Preferenceandroid:key="xxxx"android:title="xxxxx">// 举个例子 通过配置intent参数,跳转到指定的界面<intent android:action="android.credentials.INSTALL_AS_USER"android:targetPackage="com.android.certinstaller"android:targetClass="com.android.certinstaller.CertInstallerMain"><extra android:name="install_as_uid" android:value="1010" /></intent></Preference>
4. 设置界面代码
自定义的Preference代码完成后,接下来就开始写界面加载的xml文件了
布局文件:navigation_type_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"xmlns:preference="http://schemas.android.com/apk/res-auto"android:key="gesture_system_navigation_type"android:title="@string/system_navigation_title"><com.android.settings.gestures.NavigationTitlePreferenceandroid:key="gesture_navi"//自定义的titlepreference:navigationTitle="@string/system_gesture_navigation_title"/><com.android.settings.gestures.NavigationPreferenceandroid:key="gesture_navi_drawable"//自定义的图片preference:naviDrawable="@drawable/ic_gesture_navigation"//自定义文本preference:naviDetail="@string/demonstration_effect"><intentandroid:action="android.intent.action.MAIN"android:targetPackage="com.android.settings"android:targetClass="com.android.settings.Settings$GestureNaviSettingsActivity"/></com.android.settings.gestures.NavigationPreference><com.android.settings.gestures.NavigationTitlePreferenceandroid:key="virtual_navi"preference:navigationTitle="@string/system_virtual_navigation_title"/><com.android.settings.gestures.NavigationPreferenceandroid:key="virtual_navi_drawable"preference:naviDrawable="@drawable/ic_virtual_navigation"preference:naviDetail="@string/demonstration_effect"><intentandroid:action="android.intent.action.MAIN"android:targetPackage="com.android.settings"android:targetClass="com.android.settings.Settings$VirtualNaviSettingsActivity"/></com.android.settings.gestures.NavigationPreference></PreferenceScreen>
界面加载菜单代码:
public class NavigationTypeSettings extends SettingsPreferenceFragment implementsNavigationTitlePreference.OnClickListener{private static final String KEY_GESTURE = "gesture_navi";private static final String KEY_VIRTUAL = "virtual_navi";private NavigationTitlePreference mGestureNaviPref;private NavigationTitlePreference mVirtualNaviPref;@Overridepublic 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的回调方法处理@Overridepublic void onNaviPrefernceClicked(NavigationTitlePreference emiter) {..........}
5. 自定义Preference效果图
6. 总结
本篇文章是对自定义Preference的一个小结,只写了UI界面和点击响应事件,具体的逻辑看项目需求,只是一个抛砖引玉的Demo,设置模块已经有比较成熟的Preference控件,当有自定义的Preference的需求,根据情况继承类似的Preference去造轮子。对于UI事件的处理,本文中有响应整个Preference的事件处理,也有只需要响应其中一个子控件的事件处理,关键还是要多看和理解源码,面向对象编程继承和多态的灵活应用, 后续在工作项目中再多多总结吧。
Android 自定义Preference 讲解相关推荐
- android自定义更新,Android 完美解决自定义preference与ActivityGroup UI更新的问题
之前发过一篇有关于自定义preference 在ActivityGroup 的包容下出现UI不能更新的问题,当时还以为是Android 的一个BUG 现在想想真可笑 .其实是自己对机制的理解不够深刻, ...
- android 自定义设置界面,Android 设置界面之 Preference
Android系统为设置界面的UI提供了一系列的接口,设置界面的部分和Activity是分离的,会有一个PreferenceScreen的对象 是根目录,在其中会包含CheckBoxPreferenc ...
- android 自定义switchpreference,Android设置选项开发及自定义Preference样式
一个完整的Android应用程序都应该提供选项(或者叫偏好设置等等)让用户对APP的表现形式能够进行设置,比如说是否加入用户体验计划,或者是否自动升级.定时提醒.开启自启动.后台运行等等.提供一个好的 ...
- Preference组件探究之自定义Preference
上一篇文章中我们从源码入手讲解了Preference画面展示的原理.这篇文章讲述下官方提供的Preference组件是怎么实现的,以及我们自己如何自定义Preference组件. Preference ...
- android自定义view获取控件,android 自定义控件View在Activity中使用findByViewId得到结果为null...
转载:http://blog.csdn.net/xiabing082/article/details/48781489 1. 大家常常自定义view,,然后在xml 中添加该view 组件..如果在 ...
- Android 自定义ScrollView ListView 体验各种纵向滑动的需求
1.概述 群里的一个哥们有个需求是这样的:问题:主要功能就是:1.循环的一个滑动:2.每次滑动结束,保持每个Item的完整.然后我当时给他写了个Demo,所有代码都在Activity里面,后期看来其太 ...
- android 自定义取色器,【Android自定义View】仿Photoshop取色器ColorPicker(二)
ColorPicker 一款仿Photoshop取色器的Android版取色器. 前言 上一篇已经简单介绍了ColorPicker的项目结构以及两种颜色空间,接下来我们详细解析一下ColorPicke ...
- 【我的Android进阶之旅】Android自定义Lint实践
背景 2017年8月份的时候,我在公司开始推广Lint.FindBugs等静态代码检测工具.然后发现系统自带的Lint检测的Issue不满足我们团队内部的特定需求,因此去自定义了部分Lint规则.这个 ...
- android 自定义xml属性
Android 自定义组件 Android 提供了非常精致的和非常强大的组件化模型,能够更加方便的构建UI,这些UI组件都是基于基本的layout类:View 和 ViewGroup. 部分能够用的w ...
最新文章
- 【UWP】使用 Rx 改善 AutoSuggestBox
- Ansible自动化运维笔记1(安装配置)
- 阿里云 Ubuntu16.04 部署 LAMP
- 分布式文件系统研究-fastDFS安装及配置文件说明
- Python熊猫– GroupBy
- 拉格朗日差值 - 杜教板子
- docker mysql命令大全_Docker命令大全
- php js下拉框与文本联动,php mysql js 下拉框 二级联动
- Go Web 编程--如何确保Cookie数据的安全传输
- 2754. [SCOI2012]喵星球上的点名【后缀数组】
- 中心/设置地图缩放以覆盖所有可见的标记?
- 让程序员崩溃的一句话。。。
- python量化交易策略实例_Python写一个量化股票提醒系统实例
- Python学习13 ----Seaborn调色板
- 如何将图片存进SQL数据库中以及从数据库读取照片(解决办法)
- 机器学习深度学习 常用算法推导
- 深度之眼 - Python学习笔记——第四章 组合数据类型
- Compound原理
- 238. 银河英雄传说(并查集,扩展域)
- IBM i 7.2 –超越一切
热门文章
- 机械臂控制C语言程序,51单片机的6自由度机械臂 16路舵机控制 源码
- FOC Simulink仿真 --- 0.FOC概述及组成部分
- 使用python对电力故障录波数据进行滤波再分析
- c语言中double sper是什么意思啊,C语言的文件操作的使用
- Ubuntu 下 FireFox( 火狐 )无法使用HTML5播放器的解决方法
- 计算你恋爱谈了多长时间
- GUP Net论文精读
- github电脑壁纸_小创意--- C#设置电脑壁纸
- Linux热插拔硬盘导致盘符漂移,一种解决热插拔时磁盘盘符漂移方法及装置与流程...
- 上海工程技术大学本科毕业论文答辩和论文选题PPT模板