自定义view实战之市场上app设置界面的通用SettingView

自定义view听起来就十分牛逼,但是日常开发中或许我们用不到完全的自定义,比如众多app的设置界面的一些条目,我们只需要组合成一套可伸缩,可定制的简单的view就行了,本文就详细的探讨一下设置界面通用的view设计流程。

需求图

1 本文将要实现的需求图

自定义view实战之市场上app设置界面的通用SettingView

qq设置的截图

自定义view实战之市场上app设置界面的通用SettingView

如上需求图:我们发现设置界面有很多条目,这些条目相似又不相似,相似在哪呢?左侧是文字,最右侧是箭头图标。可是有的很特别右侧显示文字(如20M)不显示图片(头像)。如果我们使用普通xml布局一个一个的写这些条目。不说重复的布局问题,就是你的布局文件至少也要上几百行,如果你用ListView 或者recyclerView搭建又考虑不同条目类型的问题,啊啊啊真的好烦。

解需求

1、分析

试想我们假如有一个view:SettingView,在搭建布局时我们只需向普通的TextView一样
android:text="测试"
就可以轻松的设置文字,向Imageview一样
android:src="@drawable/qq"
就可以轻松的设置图片,同时我们还具有view类似Visiable的性质。展示我们的右侧图片,展示我们的右侧文字。这时不就完美了吗。于是我们想到了自定义view来实现我们的需求

2 实现的功能

自定义view实战之市场上app设置界面的通用SettingView

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自定义属性 (本文详解)

自定义view实战之市场上app设置界面的通用SettingView
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中获取自定义属性(思考上文具体实现中设置文字本质)

三种获取方式:(参看下面具体实现代码注释,及参考链接)

  1. attrs.getAttributeXXXValue(命名空间,自定义属性名字)
  2. for (int i=0;i<attrs.getAttributeCount();i++) 从属性集合中获取
  3. 使用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,还有一个坑,图片文字同时出的现问题这是没有解决的。作者也懒了就没实现哈,还有本文的细节,箭头图标处点击事件的处理,这是事件分发机制的逻辑这里就不越俎代庖了。
具体源码下载 这是我的开源项目中的地址哦,也欢迎小伙伴们下载观看设置界面的具体逻辑是实现。

The end