Android 自定义view第二弹——组合控件

概述:本篇为自定义控件的三种实现方式第一种—组合控件,在此篇中,我将以一个例子的形式来展现组合控件的实现方式。

一组合控件的定义

自定义组合控件一般来说都是以ViewGroup及其子类(LinearLayout、RelativeLayout、FrameLayout等)为主,内部嵌套其他控件,来组合成一个新的控件,实现一些特定的需要,可以是代码简化,结构清晰,重用性较高。通常来说,我们会实现定义好一个Layout.xml文件,然后让我们的自定义控件去加载此xml,并获取子控件,然后设置属性(可以通过代码,也可以从资源文件中加载)、添加事件。

二 自定义组合控件注意要点:

1.加载xml文件是在构造方法中完成的,通过调用inflate(R.layout.my_layout,this,true),注意第二个和第三个参数;

2.如果需要从资源文件中加载自定义的属性,则必须重写Constructor(Context context, AttributeSet attrs)此构造方法,属性是定义在attrs.xml中的;

3.获取子控件对象,可以在构造方法中获取,也可以重写onFinishInflate()方法来获取,个人建议采用第二种,可以保证控件已经完全加载好了;

4.添加事件可以直接在控件中写,不过考虑到扩展性及复用性,建议对外暴露接口。

三 一个栗子

1 栗子说明

在很多项目中,我们都会用到app标题栏,几乎每个页面的形式都一样,左边 中间 右边 。但在每个页面中 左边 中间 右边的内容又是不一样的。如果我们在每个页面的布局中重新写的话,将是一个繁琐的工作。如果我们将所有的形式都融合在一个布局用 include来引用时 每个页面的逻辑不一样 要让不用的控件来显示和隐藏 就会显得特别的麻烦。所以在这里我们用自定义view的组合控件的方式简化我们的代码实现同样的效果。

2 栗子实现步骤

① 继承

自定义组合控件必须继承ViewGroup或者其子类。
CustomTitleBar extends RelativeLayout 在这里我们继承的是RelativeLayout 至于为什么继承RelativeLayout 我会在后面说明。先来看看我们的构造方法

 public CustomTitleBar(Context context) {
        this(context, null);
    }

    public CustomTitleBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomTitleBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

一个参数的构造方法 调 二个参数的构造方法 两个参数的构造方法 调三个参数的构造方法 在三个参数的构造方法中 有一个init()的方法初始化 传入了 上下文 属性集合 默认样式

② 自定义属性

在res文件夹下的value文件中 生成 attrs文件
声明一组属性:
使用来定义一个属性集合,name就是属性集合的名字,这个名字一定要起的见名知意。

  <declare-styleable name="CustomTitleBar">
    <!--添加属性-->
   </declare-styleable>

然后就是定义属性值了,通过 方式定义属性值,属性名字同样也要起的见名知意,format表示这个属性的值的类型,类型有以下几种:

reference:引用资源

string:字符串

Color:颜色

boolean:布尔值

dimension:尺寸值

float:浮点型

integer:整型

fraction:百分数

enum:枚举类型

flag:位或运算

栗子的属性集合如下

<resources>
    <declare-styleable name="CustomTitleBar">
        <attr name="TitleBar_background_color" format="color|reference" />
        <!-- 左边 -->
        <attr name="TitleBar_left_text" format="string|reference" />
        <attr name="TitleBar_left_text_size" format="dimension|reference" />
        <attr name="TitleBar_left_text_color" format="color|reference" />
        <attr name="TitleBar_left_Drawable" format="reference" />

        <!-- 中间-->
        <attr name="TitleBar_center_text" format="string|reference" />
        <attr name="TitleBar_center_text_size" format="dimension|reference" />
        <attr name="TitleBar_center_text_color" format="color|reference" />
        <attr name="TitleBar_center_Drawable" format="reference" />

        <!-- 右边-->
        <attr name="TitleBar_right_text" format="string|reference" />
        <attr name="TitleBar_right_text_size" format="dimension|reference" />
        <attr name="TitleBar_right_text_color" format="color|reference" />
        <attr name="TitleBar_right_Drawable" format="reference" />
    </declare-styleable>
</resources>
③ 获取自定义属性

init()方法中 首先 初始化布局:

//初始化布局
LayoutInflater.from(getContext()).inflate(R.layout.layout_custom_titlebar, this, true);

我们来看看我们的布局文件

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/left_tv"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:background="?android:attr/selectableItemBackground"
        android:gravity="center_vertical|left"
        android:minWidth="50dp"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="10dp"
        android:singleLine="true"
        android:textColor="@android:color/white"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/center_tv"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_centerHorizontal="true"
        android:layout_marginLeft="70dp"
        android:layout_marginRight="70dp"
        android:gravity="center_vertical"
        android:singleLine="true"
        android:text=""
        android:textColor="@android:color/white"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/right_tv"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:background="?android:attr/selectableItemBackground"
        android:gravity="center_vertical|right"
        android:minWidth="50dp"
        android:paddingLeft="10dp"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:singleLine="true"
        android:text=""
        android:textColor="@android:color/white"
        android:textSize="16sp" />

</merge>

可以看到在布局文件中 我们用到了 merge标签 不知道什么意思和用法的同学请自行Google(我鄙视百度) 因为为了 更好的控制布局 我们继承了RelativeLayout 也就是 我们把merge标签里的控件直接放在了RelativeLayout中 所以 在merger标签中的 控件 的属性 要按RelativeLayout布局来使用 比如

<TextView
        android:id="@+id/left_tv"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        **android:layout_alignParentLeft="true"**
        android:background="?android:attr/selectableItemBackground"
        **android:gravity="center_vertical|left"**
        android:minWidth="50dp"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="10dp"
        android:singleLine="true"
        android:textColor="@android:color/white"
        android:textSize="16sp" />
//获取自定义属性的集合
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleBar, defStyleAttr, 0);

//背景颜色
mBackgroundColor = array.getColor(R.styleable.CustomTitleBar_TitleBar_background_color, Color.WHITE);

//左边
mLeftText = array.getString(R.styleable.CustomTitleBar_TitleBar_left_text);
mLeftTextColor = array.getColor(R.styleable.CustomTitleBar_TitleBar_left_text_color, Color.BLACK);
mLeftTextSize = array.getDimension(R.styleable.CustomTitleBar_TitleBar_left_text_size, sp2px(16));
mLeftDrawable = array.getDrawable(R.styleable.CustomTitleBar_TitleBar_left_Drawable);

//中间
mCenterText = array.getString(R.styleable.CustomTitleBar_TitleBar_center_text);
mCenterTextColor = array.getColor(R.styleable.CustomTitleBar_TitleBar_center_text_color, Color.WHITE);
mCenterTextSize = array.getDimension(R.styleable.CustomTitleBar_TitleBar_center_text_size, sp2px(18));
mCenterDrawable = array.getDrawable(R.styleable.CustomTitleBar_TitleBar_center_Drawable);

//右边
mRightText = array.getString(R.styleable.CustomTitleBar_TitleBar_right_text);
mRightTextColor = array.getColor(R.styleable.CustomTitleBar_TitleBar_right_text_color, Color.WHITE);
mRightTextSize = array.getDimension(R.styleable.CustomTitleBar_TitleBar_right_text_size, sp2px(16));
mRightDrawable = array.getDrawable(R.styleable.CustomTitleBar_TitleBar_right_Drawable);
//释放资源 必须添加
array.recycle();
④ 获取控件 设置空间属性
/**
     * 此方法中 代表布局一定初始化完成 不存在空指针问题
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        tvLeft = (TextView) findViewById(R.id.left_tv);
        tvCenter = (TextView) findViewById(R.id.center_tv);
        tvRight = (TextView) findViewById(R.id.right_tv);

        //设置背景颜色
        setBackgroundColor(mBackgroundColor);
        //设置左边
        setLeft(mLeftText, mLeftTextColor, mLeftTextSize, mLeftDrawable);
        //设置中间
        setCenter(mCenterText, mCenterTextColor, mCenterTextSize, mCenterDrawable);
        //设置右边
        setRight(mRightText, mRightTextColor, mRightTextSize, mRightDrawable);
    }

 private void setLeft(CharSequence leftText, int leftTextColor, float leftTextSize, Drawable leftDrawable) {
        if (leftDrawable != null) {
            leftDrawable.setBounds(0, 0, leftDrawable.getMinimumWidth(), leftDrawable.getMinimumHeight());
            tvLeft.setCompoundDrawables(leftDrawable, null, null, null);
        }
        tvLeft.setText(leftText);
        tvLeft.setTextSize(TypedValue.COMPLEX_UNIT_PX, leftTextSize);
        tvLeft.setTextColor(leftTextColor);
        tvLeft.setClickable(!TextUtils.isEmpty(leftText) || leftDrawable != null);
    }

    private void setCenter(CharSequence centerText, int centerTextColor, float centerTextSize, Drawable centerDrawable) {
        if (centerDrawable != null) {
            centerDrawable.setBounds(0, 0, centerDrawable.getMinimumWidth(), centerDrawable.getMinimumHeight());
            tvCenter.setCompoundDrawables(centerDrawable, null, null, null);
        }
        tvCenter.setText(centerText);
        tvCenter.setTextSize(TypedValue.COMPLEX_UNIT_PX, centerTextSize);
        tvCenter.setTextColor(centerTextColor);
        tvCenter.setClickable(!TextUtils.isEmpty(centerText) || centerDrawable != null);
    }

    private void setRight(CharSequence rightText, int rightTextColor, float rightTextSize, Drawable rightDrawable) {
        if (rightDrawable != null) {
            rightDrawable.setBounds(0, 0, rightDrawable.getMinimumWidth(), rightDrawable.getMinimumHeight());
            tvRight.setCompoundDrawables(rightDrawable, null, null, null);
        }
        tvRight.setText(rightText);
        tvRight.setTextSize(TypedValue.COMPLEX_UNIT_PX, rightTextSize);
        tvRight.setTextColor(rightTextColor);
        tvRight.setClickable(!TextUtils.isEmpty(rightText) || rightDrawable != null);
    }

    /**
     * 将空间暴露出去 提供给外界
     * @return
     */
    public TextView getTvLeft() {
        return tvLeft;
    }

    /**
     * 将空间暴露出去 提供给外界
     * @return
     */
    public TextView getTvCenter() {
        return tvCenter;
    }

    /**
     * 将空间暴露出去 提供给外界
     * @return
     */
    public TextView getTvRight() {
        return tvRight;
    }

    //一些工具方法
    protected int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    protected int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }
⑤ 引用自定义控件

千万不要忘了在跟节点 标记命名空间

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ***xmlns:app="http://schemas.android.com/apk/res-auto"***
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.jack.customtitleview.customView.CustomTitleBar
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        ***app:TitleBar_background_color="#00ff00"
        app:TitleBar_left_text="中国"***/>

    <com.jack.customtitleview.customView.CustomTitleBar
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        ***app:TitleBar_background_color="#12b7f5"***
        ***app:TitleBar_left_Drawable="@drawable/ic_back_gray"
        app:TitleBar_left_text="中国"***
        ***app:TitleBar_right_Drawable="@drawable/ic_msg_gray"*** />

    <com.jack.customtitleview.customView.CustomTitleBar
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        ***app:TitleBar_background_color="#12b7f5"***
        ***app:TitleBar_left_Drawable="@drawable/ic_back_gray"
        app:TitleBar_right_Drawable="@drawable/ic_msg_gray"***>

        ***<include layout="@layout/layout_search_view" />***

    </com.jack.customtitleview.customView.CustomTitleBar>

</LinearLayout>

因为 我们继承的RelativeLayout 也是一个容器 所以可以引用 include

四 效果显示

这里写图片描述

源码地址 源码地址

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值