Android自定义View(三)

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

本文讲的是自定义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群英传》


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