组合控件是自定义控件的一种,只不过它是由其他几个原生控件组合而成,故名组合控件。
在实际项目中,GUI会遇到一些可以提取出来做成自定义控件情况。
一个自定义控件的好处就是把一些需要模块化的UI和逻辑放在一起,做到了高内聚,向其他模块提供接口并很少
依赖外界,这样就是低耦合。一个自定义控件就是一个封闭的王国,这里由你掌控。
上述是我自己的一个体会,想必大家也会常做自定义控件吧,就像逻辑部分的模块化一样。
下面我要做一个例子,请看完成图。
下面一排图片加文字就是组合控件了,我是怎么做的呢?
其实这里用到了两个组合控件,一个是图片+文字,我把它叫一个Item,而三个在一起就是另一个控件了。
重点看这个Item,它有自己的属性如图片、文字、图片大小、文字大小、不透明度等等。这些把它定义在attr文件中,然后在xml文件中
配置,就像我们用原生控件一样。
先看attr文件。
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
03 | < declare-styleable name = "LevelMenuItem" > |
04 | < attr name = "text" format = "string" /> |
05 | < attr name = "text_color" format = "color" /> |
06 | < attr name = "text_size" format = "dimension" /> |
07 | < attr name = "image_src" format = "reference" /> |
08 | < attr name = "image_bg" format = "reference" /> |
09 | < attr name = "image_alpha" format = "integer" /> |
10 | < attr name = "image_height" format = "dimension" ></ attr > |
11 | < attr name = "image_width" format = "dimension" /> |
这个文件在values下,和string文件同级。把你自己要定义的属性都写在这里吧。format是属性的“单位”,如果你要问有多少中format呀?答案在
这里
。
有了属性了,下面看看布局文件level_menu_item.xml。
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
03 | android:layout_width = "fill_parent" |
04 | android:layout_height = "fill_parent" |
05 | android:orientation = "vertical" > |
07 | android:id = "@+id/image_item" |
08 | android:layout_width = "fill_parent" |
09 | android:layout_height = "fill_parent" |
10 | android:scaleType = "fitCenter" |
13 | android:id = "@+id/tv_item" |
14 | android:layout_width = "fill_parent" |
15 | android:layout_height = "wrap_content" |
16 | android:gravity = "center_horizontal" |
17 | android:textColor = "#23ffffff" |
18 | android:textSize = "25sp" |
这里唯一值得一说的是文本的颜色。大家看见他是8位的,前两位是表示不透明度的,后六位是表示颜色的,三色,范围都是00~ff。如果在java中设置颜色,需要这样。
1 | setTextColor( 0x23ffffff ); |
关于不透明度,一般美工会定义。有些要求不透明如30%这样的,可以用整型换算一下。00~ff对应十进制为0~255,那么30%就是255x0.3=76.5,用科学计算机换算为4c。
然后我们就要写一个类,我这继承子线性布局。有两个构造函数,我们主要在两个参数的函数中工作。
1 | public class LevelMenuItem extends LinearLayout { |
3 | public LevelMenuItem(Context context, AttributeSet attrs) { |
这个类中我们要完成的工作是,初始化控件属性、提供外部修改属性的接口、控件点击的回调接口。
此类完整代码:
001 | package com.linc.game; |
003 | import android.content.Context; |
004 | import android.content.res.TypedArray; |
005 | import android.util.AttributeSet; |
006 | import android.view.LayoutInflater; |
007 | import android.view.View; |
008 | import android.widget.ImageView; |
009 | import android.widget.LinearLayout; |
010 | import android.widget.TextView; |
016 | * 一个自定义控件的好处就是把一些需要模块化的 |
017 | * UI和逻辑放在一起,做到了高内聚,向其他模块提供接口并很少 |
018 | * 依赖外界,这样就是低耦合。一个自定义控件就是一个封闭的王国, |
021 | * 编写时,如果遇到在attr里写好属性,但是在这里认不出来, |
027 | public class LevelMenuItem extends LinearLayout { |
028 | private TextView mTextView = null ; |
029 | private ImageView mImageView = null ; |
030 | private OnItemClickListener mOnClickListener = null ; |
032 | public LevelMenuItem(Context context) { |
035 | public LevelMenuItem(Context context, AttributeSet attrs) { |
036 | super (context, attrs); |
038 | LayoutInflater layoutInflater = (LayoutInflater) context. |
039 | getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
040 | layoutInflater.inflate(R.layout.level_menu_item, this ); |
042 | TypedArray typedArray = context.obtainStyledAttributes(attrs |
043 | ,R.styleable.LevelMenuItem); |
045 | initWidget(typedArray); |
047 | private void initWidget(TypedArray typedArray) |
049 | mTextView = (TextView)findViewById(R.id.tv_item); |
050 | String textString = typedArray.getString(R.styleable.LevelMenuItem_text); |
051 | int textColor = typedArray.getColor(R.styleable.LevelMenuItem_text_color, |
053 | float textSize = typedArray.getDimension(R.styleable.LevelMenuItem_text_size, |
055 | mTextView.setText(textString); |
056 | mTextView.setTextColor(textColor); |
057 | mTextView.setTextSize(textSize); |
059 | mImageView = (ImageView)findViewById(R.id.image_item); |
060 | int imageHeight = ( int ) typedArray.getDimension(R.styleable.LevelMenuItem_image_height, 25 ); |
061 | int imageWidth = ( int ) typedArray.getDimension(R.styleable.LevelMenuItem_image_width, 25 ); |
062 | int imageSrc = typedArray.getResourceId(R.styleable.LevelMenuItem_image_src, 0 ); |
063 | int imageBg = typedArray.getResourceId(R.styleable.LevelMenuItem_image_bg, 0 ); |
064 | int imageAlpha = typedArray.getInt(R.styleable.LevelMenuItem_image_alpha, 255 ); |
065 | mImageView.setAlpha(imageAlpha); |
066 | mImageView.setImageResource(imageSrc); |
067 | mImageView.setBackgroundResource(imageBg); |
068 | LayoutParams layoutParams = new LayoutParams(imageWidth, imageHeight); |
069 | mImageView.setLayoutParams(layoutParams); |
071 | typedArray.recycle(); |
077 | public void setText(String text) |
079 | mTextView.setText(text); |
085 | public void setTextColor( int textColor) |
087 | mTextView.setTextColor(textColor); |
093 | public void setTextSize( int textSize) |
095 | mTextView.setTextSize(textSize); |
101 | public void setImageResource( int resId) |
103 | mImageView.setImageResource(resId); |
108 | public void setBackgroundResource( int resId) |
110 | mImageView.setBackgroundResource(resId); |
116 | public void setImageAlpha( int alpha) |
118 | mImageView.setAlpha(alpha); |
122 | * 这里面需要使用LayoutParams这个布局参数来设置 |
126 | public void setImageSize( int width, int height) |
128 | LayoutParams layoutParams = new LayoutParams(width, height); |
129 | mImageView.setLayoutParams(layoutParams); |
135 | public void setOnClickListener(OnItemClickListener listener) |
137 | this .mOnClickListener = listener; |
138 | mImageView.setOnClickListener( new View.OnClickListener() { |
140 | public void onClick(View v) { |
141 | mOnClickListener.onImageClick(); |
150 | public interface OnItemClickListener |
152 | public void onImageClick(); |
好,一个完整的组合控件就做好了,那么,我们如何使用呢?
我要在LevelMenu中用它。xml文件如下:
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
03 | xmlns:linc = "http://schemas.android.com/apk/res/com.linc.game" |
04 | android:layout_width = "fill_parent" |
05 | android:layout_height = "fill_parent" |
06 | android:orientation = "horizontal" > |
07 | < com.linc.game.LevelMenuItem |
08 | android:id = "@+id/item1" |
09 | android:layout_width = "70dp" |
10 | android:layout_height = "80dp" |
11 | linc:text = "@string/item1" |
13 | linc:text_color = "#80fa8072" |
14 | linc:image_src = "@drawable/orange_button_selector" |
15 | linc:image_alpha = "128" |
16 | linc:image_height = "48dp" |
17 | linc:image_width = "48dp" |
19 | < com.linc.game.LevelMenuItem |
20 | android:id = "@+id/item2" |
21 | android:layout_marginLeft = "20dp" |
22 | android:layout_width = "70dp" |
23 | android:layout_height = "80dp" |
24 | linc:text = "@string/item2" |
26 | linc:text_color = "#ffeee8aa" |
27 | linc:image_src = "@drawable/red_button_selector" |
28 | linc:image_alpha = "255" |
29 | linc:image_height = "48dp" |
30 | linc:image_width = "48dp" |
32 | < com.linc.game.LevelMenuItem |
33 | android:id = "@+id/item3" |
34 | android:layout_marginLeft = "20dp" |
35 | android:layout_width = "70dp" |
36 | android:layout_height = "80dp" |
37 | linc:text = "@string/item3" |
39 | linc:text_color = "#80cd853f" |
40 | linc:image_src = "@drawable/yellow_button_selector" |
41 | linc:image_alpha = "128" |
42 | linc:image_height = "48dp" |
43 | linc:image_width = "48dp" |
加入自己包名的索引剩下的就一目了然了。
LevelMenu.java
01 | package com.linc.game; |
03 | import com.linc.game.LevelMenuItem.OnItemClickListener; |
04 | import android.content.Context; |
05 | import android.util.AttributeSet; |
06 | import android.util.Log; |
07 | import android.view.LayoutInflater; |
08 | import android.widget.LinearLayout; |
10 | public class LevelMenu extends LinearLayout { |
11 | private LevelMenuItem item1,item2,item3; |
13 | public LevelMenu(Context context) { |
19 | public LevelMenu(Context context, AttributeSet attrs) { |
20 | super (context, attrs); |
21 | LayoutInflater layoutInflater = (LayoutInflater) context. |
22 | getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
23 | layoutInflater.inflate(R.layout.level_menu, this ); |
26 | private void initWidget() |
28 | item1 = (LevelMenuItem)findViewById(R.id.item1); |
29 | item2 = (LevelMenuItem)findViewById(R.id.item2); |
30 | item3 = (LevelMenuItem)findViewById(R.id.item3); |
32 | item1.setOnClickListener( new OnItemClickListener() { |
34 | public void onImageClick() { |
35 | Log.e( "dfjdkfjd" , "dfdfd" ); |
在处理图片点击事件的时候,我用到了选择器(selector),这是我们实际开发中最常用的小技巧了。它能描述的状态很多,各位看官可以去查查。
1 | <? xml version = "1.0" encoding = "utf-8" ?> |
2 | < selector xmlns:android = "http://schemas.android.com/apk/res/android" > |
3 | < item android:state_pressed = "true" |
4 | android:drawable = "@drawable/button_push" /> |
5 | < item android:drawable = "@drawable/orange_button" /> |
好,组合控件的例子先到这里,实际功能在下一个实战技巧中演练。
大家在做自定义控件时需要注意的是:
1、自定义控件类不能是是抽象类
2、要用
1 | (Context context, AttributeSet attrs) |
这个构造函数
否则报错:android.view.InflateException: Binary XML file line #15: Error inflating cla。。。