欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android中View自定义组合控件的基本编写方法

程序员文章站 2024-02-29 23:17:58
有很多情况下,我们只要运用好android给我提供好的控件,经过布局巧妙的结合在一起,就是一个新的控件,我称之为“自定义组合控件”。 那么,这种自定义组合控件在什么情况下...

有很多情况下,我们只要运用好android给我提供好的控件,经过布局巧妙的结合在一起,就是一个新的控件,我称之为“自定义组合控件”。

那么,这种自定义组合控件在什么情况下用呢?或者大家在做项目时候会发现,某些布局会被重复的利用,同一个布局的xml代码块会被重复的复制黏贴多次,这样会造成代码结构混乱不说,代码量也会增大,各种控件都需要在java代码中被申明和处理相应的逻辑,工作量着实不小,所以,必须要找到一个合理的“偷懒”的方式,开动脑经去怎么简化以上说的不必要的麻烦。下面看一张图,就一个简单的布局,我们就此图来实现一个简单的自定义组合控件。

Android中View自定义组合控件的基本编写方法

从上面的图来分析,我们可以看到,这个布局里面是没有“全新”的控件的,用的都是android系统原生的控件。熟悉android界面布局的人,肯定觉得这种布局真是小case,太简单了,分分钟就可以写完。于是下面就是某一个条目的布局代码:

<!--?xml version=1.0 encoding=utf-8?-->
<relativelayout android:background="@drawable/selector_blue" android:id="@+id/rl_show_address" android:layout_height="60dip" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
 
  <textview android:id="@+id/tv_title" android:layout_height="wrap_content" android:layout_marginleft="5dip" android:layout_margintop="1dip" android:layout_width="wrap_content" android:text="这是标题" android:textcolor="#000000" android:textsize="20sp">
 
  <textview android:id="@+id/tv_desc" android:layout_below="@id/tv_title" android:layout_height="wrap_content" android:layout_marginleft="6dip" android:layout_margintop="1dip" android:layout_width="wrap_content" android:text="这是描述内容" android:textcolor="#99ff0000" android:textsize="14sp">
 
  <checkbox android:clickable="false" android:focusable="false" android:id="@+id/cb_status" android:layout_alignparentright="true" android:layout_centervertical="true" android:layout_height="wrap_content" android:layout_width="wrap_content">
   
  <!-- 加一条分割线 -->
  <view android:background="#000000/" android:layout_alignbottom="@id/cb_status" android:layout_alignparentbottom="true" android:layout_height="0.2dip" android:layout_margintop="7dip" android:layout_width="match_parent">
 
</view></checkbox></textview></textview></relativelayout>

可以看到,这种布局确实相当的简单。但是,这时候产品经理告诉你,需求改了,我们需要在这个界面再加一个这样的条目,于是你觉得,小意思,ctrl+c,ctrl+v,轻松搞定,然后改一下控件的id,在java代码中findviewbyid(id),加一段逻辑代码,搞完收工。没想到这时候产品又来了,需求改了,这里需要加10个这样的布局,于是你...诚然,这时候再ctrl+c,ctrl+v是不合适的,工作量就显得很大了,即使你不嫌麻烦的话,照样做了,你料不到产品会再来,那个给我删掉几个,那个再加上几个,是不是要疯了。
 

也许,我们可以相出一个偷懒的方法来呢。通过分析上面的布局,可以发现,布局上每一个子条目是不变的,布局完全一样,唯一在变化的是,红色的textview上的文本随着checkbox的状态再改变着,而这种变化,我们是否可以想办法抽取到某个方法中呢,答案是肯定能的。我们可以将这种子条目的布局一次性封装到一个java类中,每次调用这个控件的时候,事先设定各种属性数据即可,这里涉及到了自定义属性了。分析一下这个属性集该怎么定义,从上面的图片可以看出,控件上需要设置的内容分别是,上面textview的标题,还有下面textview的描述信息,且描述信息是根据checkbox的状态发生改变的,所以这两种状态(true或false)都需要被定义到属性集里去,于是属性集就有了。

在工程下的res/values目录下,新建attrs.xml文件,定义如下属性集:

<!--?xml version=1.0 encoding=utf-8?-->
<resources>
 
  <declare-styleable name="combinationview">
    </attr>
    </attr>
    </attr>
  </declare-styleable>
 
</resources>

定义好了属性集了,接下来我们就需要定义一个java类,来渲染这段布局,解析这个属性集,并且对象提供修改控件状态的方法,已达到复用的效果。问题来了,我们定义的这个java类需要继承哪个类呢?在这里,我们不必考虑view了,因为这里不是全新自定义控件,不需要onmessure和ondraw去测量去画一个视图。那么viewgroup呢?我们也不必用这个类,因为这里的布局是给定好的,不需要使用onlayout给子控件设置显示的位置。那么,该继承什么呢?我们可以想象一下viewgroup的子类是不是可以呢?实现自定义控件的除了继承view和viewgroup之外,还可以直接继承android已有的控件进行修改,这个用面向对象的思想,应该不难想象吧。由于,该布局文件用的相对布局relativelayout,我们想当然可以自定义java类去继承这个relativelayout,relativelayout里提供一些参数和方法方便我们去实现子控件的布局。但是,我们这里直接在子控件布局已经写好了,不需要使用relativelayout提供的参数和方法来布局了。所以,导致了,即使不去继承relativelayout,而改成linearlayout,framelayout...也是可以的,只要这个布局类是viewgroup的子类就行。以下是这个自定义组合控件的实现代码:

package com.example.combinationview;
 
import android.content.context;
import android.util.attributeset;
import android.view.view;
import android.widget.checkbox;
import android.widget.relativelayout;
import android.widget.textview;
 
public class combinationview extends relativelayout {
 
  private textview tv_title;
  private textview tv_desc;
  private checkbox cb_status;
  // 命名空间,在引用这个自定义组件的时候,需要用到
  private string namespace = http://schemas.android.com/apk/res/com.example.combinationview;
  // 标题
  private string title;
  // 被选中的描述
  private string desc_on;
  // 未被选中的描述
  private string desc_off;
 
  public combinationview(context context, attributeset attrs) {
    super(context, attrs);
    // 将自定义组合控件的布局渲染成view
    view view = view.inflate(context, r.layout.layout_combinationview, this);
    tv_title = (textview) view.findviewbyid(r.id.tv_title);
    tv_desc = (textview) view.findviewbyid(r.id.tv_desc);
    cb_status = (checkbox) view.findviewbyid(r.id.cb_status);
 
    title = attrs.getattributevalue(namespace, title);
    desc_on = attrs.getattributevalue(namespace, desc_on);
    desc_off = attrs.getattributevalue(namespace, desc_off);
    system.out.println(title + : + desc_on + : + desc_off);
    // 初始化到子控件
    if (title != null) {
      tv_title.settext(title);
    }
    if (desc_off != null) {
      tv_desc.settext(desc_off);
    }
  }
 
  /**
   * 判断是否被选中
   * 
   * @return
   */
  public boolean ischecked() {
    return cb_status.ischecked();
  }
 
  /**
   * 设置选中的状态
   * 
   * @param ischecked
   */
  public void setchecked(boolean ischecked) {
    cb_status.setchecked(ischecked);
    if (ischecked) {
      tv_desc.settext(desc_on);
    } else {
      tv_desc.settext(desc_off);
    }
  }
 
}

代码很简单,首先继承relativelayout,复写其构造方法,在构造方法中先渲染布局的视图,然后读取属性集的属性,将默认显示的属性显示到布局上的子控件上即可。另外,还要对外提供一个判断状态的方法ischecked()来判断该控件是否被选中了,提供一个设置状态的方法setchecked(boolean),用来改变状态。ps:为了验证我上面的一段话,读者可以将继承relativelayout,改为继承linearlayout或者继承framelayout,运行试试看,也是可以实现的。
 

下面是引用这个自定义组合控件的方法,首先需要在activity的布局文件中定义出来:

<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:example="http://schemas.android.com/apk/res/com.example.combinationview">
 
  <com.example.combinationview.combinationview android:id="@+id/cv_first" android:layout_height="wrap_content" android:layout_width="match_parent" example:desc_off="我是未被选中的描述1" example:desc_on="我是被选中的描述1" example:title="我是标题1">
  </com.example.combinationview.combinationview>
 
  <com.example.combinationview.combinationview android:id="@+id/cv_second" android:layout_height="wrap_content" android:layout_width="match_parent" example:desc_off="我是未被选中的描述2" example:desc_on="我是被选中的描述2" example:title="我是标题2">
  </com.example.combinationview.combinationview>
 
  <com.example.combinationview.combinationview android:id="@+id/cv_third" android:layout_height="wrap_content" android:layout_width="match_parent" example:desc_off="我是未被选中的描述3" example:desc_on="我是被选中的描述3" example:title="我是标题3">
  </com.example.combinationview.combinationview>
 
  <com.example.combinationview.combinationview android:id="@+id/cv_fourth" android:layout_height="wrap_content" android:layout_width="match_parent" example:desc_off="我是未被选中的描述4" example:desc_on="我是被选中的描述4" example:title="我是标题4">
  </com.example.combinationview.combinationview>
 
</linearlayout>

首先在上面定义了四个自定义组合控件,大家可以看到,代码精简多了不是?!需要注意的地方:这里引用了自定义的属性集,所以在布局节点上必须要加上命名空间

xmlns:example=http://schemas.android.com/apk/res/com.example.combinationview

其中,example是命名空间的名称,是任意取的,但是必须在控件中引用属性的名称一致,不然会报错。后面的一串是标明属性集的路径,前半部分是固定的,最后一个“/”后面的内容必须是工程的包名,否则报错。
 

下面是activity里面的业务逻辑代码,没什么好说的

package com.example.combinationview;
 
import android.os.bundle;
import android.view.view;
import android.view.view.onclicklistener;
import android.app.activity;
 
public class mainactivity extends activity implements onclicklistener {
 
  private combinationview cv_first;
  private combinationview cv_second;
  private combinationview cv_third;
  private combinationview cv_fourth;
 
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);
    cv_first = (combinationview) findviewbyid(r.id.cv_first);
    cv_second = (combinationview) findviewbyid(r.id.cv_second);
    cv_third = (combinationview) findviewbyid(r.id.cv_third);
    cv_fourth = (combinationview) findviewbyid(r.id.cv_fourth);
    cv_first.setonclicklistener(this);
    cv_second.setonclicklistener(this);
    cv_third.setonclicklistener(this);
    cv_fourth.setonclicklistener(this);
  }
 
  @override
  public void onclick(view v) {
    switch (v.getid()) {
    case r.id.cv_first:
      if (cv_first.ischecked()) {
        cv_first.setchecked(false);
      } else {
        cv_first.setchecked(true);
      }
      break;
    case r.id.cv_second:
      if (cv_second.ischecked()) {
        cv_second.setchecked(false);
      } else {
        cv_second.setchecked(true);
      }
      break;
    case r.id.cv_third:
      if (cv_third.ischecked()) {
        cv_third.setchecked(false);
      } else {
        cv_third.setchecked(true);
      }
      break;
    case r.id.cv_fourth:
      if (cv_fourth.ischecked()) {
        cv_fourth.setchecked(false);
      } else {
        cv_fourth.setchecked(true);
      }
      break;
    default:
      break;
    }
  }
 
}

好了,关于自定义组合控件就讲完了,非常简单,但是比较常用。以后在项目用到时,想想实现步骤,自定义一种的组合的控件,用起来确实比较方便,比单纯的复制黏贴不仅高大上,而且提高代码的复用性,简化了代码的结构和减少了代码量。

下面再来看这样的一个完整的实例,比较简单,直接上代码了:Android中View自定义组合控件的基本编写方法

package com.xiong.demo1; 
 
import android.app.activity; 
import android.os.bundle; 
import android.view.view; 

public class mainactivity extends activity { 
 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.main_activity); 
    titlebarview titlebarview = (titlebarview) findviewbyid(r.id.tbar_test); 
    titlebarview.gettextviewrigth().setvisibility(view.gone); 
    titlebarview.settitlebarchangerliseter(new ititleonchangelister() { 
      @override 
      public void setleftonclicklister() { 
        finish(); 
      } 
 
      @override 
      public void setrigthonclicklister() { 
 
      } 
    }); 
  } 
 
} 

<?xml version="1.0" encoding="utf-8"?> 
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 
       xmlns:xionglh="http://schemas.android.com/apk/res-auto" 
       android:layout_width="match_parent" 
       android:layout_height="match_parent"> 
 
  <com.xiong.demo1.titlebarview 
    android:id="@+id/tbar_test" 
    android:layout_width="match_parent" 
    android:layout_height="45dp" 
    xionglh:titlebar_center_text="首页" 
    xionglh:titlebar_center_textcolor="@android:color/black" 
    xionglh:titlebar_center_text_size="18sp" 
    xionglh:titlebar_left_bg="@mipmap/left_back" 
    xionglh:titlebar_right_text="安全中心" 
    xionglh:titlebar_right_text_size="12sp"/> 
</linearlayout> 

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
  <declare-styleable name="titlebar"> 
 
    <attr name="titlebar_center_text_size" format="dimension"/> 
    <attr name="titlebar_center_text" format="string"/> 
    <attr name="titlebar_center_textcolor" format="color"/> 
 
    <attr name="titlebar_left_bg" format="reference"/> 
 
    <attr name="titlebar_right_text_size" format="dimension"/> 
    <attr name="titlebar_right_text" format="string"/> 
    <attr name="titlebar_right_textcolor" format="color"/> 
 
  </declare-styleable> 
 
</resources> 

package com.xiong.demo1; 
 
import android.content.context; 
import android.content.res.typedarray; 
import android.graphics.color; 
import android.util.attributeset; 
import android.util.typedvalue; 
import android.view.view; 
import android.view.viewgroup; 
import android.widget.imageview; 
import android.widget.relativelayout; 
import android.widget.textview; 
 
public class titlebarview extends relativelayout { 
 
  private ititleonchangelister mititleonchangelister; 
 
  private imageview mimgleft; 
  private textview mtxtcenter, mtxtrigth; 
 
  private float mtitlecentertextsize;//标题字体大小 
  private string mtitlecentertext;//标题文字 
  private int mtitlecentertextcolor;//标题颜色 
 
  private int mleftbg;//左边返回按钮 
 
  private float mtitlerigthtextsize;//标题字体大小 
  private string mtitlerigthtext;//标题文字 
  private int mtitlerigthcolor;//标题颜色 
 
  public titlebarview(context context, attributeset attrs) { 
    super(context, attrs); 
    int defualtsize = (int) typedvalue.applydimension( 
        typedvalue.complex_unit_sp, 16, getresources().getdisplaymetrics()); 
    typedarray typedarray = getcontext().obtainstyledattributes(attrs, r.styleable.titlebar); 
    mtitlecentertextsize = typedarray.getdimension(r.styleable.titlebar_titlebar_center_text_size, defualtsize); 
    mtitlecentertext = typedarray.getstring(r.styleable.titlebar_titlebar_center_text); 
    mtitlecentertextcolor = typedarray.getcolor(r.styleable.titlebar_titlebar_center_textcolor, color.red); 
    mleftbg = typedarray.getresourceid(r.styleable.titlebar_titlebar_left_bg, r.mipmap.left_back); 
    mtitlerigthtextsize = typedarray.getdimension(r.styleable.titlebar_titlebar_right_text_size, defualtsize); 
    mtitlerigthtext = typedarray.getstring(r.styleable.titlebar_titlebar_right_text); 
    mtitlerigthcolor = typedarray.getcolor(r.styleable.titlebar_titlebar_right_textcolor, color.red); 
    typedarray.recycle(); 
    initview(); 
  } 
 
  private void initview() { 
    mtxtcenter = new textview(getcontext()); 
    mtxtcenter.settext(mtitlecentertext); 
    mtxtcenter.settextsize(typedvalue.complex_unit_px, mtitlecentertextsize); 
    mtxtcenter.settextcolor(mtitlecentertextcolor); 
    layoutparams centerparams = new layoutparams(viewgroup.layoutparams.wrap_content, viewgroup.layoutparams.wrap_content); 
    centerparams.addrule(relativelayout.center_in_parent, true); 
    addview(mtxtcenter, centerparams); 
    mtxtrigth = new textview(getcontext()); 
    mtxtrigth.settext(mtitlerigthtext); 
    mtxtrigth.settextsize(typedvalue.complex_unit_px, mtitlerigthtextsize); 
    mtxtrigth.settextcolor(mtitlerigthcolor); 
    layoutparams rigthparams = new layoutparams(viewgroup.layoutparams.wrap_content, viewgroup.layoutparams.wrap_content); 
    rigthparams.addrule(relativelayout.align_parent_right, true); 
    rigthparams.addrule(relativelayout.center_vertical, true); 
 
    addview(mtxtrigth, rigthparams); 
    mimgleft = new imageview(getcontext()); 
    mimgleft.setimageresource(mleftbg); 
    layoutparams leftparams = new layoutparams(viewgroup.layoutparams.wrap_content, viewgroup.layoutparams.wrap_content); 
    leftparams.setmargins(6, 0, 0, 0); 
    leftparams.addrule(relativelayout.center_vertical, true); 
    addview(mimgleft, leftparams); 
    view view = new view(getcontext()); 
    view.setminimumwidth(1); 
    view.setbackgroundcolor(getresources().getcolor(r.color.gray_767676)); 
    layoutparams viewparams = new layoutparams(viewgroup.layoutparams.wrap_content, 1); 
    viewparams.addrule(relativelayout.align_parent_bottom); 
    addview(view, viewparams); 
    mimgleft.setonclicklistener(new onclicklistener() { 
      @override 
      public void onclick(view v) { 
        mititleonchangelister.setleftonclicklister(); 
      } 
    }); 
 
    mtxtrigth.setonclicklistener(new onclicklistener() { 
      @override 
      public void onclick(view v) { 
        mititleonchangelister.setrigthonclicklister(); 
      } 
    }); 
 
  } 
 
  public void settitlebarchangerliseter(ititleonchangelister ititleonchangelister) { 
    this.mititleonchangelister = ititleonchangelister; 
  } 
 
 
  public imageview getleftimage() { 
    return mimgleft; 
  } 
 
  public textview gettextviewcenter() { 
    return mtxtcenter; 
  } 
 
  public textview gettextviewrigth() { 
    return mtxtrigth; 
  } 
} 

package com.xiong.demo1; 

public interface ititleonchangelister { 
 
 
  void setleftonclicklister(); 
  void setrigthonclicklister(); 
 
}