自定义view实战之市场上app设置界面的通用SettingView
自定义view听起来就十分牛逼,但是日常开发中或许我们用不到完全的自定义,比如众多app的设置界面的一些条目,我们只需要组合成一套可伸缩,可定制的简单的view就行了,本文就详细的探讨一下设置界面通用的view设计流程。
需求图
1 本文将要实现的需求图
qq设置的截图
如上需求图:我们发现设置界面有很多条目,这些条目相似又不相似,相似在哪呢?左侧是文字,最右侧是箭头图标。可是有的很特别右侧显示文字(如20M)不显示图片(头像)。如果我们使用普通xml布局一个一个的写这些条目。不说重复的布局问题,就是你的布局文件至少也要上几百行,如果你用ListView 或者recyclerView搭建又考虑不同条目类型的问题,啊啊啊真的好烦。
解需求
1、分析
试想我们假如有一个view:SettingView,在搭建布局时我们只需向普通的TextView一样
android:text="测试"
就可以轻松的设置文字,向Imageview一样
android:src="@drawable/qq"
就可以轻松的设置图片,同时我们还具有view类似Visiable的性质。展示我们的右侧图片,展示我们的右侧文字。这时不就完美了吗。于是我们想到了自定义view来实现我们的需求
2 实现的功能
3 特殊的api
1 xml 中使用:
app:leftText="自定义属性1" //设置左侧文字
app:showRightPic="true" //展示右侧图片
app:rightPic="@drawable/qq"// 设置右侧图片 ps:只有showRightPic="true" 设置图片才生效
app:showRightText="true" // 展示右侧文字
app:rightText="20M" // 设置右侧文字 ps:只有showRightText="true" 设置右侧文字才生效
2 java代码中使用:
public void setLeftText(String text) 设置左侧文字
public void setRightText(String text) // 设置右侧文字
public void setHeadPortrait(Object imgType) // 设置右侧图片
4 具体实现
啰嗦了一大堆终于要上代码了啊哈,但是还要说下重点:无论是自定义属性设置文字还是调用java方法设置文字其本质都是在我们的自定义view中获得文字再设置。
4.1 首先定义SettingView类 这里我们继承了LinearLayout并进行了一些方法调用处理
public class SettingView extends LinearLayout {
@RequiresApi(api = Build.VERSION_CODES.P)
public SettingView(Context context) {
this(context, null);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public SettingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public SettingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
View view = LayoutInflater
.from(context)
.inflate(R.layout.view_setting_page, null);
this.addView(view);
}
}
可以看到我们的主要代码在第三个构造方法中,最终使用者就是调用的他,在这个方法中我们把一个布局(R.layout.view_setting_page)转换成了view添加到我们自定义的不布局中。
4.2 其实R.layout.view_setting_page就是我们的定义SettingView的布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:background="#ffffff"
android:id="@+id/rl_layout">
<!-- 分割线 没啥其他用途-->
<TextView
android:background="#ccc"
android:layout_width="match_parent"
android:layout_height="1dp" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="测试"
android:textColor="#000000" />
<ImageButton
android:focusable="false"
android:clickable="false"
android:id="@+id/ib"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null"
android:src="@drawable/news_cate_arr" />
<de.hdodenhof.circleimageview.CircleImageView
android:visibility="invisible"
android:id="@+id/img"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/ib"
android:paddingRight="5dp"
android:src="@drawable/qq"
android:text="测试数据" />
<!-- 注意 上面细节 使用了 invisiable 而不用gone的区别-->
<TextView
android:gravity="center"
android:visibility="gone"
android:id="@+id/right_text"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/ib"
android:paddingRight="5dp"
android:text="测试数据" />
</RelativeLayout>
如果我们使用自定义view不设置又图片,不设置又文字,那么条目就是需求图中"自定义属性2"的效果,也是本布局文件的默认效果(只设置左侧文字时)
4.3 自定义方法(简单)
/**
* 设置左侧文字的内容
* @param text 要设置的字符串
*/
public void setLeftText(String text) {
leftText.setText(text);
}
/**
* 设置右面侧文字的内容
* @param text 要设置的字符串
*
* 注意使用时要控件显示,由于xml属性原因默认隐藏的
*/
public void setRightText(String text){
rightText.setText(text);
}
/**
* 设置头像
*
* @param imgType 图片的类型
*
* 注意使用时要控件显示,由于xml属性原因默认隐藏的
*/
public void setHeadPortrait(Object imgType) {
if (imgType instanceof Drawable) {
headPortrait.setImageDrawable((Drawable) imgType);
} else if (imgType instanceof Integer) {
headPortrait.setImageResource((Integer) imgType);
} else if (imgType instanceof Bitmap) {
headPortrait.setImageBitmap((Bitmap) imgType);
} else if (imgType instanceof Uri) {
headPortrait.setImageURI((Uri) imgType);
} else {
throw new IllegalArgumentException("imgType just can be type:Drawable or recourceId or Bitmap or Uri");
}
}
4.4自定义属性 (本文详解)
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 1、 声明给哪个自定义控件 添加属性新的-->
<declare-styleable name="SettingView">
<!-- 2 、属性定义(解释如下) -->
<!-- 定义属性名为leftText,属性值为String类型-->
<attr name="leftText" format="string" />
<!-- 定义属性名为showRightPic,属性值为布尔类型-->
<attr name="showRightPic" format="boolean"/>
<!-- 定义属性名为rightPic,属性值为引用类型,简单地说就是可以使用用资源id的形式(R.xxx.xxx)-->
<attr name="rightPic" format="reference"/>
<attr name="showRightText" format="boolean"/>
<attr name="rightText" format="string"/>
</declare-styleable>
</resources>
如上图 到values文件夹下创建名为attrs的资源文件
4.4 自定义view中获取自定义属性(思考上文具体实现中设置文字本质)
三种获取方式:(参看下面具体实现代码注释,及参考链接)
- attrs.getAttributeXXXValue(命名空间,自定义属性名字)
- for (int i=0;i<attrs.getAttributeCount();i++) 从属性集合中获取
- 使用TypedArray
具体实现
/**
* 自定义属性初始化工作
*
* @param attrs 属性集合
*/
private void initSelfAttrs(AttributeSet attrs, Context context) {
// 1 左侧text的xml设置
String leftText = attrs.getAttributeValue(NAME_SPACE, "leftText");
setLeftText(leftText);
// 获得xml中设置 showRightPic 属性值 默认 false
boolean showRightPic = attrs.getAttributeBooleanValue(NAME_SPACE, "showRightPic", false);
boolean showRightText = attrs.getAttributeBooleanValue(NAME_SPACE, "showRightText", false);
// 3 右面text的设置
if (showRightText){
rightText.setVisibility(VISIBLE);
String rightText = attrs.getAttributeValue(NAME_SPACE, "rightText");
setRightText(rightText);
}
// 图片的设置还是使用TypeArray 获取设置
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SettingView);//参见你的attrs中定义<declare-styleable name="SettingView">
for (int i = 0; i < typedArray.getIndexCount(); i++) {
int index = typedArray.getIndex(i);
switch (index) {
//1 左侧文本的xml设置(使用这种方式也行)
// case R.styleable.SettingView_leftText:
// String leftText = typedArray.getString(index);
// setLeftText(leftText);
// break;
case R.styleable.SettingView_rightPic:
// 2 右侧图片的显示
if (showRightPic) {
headPortrait.setVisibility(VISIBLE);
Drawable drawable = typedArray.getDrawable(index);
setHeadPortrait(drawable);
}
break;
}
}
typedArray.recycle();//回收
}
ps:
1 命名空间使用http://schemas.android.com/apk/res-auto这个字符串就行。
自定义属性名就是你的attrs中定义的name
2 如果使用typedArray时:context.obtainStyledAttributes(attrs, R.styleable.自定义view类名)
case R.styleable.自定义类名_自定义属性名 固定格式
attr各种属性使用详解参考
命名空间使用及属性获取参考
小结
经过一番探讨终于完成了设置界面通用View的设计,本文中也有一些细节不知道读者们读出来了吗,比如图片设置时使用了invisiable占位而不用gone,还有一个坑,图片文字同时出的现问题这是没有解决的。作者也懒了就没实现哈,还有本文的细节,箭头图标处点击事件的处理,这是事件分发机制的逻辑这里就不越俎代庖了。
具体源码下载 这是我的开源项目中的地址哦,也欢迎小伙伴们下载观看设置界面的具体逻辑是实现。