在购物平台上,经常可以看到购物车这样的自增自减的控件。

例如:

手把手教你开发自定义控件_JAVA

假如我们的项目中也需要这样的控件,并且在多个地方需要调用,要是不做分装,我们可能得多次用LinearLayout布局获取RelativeLayout布局。里面加上相关子控件,各个子控件的点击事件等。这样开发效率相对来说比较低。我们可以通过创建复合控件来解决这样的问题。

复合控件即是指不可分割的、可重用的视图,这样的视图包含了多个布局和捆绑在一起的子控件。

复合控件可以提高我们的工作效率,方便维护,避免重复造轮子。
我们来看一下本例子实现的效果

 

手把手教你开发自定义控件_JAVA_02

下面我们一步一步来实现,这里分成6步。

 

1创建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所在的包
*/

上面的onDraw方法就是绘制的地方。

 

2创建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接口给外部调用。

下面建立相对应的布局文件

3下来在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 控件。

下面我们来实验一下,在主页面显示出我们刚才自定义的控件。

 

4主页面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,设置其点击监听事件。

 

5

主页面布局,引用自定义控件

<?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这样写也是正确的
6

在res/value 创建attrs文件

<?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>  
这样我们关于自增自减的自定义控件就算完成了。

项目结构图:

手把手教你开发自定义控件_JAVA_03
其他自定义视图,思路大致就是这样,你学会了

手把手教你开发自定义控件_JAVA_04