本文讲的是自定义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"?>其中<declare-styleable>标签声明了使用自定义属性,并通过name属性来确定引用的名称。最后通过<attr>标签来声明具体的自定义的属性,比如在这里定义了标题文字的字体、大小、颜色,左边按钮和右边按钮的文字颜色、背景、字体等属性,通过format属性来指定属性的类型。这里需要注意的是,有些属性可以是颜色属性,也可以是引用属性。比如按钮的背景,可以把它指定为具体的颜色,也可以把它指定为图片,所以可以用“|”来分割不同的属性,“reference|color”,reference表示引用。
<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>
在确定好属性后,就可以创建一个自定义控件----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);既然是模版,怎么样给左右的按钮设置点击事件呢?因为每个调用者所处的环境不同,不可能直接在UI模版中添加具体的实现逻辑,这里就要用到的就是接口回调的思想了,将具体的实现逻辑交给调用者。关于接口回调,可以参考我以前写的文章: java笔记----什么是接口回调,怎么用
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);
(1)首先定义接口
//使用了接口的回调机制,这样具体的实现逻辑,交给调用者(2)暴露接口给调用者
public interface TopBarClickListener {
public void leftClick();
public void rightClick();
}
在模版方法中,为左右按钮增加点击事件,但不去实现具体的逻辑,而是调用接口中响应的点击方法,相应代码如下:
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);当然,可以使用公共方法来动态地修改UI模版中的UI,这样就进一步的提高了模版的可扩展性。实现如下:
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();
}
});
}
//当然可以设置更多控件的属性 这里是以方法的形式设置控件的属性
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群英传》
对上面如有疑问,欢迎交流指出,共同进步