您的位置:首页 > 其它

自定义控件(视图)28期笔记07:自定义控件之 自定义属性(开关按钮案例的优化)

2015-09-29 20:21 537 查看
1.先前,我们编好的开关按钮的项目工程,如下:





2. 下面我们要使用自定义的属性优化这个开关按钮,如下:

(1)第1步,我们在res/values文件夹下,新建一个attrs.xml文件,如下:



其中attrs.xml,如下:

 <?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 声明一个属性集的名称 -->
<declare-styleable name="MyToggleBtn">

<!-- 声明一个属性name是my_background  类型为引用类型  引用资源ID-->
<attr name="my_background" format="reference"></attr>

<!-- 声明一个属性name是my_slide_btn  类型为引用类型  引用资源ID -->
<attr name="my_slide_btn" format="reference"></attr>

<!-- 声明一个属性name是curr_state  类型为布尔值 -->
<attr name="curr_state" format="boolean"></attr>
</declare-styleable>

</resources>


(2)第2步,在布局文件activity_main.xml文件中使用上面设置的属性,如下:





(3)接下来就是来到MyToggleButton代码中,获得上面自定义的属性my_background、my_slide_btn和curr_state,如下:

 package com.himi.togglebtn;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;

public class MyToggleButton extends View implements OnClickListener {

//作为背景的图片
private Bitmap backgroundBitmap;
//滑动的开关图片
private Bitmap slidebtn;
private Paint paint;

//滑动按钮的左边界
private float slidebtn_left;

/**
* 当前开关的状态
* true :为开
* false:为关
*/
private boolean currState = false;
/**
* 背景图的资源ID
*/
private int backgroundId;
/**
* 滑动图片的资源ID
*/
private int slideBtnId;

/**
* 我们在代码里面创建对象的时候,使用此构造方法
* @param context
*/
public MyToggleButton(Context context) {
super(context);
// TODO 自动生成的构造函数存根
}

/**
* 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
* 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
* @param context
* @param attrs
*/
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);

//获得自定义的属性,这里获得在xml文件中定义的属性值,然后进行相应的设置
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn);

int N = ta.getIndexCount();
for(int i=0; i<N; i++) {
/*
* 获得某个属性的ID值
*/
int itemId = ta.getIndex(i);
switch (itemId) {
case R.styleable.MyToggleBtn_curr_state:
currState = ta.getBoolean(itemId, false);

break;

case R.styleable.MyToggleBtn_my_background:
backgroundId = ta.getResourceId(itemId, -1);
if(backgroundId == -1) {
throw new RuntimeException("请设置背景图片");
}
backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId);
break;
case R.styleable.MyToggleBtn_my_slide_btn:
slideBtnId = ta.getResourceId(itemId, -1);
if(slideBtnId == -1) {
throw new RuntimeException("请设置滑动按钮图片");
}
slidebtn = BitmapFactory.decodeResource(getResources(), slideBtnId);
break;
}
}

initView();
}

/**
* 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
* 改变生成自定义的View的样式style
* @param context
* @param attrs
* @param defStyle
*/
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO 自动生成的构造函数存根
}

//初始化
private void initView() {
//初始化图片
//backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
//slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);

//初始化画笔
paint = new Paint();
paint.setAntiAlias(true);//打开抗锯齿

//添加Onclick事件监听
setOnClickListener(this);
//刷新当前状态,因为上面构造方法中设置了currState,所以这里必须设置一下flushState(),每次改变了currState,自然要刷新,才能生效
flushState();
}

/*
* View对象显示在屏幕上,有几个重要步骤:
* 1. 构造方法 创建  对象.
* 2. 测量View的大小.  onMeasure(int, int):系统调用的方法,获知View的大小
* 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
* 4. 绘制View的内容           onDraw(canvas)
*
*/

/**
*
* 测量尺寸时候的回调方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置当前View的大小
* width :当前View的宽度
* height:当前view的高度(单位:像素)
*/
setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
}

/**
* 自定义的View,作用不大
* 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO 自动生成的方法存根
super.onLayout(changed, left, top, right, bottom);
}

/**
* 绘制当前View的内容
*/
@Override
protected void onDraw(Canvas canvas) {
// TODO 自动生成的方法存根
//super.onDraw(canvas);

//绘制背景图
/*
* backgroundBitmap:要绘制的图片
* left 图片的左边界
* top 图片的上边界
* paint 绘制图片要使用的画笔
*/
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
//绘制可滑动的按钮
canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
}

/**
* 判断是否发生拖到
* 如果拖动了,就不再响应Onclick事件
* true:发生拖动
* false:没有发生拖动
*/
private boolean isDrag = false;

/**
* onClick事件在view.onTouchEvent中被解析
* 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件
*/
public void onClick(View v) {
/*
* 如果没有拖动,才执行改变状态的动作
*/
if(!isDrag) {
currState = ! currState;
flushState();//刷新当前开关状态
}
}

/**
* 刷新当前开关视图
*/
private void flushState() {
if(currState) {
slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
}else {
slidebtn_left =0;
}

flushView();
}

public void flushView() {
/**
* 对slidebtn_left的值进行判断
* 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界)
*
*/
int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
//确保slidebtn_left >= 0
slidebtn_left =(slidebtn_left>0)?slidebtn_left:0;
//确保slidebtn_left <=maxLeft
slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft;

//告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果
invalidate();
}

/**
* down事件时的x值
*/
private int firstX;
/**
* touch事件的上一个x值
*/
private int lastX;

@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstX = lastX = (int) event.getX();
isDrag = false;
break;
case MotionEvent.ACTION_MOVE:
//判断是否发生拖动
if(Math.abs(event.getX()-lastX)>5) {
isDrag = true;
}

//计算手指在屏幕上移动的距离
int dis = (int) (event.getX()-lastX);
//将本次的位置设置给lastX
lastX = (int) event.getX();
//根据手指移动的距离,改变slidebtn_left的值
slidebtn_left = slidebtn_left+dis;
break;
case MotionEvent.ACTION_UP:

//在发生拖动的情况下,根据最后的位置,判断当前的开关的状态
if(isDrag){
int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
/**
* 根据slidebtn_left判断,当前应该是什么状态
*
*/
if(slidebtn_left>maxLeft/2) {//应为打开状态
currState = true;
}else {
currState = false;
}
flushState();
}

break;

}
flushView();
return  true;
}
}


运行效果如下:





3. 上面的是标准的自定义属性的使用,还有不太正规,但是很方便的使用自定义属性的方法,如下:

(1)在上面的"开关按钮"工程的activity_main.xml文件中,添加如下:





(2)然后在MyToggleButton.java使用在xml文件中定义的testAttrs,如下:

 package com.himi.togglebtn;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;

public class MyToggleButton extends View implements OnClickListener {

//作为背景的图片
private Bitmap backgroundBitmap;
//滑动的开关图片
private Bitmap slidebtn;
private Paint paint;

//滑动按钮的左边界
private float slidebtn_left;

/**
* 当前开关的状态
* true :为开
* false:为关
*/
private boolean currState = false;
/**
* 背景图的资源ID
*/
private int backgroundId;
/**
* 滑动图片的资源ID
*/
private int slideBtnId;

/**
* 我们在代码里面创建对象的时候,使用此构造方法
* @param context
*/
public MyToggleButton(Context context) {
super(context);
// TODO 自动生成的构造函数存根
}

/**
* 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
* 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
* @param context
* @param attrs
*/
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);

//无命名空间测试,测试我们在activity_main.xml文件中定义的属性-----testAttrs="hello"
String testAttrs = attrs.getAttributeValue(null, "testAttrs");
System.out.println("testAttrs==:"+testAttrs);

//获得自定义的属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn);

int N = ta.getIndexCount();
for(int i=0; i<N; i++) {
/*
* 获得某个属性的ID值
*/
int itemId = ta.getIndex(i);
switch (itemId) {
case R.styleable.MyToggleBtn_curr_state:
currState = ta.getBoolean(itemId, false);

break;

case R.styleable.MyToggleBtn_my_background:
backgroundId = ta.getResourceId(itemId, -1);
if(backgroundId == -1) {
throw new RuntimeException("请设置背景图片");
}
backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId);
break;
case R.styleable.MyToggleBtn_my_slide_btn:
slideBtnId = ta.getResourceId(itemId, -1);
if(slideBtnId == -1) {
throw new RuntimeException("请设置滑动按钮图片");
}
slidebtn = BitmapFactory.decodeResource(getResources(), slideBtnId);
break;
}
}

initView();
}

/**
* 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
* 改变生成自定义的View的样式style
* @param context
* @param attrs
* @param defStyle
*/
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO 自动生成的构造函数存根
}

//初始化
private void initView() {
//初始化图片
//backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
//slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);

//初始化画笔
paint = new Paint();
paint.setAntiAlias(true);//打开抗锯齿

//添加Onclick事件监听
setOnClickListener(this);
//刷新当前状态
flushState();
}

/*
* View对象显示在屏幕上,有几个重要步骤:
* 1. 构造方法 创建  对象.
* 2. 测量View的大小.  onMeasure(int, int):系统调用的方法,获知View的大小
* 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
* 4. 绘制View的内容           onDraw(canvas)
*
*/

/**
*
* 测量尺寸时候的回调方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置当前View的大小
* width :当前View的宽度
* height:当前view的高度(单位:像素)
*/
setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
}

/**
* 自定义的View,作用不大
* 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO 自动生成的方法存根
super.onLayout(changed, left, top, right, bottom);
}

/**
* 绘制当前View的内容
*/
@Override
protected void onDraw(Canvas canvas) {
// TODO 自动生成的方法存根
//super.onDraw(canvas);

//绘制背景图
/*
* backgroundBitmap:要绘制的图片
* left 图片的左边界
* top 图片的上边界
* paint 绘制图片要使用的画笔
*/
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
//绘制可滑动的按钮
canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
}

/**
* 判断是否发生拖到
* 如果拖动了,就不再响应Onclick事件
* true:发生拖动
* false:没有发生拖动
*/
private boolean isDrag = false;

/**
* onClick事件在view.onTouchEvent中被解析
* 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件
*/
public void onClick(View v) {
/*
* 如果没有拖动,才执行改变状态的动作
*/
if(!isDrag) {
currState = ! currState;
flushState();//刷新当前开关状态
}
}

/**
* 刷新当前开关视图
*/
private void flushState() {
if(currState) {
slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
}else {
slidebtn_left =0;
}

flushView();
}

public void flushView() {
/**
* 对slidebtn_left的值进行判断
* 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界)
*
*/
int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
//确保slidebtn_left >= 0
slidebtn_left =(slidebtn_left>0)?slidebtn_left:0;
//确保slidebtn_left <=maxLeft
slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft;

//告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果
invalidate();
}

/**
* down事件时的x值
*/
private int firstX;
/**
* touch事件的上一个x值
*/
private int lastX;

@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstX = lastX = (int) event.getX();
isDrag = false;
break;
case MotionEvent.ACTION_MOVE:
//判断是否发生拖动
if(Math.abs(event.getX()-lastX)>5) {
isDrag = true;
}

//计算手指在屏幕上移动的距离
int dis = (int) (event.getX()-lastX);
//将本次的位置设置给lastX
lastX = (int) event.getX();
//根据手指移动的距离,改变slidebtn_left的值
slidebtn_left = slidebtn_left+dis;
break;
case MotionEvent.ACTION_UP:

//在发生拖动的情况下,根据最后的位置,判断当前的开关的状态
if(isDrag){
int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
/**
* 根据slidebtn_left判断,当前应该是什么状态
*
*/
if(slidebtn_left>maxLeft/2) {//应为打开状态
currState = true;
}else {
currState = false;
}
flushState();
}

break;

}
flushView();
return  true;
}

}


测试logcat输出为:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: