完全自定义控件

完全自定义控件.(继承View, ViewGroup)

1. 自定义开关  


> - 1. 写个类继承View

> - 2. 拷贝包含包名的全路径到xml中

> - 3. (MainActivity)界面中找到该控件, 设置初始信息

toggleView = (ToggleView) findViewById(R.id.toggleView);
// 设置开关更新监听
        toggleView.setOnSwitchStateUpdateListener(new OnSwitchStateUpdateListener(){
@Override
public void onStateUpdate(boolean state) {
Toast.makeText(getApplicationContext(), "state: " + state, 0).show();
}
        });
    }

> - 4. 根据需求绘制界面内容

 * Android 的界面绘制流程
 * 测量  摆放 绘制
 * measure -> layout -> draw
 *    |              |    |
 * onMeasure -> onLayout -> onDraw 重写这些方法, 实现自定义控件
 * 
 * onResume()之后执行
 * 
 * View
 * onMeasure() (在这个方法里指定自己的宽高) -> onDraw() (绘制自己的内容)
 * 
 * ViewGroup
 * onMeasure() (指定自己的宽高, 所有子View的宽高)-> onLayout() (摆放所有子View) -> onDraw() (绘制内容)


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(switchBackgroupBitmap.getWidth(), switchBackgroupBitmap.getHeight());
}


// Canvas 画布, 画板. 在上边绘制的内容都会显示到界面上.
@Override
protected void onDraw(Canvas canvas) {
// 1. 绘制背景
canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);

// 2. 绘制滑块

if(isTouchMode){
// 根据当前用户触摸到的位置画滑块

// 让滑块向左移动自身一半大小的位置
float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;

int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();

// 限定滑块范围
if(newLeft < 0){
newLeft = 0; // 左边范围
}else if (newLeft > maxLeft) {
newLeft = maxLeft; // 右边范围
}

canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {
// 根据开关状态boolean, 直接设置图片位置
if(mSwitchState){// 开
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 关
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}
}

}

> - 5. 响应用户的触摸事件

// 重写触摸事件, 响应用户的触摸.
@Override
public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouchMode = true;
System.out.println("event: ACTION_DOWN: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
System.out.println("event: ACTION_MOVE: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX();
float center = switchBackgroupBitmap.getWidth() / 2.0f;
// 根据当前按下的位置, 和控件中心的位置进行比较. 
boolean state = currentX > center;
// 如果开关状态变化了, 通知界面. 里边开关状态更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 状态传出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;
default:
break;
}
// 重绘界面
invalidate(); // 会引发onDraw()被调用, 里边的变量会重新生效.界面会更新
return true; // 消费了用户的触摸事件, 才可以收到其他的事件.
}

> - 6. 创建一个状态更新监听

private OnSwitchStateUpdateListener onSwitchStateUpdateListener;

// 1. 声明接口对象

    public interface OnSwitchStateUpdateListener{
        // 状态回调, 把当前状态传出去
        void onStateUpdate(boolean state);
    }
    // 2. 添加设置接口对象的方法, 外部进行调用
    public void setOnSwitchStateUpdateListener(
            OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
                this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
    }
    // 3. 在合适的位置.执行接口的方法    
    onSwitchStateUpdateListener.onStateUpdate(state);

    // 4. 界面/外部, 收到事件.


> - 7. 自定义属性

1. 在attrs.xml声明节点declare-styleable

        <declare-styleable name="ToggleView">
            <attr name="switch_background" format="reference" />
            <attr name="slide_button" format="reference" />
            <attr name="switch_state" format="boolean" />
        </declare-styleable>

    2. R会自动创建变量

        attr 3个变量
        styleable 一个int数组, 3个变量(保存位置)

    3. 在xml配置声明的属性/ 注意添加命名空间
    
        xmlns:app="http://schemas.android.com/apk/res/com.app.toggleview"
      
        app:switch_background="@drawable/switch_background"
        app:slide_button="@drawable/slide_button"
        app:switch_state="false"

    4. 在构造函数中获取并使用


    public ToggleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
// 获取配置的自定义属性
String namespace = "http://schemas.android.com/apk/res/com.app.toggleview";
int switchBackgroundResource = attrs.getAttributeResourceValue(namespace , "switch_background", -1);
int slideButtonResource = attrs.getAttributeResourceValue(namespace , "slide_button", -1);

mSwitchState = attrs.getAttributeBooleanValue(namespace, "switch_state", false);
setSwitchBackgroundResource(switchBackgroundResource);
setSlideButtonResource(slideButtonResource);
}


案例代码展示:

/**
 * 自定义开关
 * Android 的界面绘制流程
 * 测量 摆放 绘制
 * measure -> layout -> draw
 *  |             |    |
 * onMeasure -> onLayout -> onDraw 重写这些方法, 实现自定义控件
 * 
 * onResume()之后执行
 * 
 * View
 * onMeasure() (在这个方法里指定自己的宽高) -> onDraw() (绘制自己的内容)
 * 
 * ViewGroup
 * onMeasure() (指定自己的宽高, 所有子View的宽高)-> onLayout() (摆放所有子View) -> onDraw() (绘制内容)
 */
public class ToggleView extends View {


private Bitmap switchBackgroupBitmap; // 背景图片
private Bitmap slideButtonBitmap; // 滑块图片
private Paint paint; // 画笔
private boolean mSwitchState = false; // 开关状态, 默认false
private float currentX;


/**
* 用于代码创建控件
* @param context
*/
public ToggleView(Context context) {
super(context);
init();
}


/**
* 用于在xml里使用, 可指定自定义属性
* @param context
* @param attrs
*/
public ToggleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
// 获取配置的自定义属性
String namespace = "http://schemas.android.com/apk/res/com.itheima74.toggleview";
int switchBackgroundResource = attrs.getAttributeResourceValue(namespace , "switch_background", -1);
int slideButtonResource = attrs.getAttributeResourceValue(namespace , "slide_button", -1);

mSwitchState = attrs.getAttributeBooleanValue(namespace, "switch_state", false);
setSwitchBackgroundResource(switchBackgroundResource);
setSlideButtonResource(slideButtonResource);
}


/**
* 用于在xml里使用, 可指定自定义属性, 如果指定了样式, 则走此构造函数
* @param context
* @param attrs
* @param defStyle
*/
public ToggleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

private void init() {
paint = new Paint();
}



@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(switchBackgroupBitmap.getWidth(), switchBackgroupBitmap.getHeight());
}


// Canvas 画布, 画板. 在上边绘制的内容都会显示到界面上.
@Override
protected void onDraw(Canvas canvas) {
// 1. 绘制背景
canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);

// 2. 绘制滑块

if(isTouchMode){
// 根据当前用户触摸到的位置画滑块

// 让滑块向左移动自身一半大小的位置
float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;

int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();

// 限定滑块范围
if(newLeft < 0){
newLeft = 0; // 左边范围
}else if (newLeft > maxLeft) {
newLeft = maxLeft; // 右边范围
}

canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {
// 根据开关状态boolean, 直接设置图片位置
if(mSwitchState){// 开
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 关
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}
}

}

boolean isTouchMode = false;
private OnSwitchStateUpdateListener onSwitchStateUpdateListener;
// 重写触摸事件, 响应用户的触摸.
@Override
public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouchMode = true;
System.out.println("event: ACTION_DOWN: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
System.out.println("event: ACTION_MOVE: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX();

float center = switchBackgroupBitmap.getWidth() / 2.0f;

// 根据当前按下的位置, 和控件中心的位置进行比较. 
boolean state = currentX > center;

// 如果开关状态变化了, 通知界面. 里边开关状态更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 状态传出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}

mSwitchState = state;
break;


default:
break;
}


// 重绘界面
invalidate(); // 会引发onDraw()被调用, 里边的变量会重新生效.界面会更新

return true; // 消费了用户的触摸事件, 才可以收到其他的事件.
}


/**
* 设置背景图
* @param switchBackground
*/
public void setSwitchBackgroundResource(int switchBackground) {
switchBackgroupBitmap = BitmapFactory.decodeResource(getResources(), switchBackground);
}


/**
* 设置滑块图片资源
* @param slideButton
*/
public void setSlideButtonResource(int slideButton) {
slideButtonBitmap = BitmapFactory.decodeResource(getResources(), slideButton);
}


/**
* 设置开关状态
* @param b
*/
public void setSwitchState(boolean mSwitchState) {
this.mSwitchState = mSwitchState;
}

public interface OnSwitchStateUpdateListener{
// 状态回调, 把当前状态传出去
void onStateUpdate(boolean state);
}


public void setOnSwitchStateUpdateListener(
OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
}


}


MainActivity.java

public class MainActivity extends Activity {


    private ToggleView toggleView;


@Override


    protected void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);


        setContentView(R.layout.activity_main);


        
        toggleView = (ToggleView) findViewById(R.id.toggleView);


//        toggleView.setSwitchBackgroundResource(R.drawable.switch_background);


//        toggleView.setSlideButtonResource(R.drawable.slide_button);


//        toggleView.setSwitchState(true);


//
        
        // 设置开关更新监听


        toggleView.setOnSwitchStateUpdateListener(new OnSwitchStateUpdateListener(){




@Override


public void onStateUpdate(boolean state) {


Toast.makeText(getApplicationContext(), "state: " + state, 0).show();


}


       
        });


    }




// @Override


// protected void onResume() {


// super.onResume();


// }


//    
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值