自定义View的总结(自定义滑动开关)

前言:

由于有些控件,在android中样式比较挫,并不能满足我们的需求,此时,我们可以将其进行一个自定义,下面一以一个自定义编写的ToggleButton为例,

来简要说明下,自定义所涉及到的一些步骤;以下是自定义控件ToggleButton的效果图:

自定义View的总结(自定义滑动开关)_第1张图片


其是由两张图片组成的:

<1>

 

<2> 


下面我们通过这个示例,来说明下,如何编写一个自定义view控件!!!!



步骤一:自定义控件属性的定义和使用

自定义控件属性的定义:

1>关于理解android的自定义属性

可以先参考此篇博客:http://blog.csdn.net/u010661782/article/details/50864079


2>在本示例中,我们来分析一下,我们需要自定义哪些属性?

第一:图片<1>的背景图片,需要做一个更改,因为每个人的喜好不一样;

第二:图片<2>的背景图片也需要做更改;

第三:整个控件的一个状态,开抑或关,这个属性,我们也需要进行一个定义;


在 values/attrs.xml 中,其自定义属性的内容定义如下:

xml version="1.0" encoding="utf-8"?>

    
    name="switchBackground" format="reference"/>
    name="slideBackground" format="reference"/>
    name="switchState" >

        name="open" value="0">
        name="close" value="1">

    

    name="ToggleButton">
        name="switchBackground" />
        name="slideBackground"/>
        name="switchState"/>
    

    

分析:

1>

name="switchBackground" format="reference"/>
为图片<1>的背景图片的属性,其属性名称switchBackground, 其属性的值的类型为 reference


2>

name="slideBackground" format="reference"/>
为图片<2>的背景图片的属性,其属性名称为 slideBackground, 其属性的值的类型为 reference


3>

name="switchState" >

        name="open" value="0">
        name="close" value="1">

    
其为开关的状态,其属性名称为 switchState, 其属性的值的类型为 enum , 其有两种属性值:open和close

其中: open 的值为 整型0 ,close的值为整型 1;


注:1>,2>,3>都是为属性的定义


4>

name="ToggleButton">
    name="switchBackground" />
    name="slideBackground"/>
    name="switchState"/>
编写其的目的是为了方便让系统自动帮我们生成一个包含这三个属性的一个资源ID的数组,方便我们在后面予以调用,而无需自己手动去定义一个这样的数组;

记住:若属性在前面已经定义了,那么在 declare-stayleable中就不可以再重复定义,也就是不能再为属性指定其format了;


自定义控件属性的使用:

1>如何在布局文件中,使用我们自定义的属性

定义好了属性之后,我们就可以在布局文件中去使用了,如何使用呢?见content_main.xml,其示例如下:

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:toggle="http://schemas.android.com/apk/res-auto"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.example.dai.togglebutton.MainActivity"
    tools:showIn="@layout/activity_main">

            android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:id="@+id/tb"
        toggle:slideBackground="@drawable/btn_slip"
        toggle:switchBackground="@drawable/bkg_switch"
        toggle:switchState="close"
        />


分析:

1>

xmlns:toggle="http://schemas.android.com/apk/res-auto"
这只是为我们定义的属性定义了一个命名空间,其命名空间的名字为toggle


2>

在使用我们自定义的控件时,需要在控件名称前加上我们自定义控件所在的包名


3>

toggle:slideBackground="@drawable/btn_slip"
toggle:switchBackground="@drawable/bkg_switch"
toggle:switchState="close"
为我们自定义控件的属性slideBackground, switchBackground, switchState进行一个赋值,

也是自定义控件在布局文件中如何使用的一个demo



2>如何在java代码中,获取我们在布局文件中所定义的属性的值?

public ToggleButton(Context context) {         //构造器<1>
    this(context, null);         //构造器<1>调用构造器<2>
}

public ToggleButton(Context context, AttributeSet attrs) {  //构造器<2>

    this(context, attrs, 0);    //构造器<2>调用构造器<3>
}

//最终都会转化为调用构造器<3>
public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {    //构造器<3>  
    super(context, attrs, defStyleAttr);

    TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ToggleButton,defStyleAttr,0);
    int n = typedArray.getIndexCount();
    for(int i=0; i; i++){
        /*
        * 在这里注意:i并非资源文件的id
        * 所以需要int attr = typedArray.getIndex(i);
        * */
        int attr = typedArray.getIndex(i);
        switch (attr){                     
            case R.styleable.ToggleButton_switchBackground:   
                bitmapSwitch = BitmapFactory.decodeResource(getResources(),typedArray.getResourceId(attr,0));
                Log.e("bitmapSwitch",bitmapSwitch.toString());
                break;

            case R.styleable.ToggleButton_slideBackground:
                bitmapSlide = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(attr,0));
                Log.e("bitmapSlide",bitmapSlide.toString());
                break;

            case R.styleable.ToggleButton_switchState:
                currentToggleButtonState = typedArray.getInt(attr,0);
                Log.e("currentState",""+currentToggleButtonState);
                break;

        }

    }
    /*这一步不要遗忘*/
    typedArray.recycle();

}

分析:

1>

由上述注解,我们可知,构造器<>,构造器<2>最终都会转化调用构造器<3>


2>

先获得TypeArray:

TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ToggleButton,defStyleAttr,0);
再获得TypeArray中的属性的个数:

int n = typedArray.getIndexCount();
再通过 i 索引来遍历整个控件的属性:

int attr = typedArray.getIndex(i);


3>

switch (attr){                     
            case R.styleable.ToggleButton_switchBackground:   
                bitmapSwitch = BitmapFactory.decodeResource(getResources(),typedArray.getResourceId(attr,0));
                Log.e("bitmapSwitch",bitmapSwitch.toString());
                break;

            case R.styleable.ToggleButton_slideBackground:
                bitmapSlide = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(attr,0));
                Log.e("bitmapSlide",bitmapSlide.toString());
                break;

            case R.styleable.ToggleButton_switchState:
                currentToggleButtonState = typedArray.getInt(attr,0);
                Log.e("currentState",""+currentToggleButtonState);
                break;

        }
是将我们从布局文件中获得我们自定义的三个属性值,方便我们在后面进行重绘;


其中

R.styleable.ToggleButton_switchBackground

R.styleable.ToggleButton_slideBackground
R.styleable.ToggleButton_switchState
是我们在 value/attrs.xml中定义declare-styleable节点时,系统自动为我们生成的;


4>

在构造器编写代码时,不要忘记添加这一步:

    typedArray.recycle();


步骤二:自定义控件的测量和绘制

在我们自定义View控件时,一般需要重写View的两个方法:

onMeasure()和onDraw()


重写自定义控件的测量:

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

}
其目的是为了设置我们ToggleButton控件的宽和高,而其宽和高正好可以通过bitmapSwitch图片的宽和高来设置;

注意:在这里使用

setMeasuredDimension()

来设置我们控件的宽和高;


重写自定义控件的绘制:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    /*
    * 思考一下,这里是否需要画笔?
    * 由于这里没有文本的绘制,所以这里可以不需要画笔;
    * */
    //Paint paint = new Paint();
    canvas.drawBitmap(bitmapSwitch, 0, 0, null);

    if(isSliding){  //若开始滑动或正在滑动
        float left = currentX - bitmapSlide.getWidth()/2;
        if(left < 0){     //当滑块滑到左边边界时,不允许其再往左边滑动;
            left =0;
        }
        if(left > bitmapSwitch.getWidth() - bitmapSlide.getWidth()){   //当滑块滑到右边边界时,不允许其再往右边滑;
            left = bitmapSwitch.getWidth() - bitmapSlide.getWidth();
        }
        canvas.drawBitmap(bitmapSlide, left, 0, null);
    }else {    //若放手停止滑动
        if(currentToggleButtonState == 1){   //若点击的位置出在左边,则其状态为关闭状态
            canvas.drawBitmap(bitmapSlide, 0, 0, null);
        }else {                                    //若点击的位置出在右边,则其状态为开启状态
            canvas.drawBitmap(bitmapSlide, bitmapSwitch.getWidth() - bitmapSlide.getWidth(), 0, null);
        }

    }

}

在onMeasure()方法中,我们知道了控件的大小,但是控件在父窗体上显示出什么效果图出来,

这就需要我们通过重写onDraw()方法来达到这点了。(至于控件摆放在父窗体的什么位置,则由父窗体的onLayout()方法来确定的)


我们知道:在这里,对于bitmapSwitch,我们无需对其逻辑进行修改,因为其位置始终是固定的,我们只需要修改bitmapSlide(滑动块)的位置,

即可达到我们拖动滑动块时,滑动块每次都重绘,从而显示出滑块动态滑动的效果;


至于滑块什么时候滑动以及滑动到什么位置,这就需要我们对滑块的滑动进行一个侦听,在这里使用:

@Override
public boolean onTouchEvent(MotionEvent event) {

    currentX = event.getX();
    switch (event.getAction()){

        case MotionEvent.ACTION_DOWN:
            isSliding = true;
            break;

        case MotionEvent.ACTION_MOVE:
            break;

        case MotionEvent.ACTION_UP:
            isSliding = false;
            if(currentX <= bitmapSwitch.getWidth()/2){
                if(currentToggleButtonState != 1){
                    if(listener != null){
                        /*onToggleButtonStateChange()方法此时才会执行*/
                        listener.onToggleButtonStateChange(1);
                    }
                }
                currentToggleButtonState = 1;     //其状态为关闭
            }else {
                if(currentToggleButtonState !=0){
                    if(listener != null){
                        listener.onToggleButtonStateChange(0);
                    }
                }
                currentToggleButtonState = 0;     //其状态为开启
            }

            break;

    }
    /*当X值变化时,让控件重新绘制
    * 这时就会调用onDraw()方法;
    * */
    invalidate();
    return true;
}
来获取当前点击的X坐标的变化,然后通过X坐标的变化以及通过
invalidate();

来使控件进行一个重绘,从而达到我们滑块拖到什么位置时,就让其重绘在什么位置上,从而给我们的感觉就是:滑块是被我们拖着动的;



步骤三:自定义控件方法的定义及使用:


自定义控件方法的定义:

/*设置switch的背景图片*/
public void setSwitchBackgroundResource(int id){

    bitmapSwitch = BitmapFactory.decodeResource(getResources(),id);

}

/*设置slide的背景图片*/
public void setSlideBackgroundResource(int id){

    bitmapSlide = BitmapFactory.decodeResource(getResources(),id);

}


/*
* 0:表示打开
* 1:表示关闭
* */
public void setTonggleButtonState(int state){

    currentToggleButtonState = state;

}


/*如何设置状态改变的侦听*/
private OnToggleButtonStateChangeListener listener;

/*当外部调用这个函数的时候,其首先需要实现listener这个对象,然后在执行这函数时,
将这个对象赋值给this.listener,此时this.listener就不可能为null,然后通过
onTouchEvent()中的listener.onToggleButtonStateChange()方法,从而达到了
当控件的属性改变时,其方法也就会被执行的效果;

*/
public void onToggleButtonStateChange(OnToggleButtonStateChangeListener listener){
    this.listener = listener;   //把listener的指针赋值给this.listener,这样通过修改this.listener的
}                                  //onToggleButtonStateChange方法,间接就修改了listener中的方法;
                                    //因为this.listener和listener都是指向同一个实体;


public interface OnToggleButtonStateChangeListener{
    void onToggleButtonStateChange(int toggleButtonState);
}

自定义控件方法的使用:

在MainActivity.java的onCreate()方法中,其控件方法的使用如下:

toggleButton = (ToggleButton) findViewById(R.id.tb);
toggleButton.onToggleButtonStateChange(new ToggleButton.OnToggleButtonStateChangeListener() {
    @Override
    public void onToggleButtonStateChange(int toggleButtonState) {
        Toast.makeText(MainActivity.this,toggleButtonState==0?"open":"close",Toast.LENGTH_SHORT).show();
    }
});


总结:

通过自定义滑动开关,我们解决了自定义控件属性的自定义及使用,

也解决了自定义控件编写流程及自定义控件的方法的定义及其使用,希望大家对自定义控件有个更新的认识,

喜欢的话,就给俺留个言哈~~~~~


整个工程源码链接如下:

http://download.csdn.net/detail/u010661782/9462381


注:另附一篇关于自定义View的好文章:

http://blog.csdn.net/lmj623565791/article/details/24252901

你可能感兴趣的:(Android应用开发基础篇)