(二十九)自定义控件

一、创建自定义控件的3种主要实现方式:

1)继承已有的控件来实现自定义控件: 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。
2)通过继承一个布局文件实现自定义控件,一般来说做组合控件时可以通过这个方式来实现。
    注意此时不用onDraw方法,在构造广告中通过inflater加载自定义控件的布局文件,再addView(view),自定义控件的图形界面就加载进来了。
3)通过继承view类来实现自定义控件,使用GDI绘制出组件界面,一般无法通过上述两种方式来实现时用该方式。
二、属性文件中format可选项 
自定义控件就需要首先自定义该控件的属性。在开始前,我们需要检查在values目录下是否有attrs.xml,如果没有则创建。attrs.xml的例子如下所示:

<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="fustyle"> <attr name="text_msg" format="boolean"></attr> <attr name="text_custom" format="string"></attr> </declare-styleable> </resources>

format属性类型如下所示:

1)"reference" //引用
2)"color" //颜色
3) "boolean" //布尔值
4)"dimension" //尺寸值
5)"float" //浮点值
6)"integer" //整型值
7) "string" //字符串
8)"fraction" //百分数 比如200%
三、View结构原理
Android系统的视图结构的设计也采用了组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类。
 View定义了绘图的基本操作
基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。具体操作如下:
1、measure操作
     measure操作主要用于计算视图的大小,即视图的宽度和长度。在view中定义为final类型,要求子类不能修改。measure()函数中又会调用下面的函数:
     (1)onMeasure(),视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果。
 
2、layout操作
     layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作:
     (1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;
     (2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;
 
3、draw操作
     draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:
     (1)绘制背景;
     (2)如果要视图显示渐变框,这里会做一些准备工作;
     (3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
     (4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
     (5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
     (6)绘制滚动条;
      从上面可以看出自定义View需要最少覆写onMeasure()和onDraw()两个方法。
4、例子程序(主要转载于http://www.cnblogs.com/xiaohou/articles/2179092.html)
4.1 第一个实现一个带图片和文字的按钮,如图所示:

整个过程可以分四步走。第一步,定义一个layout,实现按钮内部的布局。代码如下:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="horizontal"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    >  
<ImageView  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:id="@+id/iv"  
    android:src="@drawable/confirm"  
    android:paddingTop="5dip"  
    android:paddingBottom="5dip"  
    android:paddingLeft="40dip"  
    android:layout_gravity="center_vertical"  
    />  
<TextView  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:text="确定"  
    android:textColor="#000000"  
    android:id="@+id/tv"  
    android:layout_marginLeft="8dip"  
    android:layout_gravity="center_vertical"  
    />  
</LinearLayout>  

这个xml实现一个左图右字的布局,接下来写一个类继承LinearLayout,导入刚刚的布局,并且设置需要的方法,从而使的能在代码中控制这个自定义控件内容的显示。代码如下:

 
package com.notice.ib;  
  
import android.content.Context;  
import android.util.AttributeSet;  
import android.view.LayoutInflater;  
import android.widget.ImageView;  
import android.widget.LinearLayout;  
import android.widget.TextView;  
  
public class ImageBt extends LinearLayout {  
  
    private ImageView iv;  
    private TextView  tv;  
    
//在代码里面创建对象的时候,使用此构造方法
public ImageBt(Context context) { this(context, null); }
//在布局文件中声明的view,创建时由系统自动调用
public ImageBt(Context context, AttributeSet attrs) { super(context, attrs); // 导入布局 LayoutInflater.from(context).inflate(R.layout.custombt, this, true); iv = (ImageView) findViewById(R.id.iv); tv = (TextView) findViewById(R.id.tv); } /** * 设置图片资源 */ public void setImageResource(int resId) { iv.setImageResource(resId); } /** * 设置显示的文字 */ public void setTextViewText(String text) { tv.setText(text); } }

第三步,在需要使用这个自定义控件的layout中加入这控件,只需要在xml中加入即可。方法如下:

<RelativeLayout  
         android:orientation="horizontal"  
         android:layout_width="fill_parent"  
         android:layout_height="wrap_content"  
         android:layout_gravity="bottom"  
         >  
         <com.notice.ib.ImageBt  
             android:id="@+id/bt_confirm"  
             android:layout_height="wrap_content"  
             android:layout_width="wrap_content"  
             android:layout_alignParentBottom="true"  
             android:background="@drawable/btbg"  
             android:clickable="true"  
             android:focusable="true"  
             />  
         <com.notice.ib.ImageBt  
             android:id="@+id/bt_cancel"  
             android:layout_toRightOf="@id/bt_confirm"  
             android:layout_height="wrap_content"  
             android:layout_width="wrap_content"  
             android:layout_alignParentBottom="true"  
             android:background="@drawable/btbg"  
             android:clickable="true"  
             android:focusable="true"  
            />  
         </RelativeLayout>  

注意的是,控件标签使用完整的类名即可。为了给按钮一个点击效果,你需要给他一个selector背景,这里就不说了。

最后一步,即在activity中设置该控件的内容。当然,在xml中也可以设置,但是只能设置一个,当我们需要两次使用这样的控件,并且显示内容不同时就不行了。在activity中设置也非常简单,我们在ImageBt这个类中已经写好了相应的方法,简单调用即可。代码如下

public class MainActivity extends Activity {  
  
    private ImageBt ib1;  
    private ImageBt ib2;  
  
    /** Called when the activity is first created. */  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.login);  
  
        ib1 = (ImageBt) findViewById(R.id.bt_confirm);  
        ib2 = (ImageBt) findViewById(R.id.bt_cancel);  
  
        ib1.setTextViewText("确定");  
        ib1.setImageResource(R.drawable.confirm);  
        ib2.setTextViewText("取消");  
        ib2.setImageResource(R.drawable.cancel);  
  
        ib1.setOnClickListener(new OnClickListener() {  
  
            @Override  
            public void onClick(View v) {  
                    //在这里可以实现点击事件  
            }  
        });  
  
    }  
}  

这样,一个带文字和图片的组合按钮控件就完成了。这样梳理一下,使用还是非常简单的。组合控件能做的事还非常多,主要是在类似上例中的ImageBt类中写好要使用的方法即可。

4.2 再来看一个组合控件,带删除按钮的EidtText。即在用户输入后,会出现删除按钮,点击即可取消用户输入。

定义方法和上例一样。首先写一个自定义控件的布局:

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    >  
<EditText  
    android:id="@+id/et"  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"  
    android:singleLine="true"  
    />  
<ImageButton  
    android:id="@+id/ib"  
    android:visibility="gone"  
    android:src="@drawable/menu_delete"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:background="#00000000"  
    android:layout_alignRight="@+id/et" />  
</RelativeLayout>  

实现输入框右侧带按钮效果,注意将按钮隐藏。然后写一个EditCancel类,实现删除用户输入功能。这里用到了TextWatch这个接口,监听输入框中的文字变化。使用也很简单,实现他的三个方法即可。看代码:

package com.example.customdeleteedittext;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;

public class EditCancel extends LinearLayout implements EdtInterface {
    ImageButton ib;
    EditText et;

    public EditCancel(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.custom_editview, this,
                true);
        init();
    }

    public EditCancel(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    private void init() {
        // TODO Auto-generated method stub
        ib = (ImageButton) findViewById(R.id.ib);
        et = (EditText) findViewById(R.id.et);
        et.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before,
                    int count) {
                // TODO Auto-generated method stub

            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,
                    int after) {
                // TODO Auto-generated method stub

            }

            @Override
            public void afterTextChanged(Editable s) {
                // TODO Auto-generated method stub
                if (s.length() == 0) {
                    hideBtn();// 隐藏按钮
                } else {
                    showBtn();// 显示按钮
                }
            }
        });
        ib.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                hideBtn();// 隐藏按钮
                String str = et.getText().toString().trim();
                String newStr = str.substring(0, str.length() - 1);
                et.setText(newStr);// 设置输入框内容
            }
        });
    }

    @Override
    public void hideBtn() {
        // TODO Auto-generated method stub
        if (ib.isShown())
            ib.setVisibility(View.GONE);
    }

    @Override
    public void showBtn() {
        // TODO Auto-generated method stub
        if (!ib.isShown())
            ib.setVisibility(View.VISIBLE);
    }

}

interface EdtInterface {

    public void hideBtn();

    public void showBtn();

}

在TextWatch接口的afterTextChanged方法中对文字进行判断,若长度为0,就隐藏按钮,否则,显示按钮。

另外,实现ImageButton(即那个叉)的点击事件,删除输入框中的内容,并隐藏按钮。

posted @ 2014-12-23 11:22  小菜美妞成长中  阅读(209)  评论(0编辑  收藏  举报