本文讲的是自定义View的第二种方式-----创建复合控件
创建复合(组合)可以很好的创建出具有重用功能的控件集合。这种方式通常需要继承一个ViewGroup,再给它添加指定功能的控件,从而组合成新的复合控件。通过这种方式创建的控件,我们一般会给它指定一些可配置的属性,让它具有更强的扩展性。本文参考《Android群英传》中的例子,算是笔记吧。通过这个例子,熟悉了自定义属性的配置以及接口回调的方式。
有时程序为了风格的统一,很多应用程序都有一些共同的UI界面,比如常见的标题TopBar,一般左上角为返回按钮,中间为文字说明,右上角为其他功能(或没有)。
通常情况下,这些界面都会被抽象出来,形成一个共通的UI组件。所有需要添加标题的界面都会引用这样的一个TopBar,而不是每个界面都在布局文件中写这样一个TopBar.同时设计者还可以给TopBar增加相应的接口,让调用者能够更加灵活地控制TopBar,这样可以提高界面的复用率,更能在需要修改UI时,做到快速修改,而不用对每个页面的标题去进行修改。
1.设置自定义的属性
为一个View提供可自定义的属性很简单,在res/values文件下,新建一个atts.xml文件,并在文件中通过代码来定义属性即可。
atts.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Topbar"> <attr name="MyTitle" format="string"/><!--format表示以后在xml文件中所引用的资源类型--> <attr name="titleTextSize" format="dimension"/> <attr name="titleTextColor" format="color"/> <attr name="leftTextColor" format="color"/> <attr name="leftBackground" format="reference|color"/> <attr name="leftText" format="string"/> <attr name="rightTextColor" format="color"/> <attr name="rightBackground" format="reference|color"/> <attr name="rightText" format="string"/> </declare-styleable> </resources>其中<declare-styleable>标签声明了使用自定义属性,并通过name属性来确定引用的名称。最后通过<attr>标签来声明具体的自定义的属性,比如在这里定义了标题文字的字体、大小、颜色,左边按钮和右边按钮的文字颜色、背景、字体等属性,通过format属性来指定属性的类型。这里需要注意的是,有些属性可以是颜色属性,也可以是引用属性。比如按钮的背景,可以把它指定为具体的颜色,也可以把它指定为图片,所以可以用“|”来分割不同的属性,“reference|color”,reference表示引用。
在确定好属性后,就可以创建一个自定义控件----TopBar,并让它继承自ViewGroup,从而组合一些需要的控件。这里为了简单,我们继承RelativeLayout。在构造方法中,通过如下所示的代码来获取在xml布局文件中自定义的那些属性,即与我们使用系统提供的那些属性一样。
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.Topbar);其中R.styleble.Topbar中的Topbar即为atts文件中声明的名字。系统提供了TypedArray这样的数据结构来获取自定义属性集,通过一系列的get方法,就可以获取这些自定义的属性值,代码如下所示:
在构造方法中去获取属性
import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; public class TopBar extends RelativeLayout { private int leftTextColor; private Drawable leftBackground; private String leftText; private int rightTextColor; private Drawable rightBackground; private String rightText; private float titleTextSize; private int titleTextColor; private String MyTitle; public TopBar(final Context context, AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Topbar); leftTextColor = ta.getColor(R.styleable.Topbar_leftTextColor, 0); leftBackground = ta.getDrawable(R.styleable.Topbar_leftBackground); leftText = ta.getString(R.styleable.Topbar_leftText); rightTextColor = ta.getColor(R.styleable.Topbar_rightTextColor, 0); rightBackground = ta.getDrawable(R.styleable.Topbar_rightBackground); rightText = ta.getString(R.styleable.Topbar_rightText); titleTextSize = ta.getDimension(R.styleable.Topbar_titleTextSize, 0); titleTextColor = ta.getColor(R.styleable.Topbar_titleTextColor, 0); MyTitle = ta.getString(R.styleable.Topbar_MyTitle); ta.recycle();} }我们在两个参数的构造方法中去获取这些自定义的属性,接下来是初始化和组合这些控件。
2.组合控件 整个TopBar实际上由3个控件组成,左边点击的按钮leftButton,rightButton,中间的标题栏tvTitle。通过动态添加控件的方式,使用addView()方法将这3个控件假如到定义的TopBar模版中,并给它们设置我们前面获取到的具体的属性值。这些同样是写在先前的构造函数中。
leftButton = new Button(context); rightButton = new Button(context); tvTitle = new TextView(context); leftButton.setTextColor(leftTextColor); leftButton.setBackground(leftBackground); leftButton.setText(leftText); rightButton.setTextColor(rightTextColor); rightButton.setBackground(rightBackground); rightButton.setText(rightText); tvTitle.setText(MyTitle); tvTitle.setTextColor(titleTextColor); tvTitle.setTextSize(titleTextSize); tvTitle.setGravity(Gravity.CENTER); setBackgroundColor(0xFFF59563); leftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); leftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE); addView(leftButton, leftParams); rightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); rightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE); addView(rightButton, rightParams); titleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); titleParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE); addView(tvTitle, titleParams);既然是模版,怎么样给左右的按钮设置点击事件呢?因为每个调用者所处的环境不同,不可能直接在UI模版中添加具体的实现逻辑,这里就要用到的就是接口回调的思想了,将具体的实现逻辑交给调用者。关于接口回调,可以参考我以前写的文章: java笔记----什么是接口回调,怎么用
(1)首先定义接口
//使用了接口的回调机制,这样具体的实现逻辑,交给调用者 public interface TopBarClickListener { public void leftClick(); public void rightClick(); }(2)暴露接口给调用者
在模版方法中,为左右按钮增加点击事件,但不去实现具体的逻辑,而是调用接口中响应的点击方法,相应代码如下:
leftButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Toast.makeText(context, "左边bt", 0).show(); topBarClickListener.leftClick(); } }); rightButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Toast.makeText(context, "右边bt", 0).show(); topBarClickListener.rightClick(); } });暴露调用的接口方法
//暴露一个方法给其他地方调用 public void setTopBarClickListener(TopBarClickListener listener) { this.topBarClickListener = listener; }
(3)实现接口的回调
TopBar topBar = (TopBar) findViewById(R.id.topbar); topBar.setTopBarClickListener(new TopBarClickListener() { @Override public void rightClick() { // TODO 自动生成的方法存根 Toast.makeText(MainActivity.this, "获取更多", 0).show(); } @Override public void leftClick() { // TODO 自动生成的方法存根 Toast.makeText(MainActivity.this, "点击取消", 0).show(); } }); }当然,可以使用公共方法来动态地修改UI模版中的UI,这样就进一步的提高了模版的可扩展性。实现如下:
//当然可以设置更多控件的属性 这里是以方法的形式设置控件的属性 public void setLeftButtonVisible(boolean b) { if (b) { leftButton.setVisibility(View.VISIBLE); }else { leftButton.setVisibility(View.GONE); } }
调用的时候
topBar.setLeftButtonVisible(false);通过方法可以设置更多自定义属性
3.引用UI模版
在需要使用的地方引用UI模版,在引用前,需要指定引用第三方控件的名字空间。在布局文件中,要添加如下一行代码:
xmlns:custom="http://schemas.android.com/apk/res/com.example.myview"如果你使用的是Android Studio的IDE,后面不用详细的写清楚包名,可以写成 ......apk/res/res-auto
上面的代码指定了引用的名字xmlns(xml name space)。这里指定了名字的空间为custom,这个名字你可以随便取,后面在引用控件的地方会用到这个名字。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:custom="http://schemas.android.com/apk/res/com.example.myview" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.myview.MainActivity" > <!--注意上面自定义控件的命名空间xmlns --> <com.example.myview.TopBar android:id="@+id/topbar" android:layout_width="match_parent" android:layout_height="40dp" custom:MyTitle="这个是标题" custom:leftBackground="@drawable/img_back2" custom:leftText="返回" custom:leftTextColor="#FF3FF1" custom:rightBackground="@drawable/ic_launcher" custom:rightText="更多" custom:rightTextColor="#cccccc" custom:titleTextColor="#ffff0000" custom:titleTextSize="15sp" > </com.example.myview.TopBar> </RelativeLayout>
通过如上代码,可以直接通过<include>标签来引用这个UI模版View。
<include layout="@layout/topbar">整体效果如下:
完整源码如下:
atts.xml文件上面有,就不贴了
MainActivity.java
package com.example.myview; import com.example.myview.TopBar.TopBarClickListener; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TopBar topBar = (TopBar) findViewById(R.id.topbar); topBar.setTopBarClickListener(new TopBarClickListener() { @Override public void rightClick() { // TODO 自动生成的方法存根 Toast.makeText(MainActivity.this, "获取更多", 0).show(); } @Override public void leftClick() { // TODO 自动生成的方法存根 Toast.makeText(MainActivity.this, "点击取消", 0).show(); } }); //topBar.setLeftButtonVisible(false);通过方法可以设置更多自定义属性 } }
TopBar.java
package com.example.myview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; /** * Created by Administrator on 2015/12/28. */ public class TopBar extends RelativeLayout { private Button leftButton, rightButton; private TextView tvTitle; private int leftTextColor; private Drawable leftBackground; private String leftText; private int rightTextColor; private Drawable rightBackground; private String rightText; private float titleTextSize; private int titleTextColor; private String MyTitle; private LayoutParams leftParams, rightParams, titleParams; TopBarClickListener topBarClickListener; public TopBar(final Context context, AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Topbar); leftTextColor = ta.getColor(R.styleable.Topbar_leftTextColor, 0); leftBackground = ta.getDrawable(R.styleable.Topbar_leftBackground); leftText = ta.getString(R.styleable.Topbar_leftText); rightTextColor = ta.getColor(R.styleable.Topbar_rightTextColor, 0); rightBackground = ta.getDrawable(R.styleable.Topbar_rightBackground); rightText = ta.getString(R.styleable.Topbar_rightText); titleTextSize = ta.getDimension(R.styleable.Topbar_titleTextSize, 0); titleTextColor = ta.getColor(R.styleable.Topbar_titleTextColor, 0); MyTitle = ta.getString(R.styleable.Topbar_MyTitle); ta.recycle(); leftButton = new Button(context); rightButton = new Button(context); tvTitle = new TextView(context); leftButton.setTextColor(leftTextColor); leftButton.setBackground(leftBackground); leftButton.setText(leftText); rightButton.setTextColor(rightTextColor); rightButton.setBackground(rightBackground); rightButton.setText(rightText); tvTitle.setText(MyTitle); tvTitle.setTextColor(titleTextColor); tvTitle.setTextSize(titleTextSize); tvTitle.setGravity(Gravity.CENTER); setBackgroundColor(0xFFF59563); leftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); leftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE); addView(leftButton, leftParams); rightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); rightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE); addView(rightButton, rightParams); titleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); titleParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE); addView(tvTitle, titleParams); leftButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Toast.makeText(context, "左边bt", 0).show(); topBarClickListener.leftClick(); } }); rightButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Toast.makeText(context, "右边bt", 0).show(); topBarClickListener.rightClick(); } }); } //使用了接口的回调机制 public interface TopBarClickListener { public void leftClick(); public void rightClick(); } //暴露一个方法给其他地方调用 public void setTopBarClickListener(TopBarClickListener listener) { this.topBarClickListener = listener; } //当然可以设置更多控件的属性 这里是以方法的形式设置控件的属性 public void setLeftButtonVisible(boolean b) { if (b) { leftButton.setVisibility(View.VISIBLE); }else { leftButton.setVisibility(View.GONE); } } }
本文参考《Android群英传》
对上面如有疑问,欢迎交流指出,共同进步