自定义Switch滑动开关

要自定义控件需要先继承View然后再重写两个方法,分别是onDraw方法和onMeasure().
(有时还可能会用到onLayout方法—改变该自定义控件在ViewGroup中的位置)
(加载自定义属性和的layout布局文件的代码可写在构造方法中,findviewbyId实例化控件写在onFinishInflate方法中更好些)
(在某些情况下还会用到onSizeChanged方法,view的绘图触发事件可写在这里。)
如果只重写onFinishInflate(),onSizeChanged(),onDraw()三个方法时执行顺序是:

 22:23:03.597: D/mDebug(9715): onFinishInflate  
 22:23:03.667:  D/mDebug(9715):onSizeChanged,w=240,h=282,oldw=0,oldh=0  
 22:23:03.727: D/mDebug(9715): onDraw  

//onDraw的参数是canvas: the canvas on which the background will be drawn
onDraw方法:主要负责绘制图形(也可是Bitmap)
注意哦:该控件继承的对象是,直接或,间接继承View的ViewGroup,比如LinearLayout,可能onDraw不会被执行,这是就要注意,setWillNotDraw(false);setWillNotDraw===>让不绘图,false。双重否定,就是让他绘图,之后onDraw才会被调用。

onMeasure方法主要是负责控制该view的大小(没有重写时是默认调用系统的onMeasure方法)。
有个没意义的小实验:有时候控件不想被xml中写死,想在代码中动态的改变控件的大小(只是外部大小,内容没有进行缩放,设置之后部分内容可能被遮掉),

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);  
        int width = MeasureSpec.getSize(widthMeasureSpec);  
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.UNSPECIFIED);
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.UNSPECIFIED);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

在onMeasure方法中加上这段代码,将测量模式设置为MeasureSpec.UNSPECIFIED,也就是大小不受限制,Java代码中可以通过setMinimumHeight()和setMinimumWidth()动态的修改控件大小。
相当于在外部调用了本来外部不能被调用到的setMeasuredDimension,控制控件的大小。

进入正题,其实开关就是两个图片构成的,一张是背景图片一张是开罐图片,只需要控制开关图片,就能显示的看到开关的改变。就是通过监听用户对控件(其实就是背景图片上面的开关图片)对它的滑动或点击事件。

实现方式可能有多种,有些方式区分多种状态标记,且点击(DOWN)后立即响应开关滑至左边或右边。而这里没有用多种状态标记,且是对点击事件(DOWN)不立刻处理,而是在松开后才会移动到左边或右边,滑动事件是立即处理,移动的时候一直在重绘。

如果是直接通过代码添加那就需要测量出那张图drawable_bg的大小作为
控件的总宽度bgbitmap.getWidth(),高度也是一样的,
但是如果想添加在xml布局中那就需要自定义属性,然后通过typedArray
获取到用户在xml中设置的drawable,得到那张图片的width和height.作为总的大小,相比之下xml中自定义属性(背景图,开关图,开关的状态灵活性更高)可以动态的替换控件的背景图和开关图,
这里有个麻烦的地方,自定义属性得到的是Drawable,而控件的canvas只能化能画Bitmap,所以需要将Drawable转成Bitmap。方法有两种。
方法一:

    public Bitmap DrawableConventToBitmap(Drawable drawable) {
        // 创建一个空的Bitmap 里面是参数Drawable的大小信息    
        // Drawable--->创建Bitmap
        Bitmap bitmap = Bitmap.createBitmap(
                drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(), 
                drawable.getOpacity()!=PixelFormat.OPAQUE?
                  Config.ARGB_8888:Bitmap.Config.RGB_565
        );
        // 依据这Bitmap再创建一个canvas
        // Bitmap--->创建Canvas
        Canvas canvas = new Canvas(bitmap);
        // 为drawable设置一个大小,当drawable调用onDraw方法时就会去在这个范围里面去draw
        // Drawable宽高属性getIntrinsic--->Drawable.setBound()
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        // 往canvas里面画,范围大小就是上一部设置的SetBound的大小
        // canvas--->draw()
        drawable.draw(canvas);
        return bitmap;
    }

方法二:

Bitmap bitmap = null; 
BitmapDrawable bd = (BitmapDrawable) drawable;
bitmap = bd.getBitmap();
return bitmap;

1.画出图片,开关的背景图片和开关图片,背景图片在开关图的下面。
背景图片从开关的左上角开始画起也就是(0,0)坐标,开关图片要注意需要从控件的(0,size/2)开始画起,

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(bgbitmap, 0, 0, paint); // 在左上角开始绘制
        // 在宽:bgbitmapWidth左边绘制,高bgbitmapHeight的一般(相当于垂直居中)
        canvas.drawBitmap(switchbitmap, changeposition,
                (bgbitmap.getHeight() - switchbitmap.getHeight()) / 2, paint);
    }

2,控制自定义view的大小,在onMeasure里面写:

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

3 实现onTouchEvent方法或者OnTouchListener接口都行.在onDraw里面画开关图片的时候预留一个参数changeposition(x轴上的坐标)去
出发事件后调用invalidate();通知view去重绘一下。
注意:开关的触发点在滑动开关图片的中间。所以所有的点击的位置需要减去开关图的一半

startposi = event.getX() - switchbitmap.getWidth() / 2;

Down事件的时候只需要记录开关图的x位置

Move事件就是实时的获取到x位置然后再去调用invalidate()

UP事件主要是判断当前位置是在左边还是右边,判断之后直接调用invalidate(),让它绘制到左边或者右边。
自定义Switch滑动开关_第1张图片

事件判别的代码:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        float startposi = 0;
        float stopPointx = 0;
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 记录点下的x位置
            startposi = event.getX() - switchbitmap.getWidth() / 2;
            System.out.println("stopPointx-->" + stopPointx);
            break;
        case MotionEvent.ACTION_MOVE:
            stopPointx = event.getX() - switchbitmap.getWidth() / 2;
            // 因为是想在switchbitmap的中间触发
            // 所以画图开始的位置是从所点击的位置要减去 switchbitmap/2的距离
            changeposition = (int) stopPointx;
            if (changeposition >= bgbitmap.getWidth() - switchbitmap.getWidth()) {
                changeposition = bgbitmap.getWidth() - switchbitmap.getWidth();
            } else if (changeposition < 0) {
                changeposition = 0;
            }
            invalidate();
            // 重新刷新起始点
            startposi = event.getX() - switchbitmap.getWidth() / 2;
            break;
        case MotionEvent.ACTION_UP:
            float now = event.getX() - switchbitmap.getWidth() / 2;
            if (now >= bgbitmap.getWidth() / 2 - switchbitmap.getWidth() / 2) {
                changeposition = bgbitmap.getWidth() - switchbitmap.getWidth();
                isSwitch = true;
                switchListener.onSwitch(true);
            } else {
                changeposition = 0;
                isSwitch = false;
                switchListener.onSwitch(false);
            }
            invalidate();
            break;
        default:
            break;
        }
        return true;
    }

控件自定义完成,但是使用起来还需要给外部留接口操作,对不同状态进行不同操作。设计接口回调。
步骤1:
创建接口,以及接口中的方法

interface onSwitchListener {
        public void onSwitch(boolean toggleState);
    }

步骤二:
创建接口对象

private onSwitchListener switchListener;

步骤三:
给外部public方法设置接口对象

    public void setOnSwitchListener(onSwitchListener switchListener) {
        this.switchListener = switchListener;
    }

步骤四:
在该类中合适的地方调用接口中的方法

case MotionEvent.ACTION_UP:
float now = event.getX() - switchbitmap.getWidth() / 2;
if (now >= bgbitmap.getWidth() / 2 - switchbitmap.getWidth() / 2) {
changeposition = bgbitmap.getWidth() - switchbitmap.getWidth();
                isSwitch = true;
                switchListener.onSwitch(true);
} else {
    changeposition = 0;
    isSwitch = false;
                switchListener.onSwitch(false);
}
    invalidate();
    break;
    default:
    break;

使用时外部需要set接口或者是implements接口,并且实现接口中的方法。

public class MainActivity extends AppCompatActivity implements onSwitchListener {
    private CustomSwitchView switchview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        switchview = (CustomSwitchView) findViewById(R.id.customswitch);
        switchview.setOnSwitchListener(this);
    }

    @Override
    public void onSwitch(boolean toggleState) {
        if (toggleState == true) {
            Toast.makeText(MainActivity.this, "开了", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(MainActivity.this, "关了", Toast.LENGTH_SHORT).show();
        }
    }

}

运行结果:
自定义Switch滑动开关_第2张图片

你可能感兴趣的:(Android)