Android自定义View(三)

时间:2023-02-08 23:53:42

本文讲的是自定义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">
整体效果如下:

Android自定义View(三)




完整源码如下:

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群英传》


     对上面如有疑问,欢迎交流指出,共同进步