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

网址简介:未填写

更新时间:10个月前

访问次数:104

详细介绍

在android开发中,除了自定义视图的绘制外,复合控件也是一种常用的自定义视图,它通过结合系统提供的一些公共视图添加一些自定义属性,下面是爱站技术频道小编介绍的中View自定义组合控件的基本编写方法。

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

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

Android中View自定义组合控件的基本编写方法-第1张图片

从上面的图来分析,我们可以看到,这个布局里面是没有“全新”的控件的,用的都是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:>    </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:>    </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:>    </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:>    </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自定义组合控件的基本编写方法-第2张图片

  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();      }   上文是爱站技术频道小编介绍的Android中View自定义组合控件的基本编写方法,大家在进行软件开发的时候,最好理性一点,选择一些正规、有名气的技术网站进行学习,这样能让我们在开发的时候减少不必要的麻烦。  

请发表您的评论