在购物平台上,经常可以看到购物车这样的自增自减的控件。
假如我们的项目中也需要这样的控件,并且在多个地方需要调用,要是不做分装,我们可能得多次用LinearLayout布局获取RelativeLayout布局。里面加上相关子控件,各个子控件的点击事件等。这样开发效率相对来说比较低。我们可以通过创建复合控件来解决这样的问题。
复合控件即是指不可分割的、可重用的视图,这样的视图包含了多个布局和捆绑在一起的子控件。
复合控件可以提高我们的工作效率,方便维护,避免重复造轮子。
我们来看一下本例子实现的效果
下面我们一步一步来实现,这里分成6步。
- 创建AddTextView继承View
public class AddTextView extends View {
Paint mPaint; //画笔
public AddTextView(Context context) {
super(context);
}
public AddTextView(Context context, AttributeSet attrs){
super(context, attrs);
mPaint = new Paint();
//TypedArray是一个用来存放由context.obtainStyledAttributes获得的属性的数组
//在使用完成后,调用recycle方法
//属性的名称是styleable中的名称+“_”+属性名称
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AddTextView);
int textColor = array.getColor(R.styleable.AddTextView_textColor, 0XFF00FF00); //提供默认值,放置未指定
float textSize = array.getDimension(R.styleable.AddTextView_textSize, 12);
mPaint.setColor(textColor);
mPaint.setTextSize(textSize);
array.recycle();
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
mPaint.setStyle(Paint.Style.FILL); //设置填充
canvas.drawRect(10, 10, 100, 100, mPaint); //绘制矩形
mPaint.setColor(Color.BLUE);
canvas.drawText("自定义TextView", 10, 40, mPaint);
}
}
分析说明:
重载构造方法和onDraw方法
对于自定义的View如果没有自己独特的属性,可以直接在xml文件中使用就可以了
如果含有自己独特的属性,那么就需要在构造函数中获取属性文件attrs.xml中自定义属性的名称
并根据需要设定默认值,放在在xml文件中没有定义。
如果使用自定义属性,那么在应用xml文件中需要加上新的schemas,
xmlns:add="http://schemas.android.com/apk/res/com.peng.addoneview"
其中xmlns后的“add”是自定义的属性的前缀,res后的是我们自定义View所在的包
- 创建AddOneLinearLayout继承LinearLayout
public class AddOneLinearLayout extends LinearLayout {
private final static int reduce_number=1;
private final static int add_number=2;
private ImageButton reduce;
private TextView numbeText;
private ImageButton add;
private int size;
private NumberClickListener numberClickListener;
public AddOneLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.add_reduce, this);
initView();
// TypedArray是存放资源的array,1.通过上下文得到这个数组,attrs是构造函数传进来的,对应attrs.xml
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AddOneLinearLayout);
// 获得xml里定义的属性,格式为 名称_属性名 后面是默认值
Drawable reduceDrawable = a.getDrawable(R.styleable.AddOneLinearLayout_reductionDrawble);
if(reduceDrawable != null) {
reduce.setImageDrawable(reduceDrawable);
};
Drawable addDrawable = a.getDrawable(R.styleable.AddOneLinearLayout_addDrawble);
if(addDrawable != null) {
add.setImageDrawable(addDrawable);
};
// 为了保持以后使用该属性一致性,返回一个绑定资源结束的信号给资源
a.recycle();
}
class ChangeHandler extends Handler{
public void handleMessage(Message msg) {
switch (msg.what) {
case reduce_number:
numbeText.setText(size+"");
break;
case add_number:
numbeText.setText(size+"");
break;
default:
break;
}
super.handleMessage(msg);
}
}
private void initView() {
reduce = (ImageButton) findViewById(R.id.img_reduce);
numbeText = (TextView) findViewById(R.id.txt_num);
add = (ImageButton) findViewById(R.id.img_add);
numbeText.setText("0");
reduce.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(size==0){
return;
}
size--;
numberClickListener.onChangeNumber();
Message msg = new ChangeHandler().obtainMessage();
msg.what=reduce_number;
msg.sendToTarget();
}
});
add.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
size++;
numberClickListener.onChangeNumber();
Message msg = new ChangeHandler().obtainMessage();
msg.what=add_number;
msg.sendToTarget();
}
});
}
public int getNumber(){
return size;
}
public void setNumberClickListener(NumberClickListener NumberClickListener){
this.numberClickListener = NumberClickListener;
}
public interface NumberClickListener {
void onChangeNumber();
}
}
通过线程来通知文本框显示的值。定义NumberClickListener接口给外部调用。
下面建立相对应的布局文件
- 下来在layout文件夹中创建自增自减的add_reduce.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<ImageButton
android:id="@+id/img_reduce"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:background="@null"
android:contentDescription="@string/empty"
android:src="@drawable/bg_selector_reduce_state" />
<TextView
android:id="@+id/txt_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="@string/empty"
android:textColor="@android:color/black"
android:textSize="18sp" />
<ImageButton
android:id="@+id/img_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:background="@null"
android:contentDescription="@string/empty"
android:src="@drawable/bg_selector_add_state"/>
</LinearLayout>
这是一个简单的LinearLaout直线水平布局,里面放置了两个ImageButton控件和一个TextView 控件。
下面我们来实验一下,在主页面显示出我们刚才自定义的控件。
- 主页面MainActivity
public class MainActivity extends Activity {
AddOneLinearLayout llay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
llay = (AddOneLinearLayout) this.findViewById(R.id.number_linear);
llay.setNumberClickListener(new AddOneLinearLayout.NumberClickListener() {
@Override
public void onChangeNumber() {
int number = llay.getNumber();
Toast.makeText(MainActivity.this, number+"", Toast.LENGTH_SHORT).show();
}
});
}
}
我们是实例话AddOneLinearLayout,找到布局控件的id,设置其点击监听事件。
- 主页面布局,引用自定义控件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:add="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center">
<com.peng.addoneview.widget.AddOneLinearLayout
android:id="@+id/number_linear"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
add:reductionDrawble="@drawable/bg_selector_reduce_state"
add:addDrawble="@drawable/bg_selector_add_state"
android:layout_gravity="center_vertical|center_horizontal"/>
</RelativeLayout>
xmlns:add="http://schemas.android.com/apk/res/com.peng.addoneview"
其中xmlns后的“add”是自定义的属性的前缀,res后的是我们自定义View所在的包
这里我们写成
xmlns:add="http://schemas.android.com/apk/res-auto"
那是在android studio环境中gradle这样写也是正确的
- 在res/value 创建attires文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AddTextView">
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
</declare-styleable>
<declare-styleable name="AddOneLinearLayout">
<attr name="reductionDrawble" format="reference"/>
<attr name="addDrawble" format="reference"/>
</declare-styleable>
</resources>
这样我们关于自增自减的自定义控件就算完成了。
项目结构图:
其他自定义视图,思路大致类似这样。