Andriod 自定义控件之创建可以复用的组合控件

前面已学习了一种自定义控件的实现,是Andriod 自定义控件之音频条,还没学习的同学可以学习下,学习了的同学也要去温习下,一定要自己完全的掌握了,再继续学习,贪多嚼不烂可不是好的学习方法,我们争取学习了一种技术就会一种技术,而且不光看了就算了,最好的方法就是看完我自己再练习下,再扩展下,在原来的基础上在添加一些东西,比如,增加一些功能实现等等。

今天我们打算学习下另外一种自定义控件,就是创建可重复使用的组合控件,那么问题来了:

  • 什么是可重复使用?
    就是在应用中,可以在多个地方共同使用一套代码。这样不仅能减少我们的工作量,而且还能保持应用风格的一致,这种应用最多最直接的体现就是统一风格样式的标题栏。
  • 那什么又是组合控件呢?
    组合控件,顾名思义就是多个控件组合在一起,相互协作共同完成某些特定的功能。

下面我们就针对app应用中风格统一的标题栏来开始我们的学习。

首先,既然是一组组合的控件,那就必须有一个可以来包含这些控件的容器,我们所接触的可以存放控件的容器很多,比如LinearLayout、RelativeLayout等等多种Layout,今天我们就选择RelativeLayout来做我们的容器。和以前一样,我们先定义一个CompositeViews类来继承RelativeLayout类,并重写它的构造方法,代码如下:

public class CompositeViews extends RelativeLayout{

    public CompositeViews(Context context) {
        this(context,null);
    }
    public CompositeViews(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CompositeViews(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
 }

接下来,我们再定义三个TextView控件,分别是mLefeText,mRightText,textTitle用来显示“返回”,“搜索”以及“标题”并且使用LayoutParams规定它们在容器里面的对齐方式。请看代码:

public class CompositeViews extends RelativeLayout{
    private TextView mLefeText;
    private TextView mRightText;
    private TextView textTitle;
    private LayoutParams leftLayoutParams;
    private LayoutParams ridhtLayoutParams;
    private LayoutParams titleLayoutParams;

    public CompositeViews(Context context) {
        this(context,null);
    }
    public CompositeViews(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CompositeViews(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

initView(context)方法中是用来初始化三个组合控件的,请看:

	private void initView(Context context) {
        mLefeText = new TextView(context);
        mRightText = new TextView(context);
        textTitle = new TextView(context);
		/*
         * 左按钮位置
         */
        leftLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        leftLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
        mLefeText.setText("返回");
        mLefeText.setTextSize(22);
		/*
         * 右按钮位置
         */
        ridhtLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        ridhtLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
        mRightText.setText("搜索");
        mRightText.setTextSize(22);
		/*
         * 中间标题位置
         */
        titleLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        titleLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
        textTitle.setText("这是一个标题");
		textTitle.setTextSize(22);
	}

ok,以上的代码已经实现了组合控件的显示和对齐方式,我们把定义的View添加到布局文件中并在Activity加载吧

activity_main.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:background="#999999"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <com.sanhuimusic.mycustomview.view.CompositeViews
        android:id="@+id/topBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</LinearLayout>

MainActivity:

public class MainActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

我们先来运行看下结果

这里写图片描述

ok,已显示出来了,但是相信大家也看出来了,这上面的代码中,各个控件中的属性是都是我们固定写死的,既然我们是创建可服用的控件,固定写死的东西肯定是不可取的,那么我们怎么可以灵活地获取控件的属性,以至于能达到复用呢?

这就必须要接触另外一种技术了,就是自定义属性。用我们自定义的属于可以在每次使用我们定义的控件时为其分配属性即可。下面我们来学习下自定义属性。

自定义属性其实也是相当的简单,首先,我们现在资源文件res下values目录下新建一个attrs.xml文件(eclipse自带,as自建),新建的attrs.xml是一个包含如下代码的文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>

</resources>

在resources中有各种属性供我们使用,同学们可以自己看下。根据我们现在的需求,我们选择使用declare-styleable来声明我们的属性集,然后为其定义特有的name属性,这个name是供我们在使用自定义属性时,通过它可以查找到里面的所有属性。请看如下代码:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CompositeViews">
        <attr name="titleText" format="string"/>
        <attr name="titleTextSize" format="dimension"/>
        <attr name="titleColor" format="color"/>
        <attr name="titleBackground" format="color|reference"/>

        <attr name="leftTextColor" format="color"/>
        <attr name="leftBackground" format="color|reference"/>
        <attr name="leftText" format="string"/>
        <attr name="leftTextSize" format="dimension"/>

        <attr name="rightTextColor" format="color"/>
        <attr name="rightBackground" format="color|reference"/>
        <attr name="rightText" format="string"/>
        <attr name="rightTextSize" format="dimension"/>
    </declare-styleable>

</resources>

单独拿一行属性来解析下它所代表的含义:如 :attr代表的是一个属性,它里面所包含name字段是这条属性名,通过该属性名可以获取以format的约束为真的属性值;formate是该属性的格式,分别包含string,dimension,color,reference等等,分别代表字符串,大小,颜色,引用。其他的大家可以自行学习resources所包含的属性。

好了,自定义属性我们已学习完毕,那么该怎么使用我们自己定义的属性呢?其实也很简单,在我们的activity_main.xml文件中直接使用我们定义的属性就可以了,但是在使用是之前必须在指定引用第三方控件的命名空间,在跟布局文件中添加如下一行代码:

xmlns:custom="http://schemas.android.com/apk/res-auto"

custom是我们第三方命名空间的名字,可以任意命名,我们在使用自定义属性时必须以它开头。请看代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:background="#999999"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <com.sanhuimusic.mycustomview.view.CompositeViews
        android:id="@+id/topBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        custom:titleText="@string/titleText"
        custom:titleColor="#000000"
        custom:titleTextSize="@dimen/titleTextSize"
        custom:titleBackground="#999999"
        custom:leftText="@string/leftText"
        custom:leftTextColor="#FFFFFF"
        custom:leftBackground="#666666"
        custom:leftTextSize="@dimen/leftTextSize"
        custom:rightText="@string/rightText"
        custom:rightTextColor="#FFFFFF"
        custom:rightBackground="#666666"
        custom:rightTextSize="@dimen/rightTextSize"
        />
 
</LinearLayout>

我们是使用custom加上我们自定义属性里面< attr name="titleText" format="string"/>里的name值来动态设置属性值的,如:custom:titleText="@string/titleText"。

ok,在我们xml文件中已设定好属性值,那么该怎么显示出来呢?这个是需要通过一个类型组TypedArray来获取的,它里面包含各种从AttributeSet属性集中获取属性的方法,所以我们修改上面的构造方法和initView(context)方法,如下所示:

    private void initView(Context context, AttributeSet attrs) {
        mLefeText = new TextView(context);
        mRightText = new TextView(context);
        textTitle = new TextView(context);

        /**
         * 获取自定义属性
         */
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompositeViews);
        String titleText = typedArray.getString(R.styleable.CompositeViews_titleText);
        float titleTextSize = typedArray.getDimension(R.styleable.CompositeViews_titleTextSize, 16);
        int titleColor = typedArray.getColor(R.styleable.CompositeViews_titleColor,0);
        Drawable titleBackground = typedArray.getDrawable(R.styleable.CompositeViews_titleBackground);

        String leftText = typedArray.getString(R.styleable.CompositeViews_leftText);
        int leftTextColor = typedArray.getColor(R.styleable.CompositeViews_leftTextColor, 0);
        float leftTextSize = typedArray.getDimension(R.styleable.CompositeViews_leftTextSize, 16);
        Drawable leftBackground = typedArray.getDrawable(R.styleable.CompositeViews_leftBackground);

        String rightText = typedArray.getString(R.styleable.CompositeViews_rightText);
        int rightTextColor = typedArray.getColor(R.styleable.CompositeViews_rightTextColor, 0);
        float rightTextSize = typedArray.getDimension(R.styleable.CompositeViews_rightTextSize, 16);
        Drawable rightBackground = typedArray.getDrawable(R.styleable.CompositeViews_rightBackground);
        typedArray.recycle();
        /*
         * 左按钮位置
         */
        leftLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        leftLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,TRUE);
	    mLefeText.setText(leftText);
        mLefeText.setTextColor(leftTextColor);
        mLefeText.setTextSize(leftTextSize);
        mLefeText.setBackground(leftBackground);
        addView(this.mLefeText,leftLayoutParams);
         /*
         * 右按钮位置
         */
        ridhtLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        ridhtLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,TRUE);
        mRightText.setText(rightText);
        mRightText.setTextColor(rightTextColor);
        mRightText.setTextSize(rightTextSize);
        mRightText.setBackground(rightBackground);
        addView(mRightText,ridhtLayoutParams);
        /*
         * 中间标题位置
         */
        titleLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        titleLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
        textTitle.setText(titleText);
        textTitle.setTextSize(titleTextSize);
        textTitle.setTextColor(titleColor);
        textTitle.setBackground(titleBackground);
        addView(textTitle,titleLayoutParams);
    }

代码解释:首先通过上下文context获取到属性存放到TypedArray 中,然后通过TypedArray 里封装好的各种方法获取对应的属性值,然后再分别为我们的控件设置属性。这样就完成了,自定义属性的使用,并且复用度高,每当需要使用标题栏是都只需要在xml中添加我们定义的View控件,为其配置属性即可使用,节约了开发时间,提高了效率,并且还保持的app风格的一致。

好,到这里感觉已经讲完了整个过程吧,其实还有一个重要的实现还没有讲。我们的控件已经可以呈现出来了,但是怎么完成里面控件的作用呢?

这里比较常见的做法是利用回调机制来实现功能的开发,首先我们先定义一个接口,创建两个方法,用于左右控件的点击事件。

	public interface TopBarClickListener{
            void leftClickListener();
            void rightClickListener();
    }

然后在构造方法中为左右控件添加点击事件,但不实现功能,等待调用者自己实现:

 private void setListener() {
        mLefeText.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mTopBarClickListener.leftClickListener();
            }
        });
        mRightText.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mTopBarClickListener.rightClickListener();
            }
        });
    }

再者,把定义好的接口暴露给调用者:

 public void setOnTopBarClickListener(TopBarClickListener topBarClickListener){
        mTopBarClickListener = topBarClickListener;
    }

最后,谁调用,谁实现。这就完成了不同界面复用控件实现不同的功能的便利。在这里我们只在MainActivity中打印Toast就可以了。

public class MainActivity extends AppCompatActivity {
    private CompositeViews topBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        topBar = (CompositeViews) findViewById(R.id.topBar);
        topBar.setOnTopBarClickListener(new CompositeViews.TopBarClickListener(){
            @Override
            public void leftClickListener() {
                ToastUtil.makeText(MainActivity.this,"您点击了返回键",Toast.LENGTH_SHORT).show();
            }
            @Override
            public void rightClickListener() {
                ToastUtil.makeText(MainActivity.this,"您点击了搜索键",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

OK,看看结果吧

这里写图片描述

好,已经可以实现我们的需求了,是不是学会很多呢。

今天主要讲了android自定义View中另一种的实现,并且还学习了自定义属性,同学们下去好好消化下,并自己动手现实一两个例子吧,好了,今天就讲到这里,谢谢大家。

更多资讯请关注微信平台,有博客更新会及时通知。爱学习爱技术。

这里写图片描述

posted @ 2016-11-30 08:47  管满满  阅读(564)  评论(0编辑  收藏  举报