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

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

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

从上面的图来分析,我们可以看到,这个布局里面是没有“全新”的控件的,用的都是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;
    }
  }

}

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

下面再来看这样的一个完整的实例,比较简单,直接上代码了:

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(); 

}
(0)

相关推荐

  • Android自定义控件之组合控件学习笔记分享

    我们来讲一下自定义组合控件,相信大家也接触过自定义组合控件吧,话不多说,直接干(哈~哈~): 大家看到这个觉得这不是很简单的吗,这不就是写个布局文件就搞定嘛,没错,确实直接上布局就行,不过,我只是用这个简单的例子来讲一下自定义组合控件的用法. 首先看看,这一行行的条目看起来都长得差不多,只是图片和文字不一样,没错,就是看中这一点,我们可以把一个条目做成一个组合控件,做为一个整体,这样不管你有几个条目,就写几个组合控件就行了. 步骤: 1.先建立组合控件的布局 myView.xml <Relati

  • 实例讲解Android应用中自定义组合控件的方法

    自定义view大概可以分成: 继承view 组合view 自己画的view(用paint和canvas) 自定义viewgroup(widget) 最近又跟同学聊起来了,于是就准备自己实现一个imagebutton来练练手.(最简单的了)以及后面一个把imageview与textview结合使用的案例. ImageButton 要实现ImageButton最容易想到的不就是把button和imageview合在一起么.嗯,人容易就想到这是一个组合自定义view.可是如何把一个image放到but

  • 探究Android中ListView复用导致布局错乱的解决方案

    首先来说一下具体的需求是什么样的: 需求如图所示,这里面有ABCD四个选项的题目,当点击A选项,如果A是正确的答案,则变成对勾的图案,如果是错误答案,则变成错误的图案,这里当时在写的时候觉得很简单,只要是在点击的时候判断我点击的选项与正确答案是否一样,是一样就将图片换成正确的样式,如果不一样就换成错误的样式,于是我便写了下面的代码(只贴出了核心Adapter中的代码) package com.fizzer.anbangproject_dahuo_test.Adapter; import andr

  • Android自定义组合控件之自定义下拉刷新和左滑删除实例代码

    绪论 最近项目里面用到了下拉刷新和左滑删除,网上找了找并没有可以用的,有比较好的左滑删除,但是并没有和下拉刷新上拉加载结合到一起,要不就是一些比较水的结合,并不能在项目里面使用,小编一着急自己组合了一个,做完了和QQ的对比了一下,并没有太大区别,今天分享给大家,其实并不难,但是不知道为什么网上没有比较好的Demo,当你的项目真的很急的时候,又没有比较好的Demo,那么"那条友谊的小船儿真是说翻就翻啊",好了,下面先来具体看一下实现后的效果吧: 代码已经上传到Github上了,小伙伴们记

  • 在Android开发中使用自定义组合控件的例子

    一.定义一个XML布局文件 setting_item_view.xml <?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

  • Android自定义控件之创建可复用的组合控件

    前面已学习了一种自定义控件的实现,是Andriod 自定义控件之音频条,还没学习的同学可以学习下,学习了的同学也要去温习下,一定要自己完全的掌握了,再继续学习,贪多嚼不烂可不是好的学习方法,我们争取学习了一种技术就会一种技术,而且不光看了就算了,最好的方法就是看完我自己再练习下,再扩展下,在原来的基础上在添加一些东西,比如,增加一些功能实现等等. 今天我们打算学习下另外一种自定义控件,就是创建可重复使用的组合控件,那么问题来了: 什么是可重复使用? 就是在应用中,可以在多个地方共同使用一套代码.

  • Android自定义控件之继承ViewGroup创建新容器

    欢迎大家来学习本节内容,前几节我们已经学习了其他几种自定义控件,分别是Andriod 自定义控件之音频条及 Andriod 自定义控件之创建可以复用的组合控件还没有学习的同学请先去学习下,因为本节将使用到上几节所讲述的内容. 在学习新内容之前,我们先来弄清楚两个问题: 1 . 什么是ViewGroup? ViewGroup是一种容器.它包含零个或以上的View及子View. 2 . ViewGroup有什么作用? ViewGroup内部可以用来存放多个View控件,并且根据自身的测量模式,来测量

  • Android组合控件实现功能强大的自定义控件

    通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 上文说过了如何继承现有控件来自定义控件:<Android继承现有控件拓展实现自定义控件textView>,这节我们来讨论第二个议题.怎么将控件组合来实现一个功能强大的自定义控件. 先看看创建组合控件的好处吧,创建组合控件能够很好的创建具有组合功能的控件集合.那我们一般又是怎么做的了,一般我们来继承一个合适的ViewG

  • Android自定义控件之自定义组合控件(三)

    前言: 前两篇介绍了自定义控件的基础原理Android自定义控件基本原理详解(一).Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本. 使用自定义组合控件的好处? 我们在项目开发中经常会遇见很多相似或者相同的布局,比如APP的标题栏,我们从三种方式实现标题栏来对比自定义组件带来的好处,毕竟好的东西还是以提高开发效率,降低开发成本为导向的.  1.)第一种方式:直接在每个xml布局中写相同的标题栏布局代码 <?xml v

  • Android自定义View之组合控件实现类似电商app顶部栏

    本文实例为大家分享了Android自定义View之组合控件,仿电商app顶部栏的相关代码,供大家参考,具体内容如下 效果图: 分析:左右两边可以是TextView和Button,设置drawableTop即可,中间的看着像是EditText,但是用过淘宝天猫等类似app的话会发现点击搜索不是在当前Activit进行搜索的,是跳转到另外的页面进行的,所以用TextView然后设置背景即可. 实现流程 参数列表: 设置属性文件:values下建立attrs.xml文件,添加需要自定义的属性. <?x

随机推荐