效果图:

定义一个类,取名为MySwitch.java,此类去继承View,为何是继承View而不是去继承ViewGroup呢,是因为自定义开关没有子控件,之需要操作自身绘制即可

package custom.view.upgrade.my_switch;

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.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View; import custom.view.R; public class MySwitch extends View implements View.OnClickListener { private static String TAG = MySwitch.class.getSimpleName(); private Paint mPaint; /**
* 让布局中来指定实例化,得到属性集合AttributeSet
* @param context
* @param attrs
*/
public MySwitch(Context context, @Nullable AttributeSet attrs) {
super(context, attrs); initView(context, attrs);
initListener();
} // 定义按钮背景图片
private Bitmap bmSwitchBackground; // 定义按钮拖动的图片
private Bitmap bmSwitchDrag; // 定义开关的状态 true || false , 默认是关闭状态
private boolean switchStatus; // 定义开关的临时记录状态
private boolean tempSwitchStatus; // 定义按钮拖动距离左边的距离
private int dragLife = -1; /**
* 初始化工作
*/
private void initView(Context context, AttributeSet attrs) {
mPaint = new Paint();
mPaint.setAntiAlias(true); // 抗锯齿 // 获取属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MySwitch);
bmSwitchBackground = ((BitmapDrawable) typedArray.getDrawable(R.styleable.MySwitch_switch_background)).getBitmap();
bmSwitchDrag = ((BitmapDrawable) typedArray.getDrawable(R.styleable.MySwitch_switch_drag)).getBitmap();
switchStatus = typedArray.getBoolean(R.styleable.MySwitch_switch_status, false);
} public void setBmSwitchBackground(int switchBackground) {
this.bmSwitchBackground = BitmapFactory.decodeResource(getResources() ,switchBackground);
} public void setBmSwitchDrag(int switchDrag) {
this.bmSwitchDrag = BitmapFactory.decodeResource(getResources(), switchDrag);
} public void setSwitChStatus(boolean switchStatus) {
this.switchStatus = switchStatus;
dragLife = -1;
} /**
* 初始化事件
*/
private void initListener() {
setOnClickListener(this);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 宽度是:按钮背景的宽度
// 高度是:按钮背景的高度
// 测量自身View
setMeasuredDimension(bmSwitchBackground.getWidth(), bmSwitchBackground.getHeight());
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); // 绘制按钮背景
canvas.drawBitmap(bmSwitchBackground, 0, 0, mPaint); if (dragLife != -1) {
canvas.drawBitmap(bmSwitchDrag, dragLife, 0 , mPaint);
} else if (dragLife == -1) {
if (switchStatus) {
// 打开状态
// 滑动点向右就是开启状态
int openDragLife = getLifeDragMaxValue();
canvas.drawBitmap(bmSwitchDrag, openDragLife, 0, mPaint);
moveEndX = openDragLife;
} else {
// 关闭状态
canvas.drawBitmap(bmSwitchDrag, 0, 0, mPaint);
moveEndX = 0;
} // 当开关的状态发生变化后,回调方法告诉用户,开关改变了
if (null != onSwitchChangeListener && switchStatusChange) {
if (tempSwitchStatus != switchStatus) {
onSwitchChangeListener.onSwitchChange(switchStatus);
}
}
}
} private float downX;
private int moveEndX; private float clickDown;
private float clickMove; // 开关状态是否发送了改变
private boolean switchStatusChange; @Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event); // 必须要调用此方法,onClick点击事件方法才会生效
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
isClick = true;
clickDown = event.getX();
switchStatusChange = false;
tempSwitchStatus = switchStatus;
break;
case MotionEvent.ACTION_MOVE:
// Log.d(TAG, ">>>>>不加等于:" + (int) (event.getX() - downX));
moveEndX += (int) (event.getX() - downX);
Log.d(TAG,">>>>>>加等于:" + moveEndX); if (moveEndX > getLifeDragMaxValue()) {
moveEndX = getLifeDragMaxValue();
} else if (moveEndX < 0){
moveEndX = 0;
} dragLife = moveEndX;
invalidate(); downX = event.getX(); clickMove = downX;
if (Math.abs(clickMove - clickDown) > 5) {
isClick = false;
} break;
case MotionEvent.ACTION_UP:
if (dragLife > (getLifeDragMaxValue() / 2)) {
dragLife = -1;
switchStatus = true;
switchStatusChange = true;
} else if (dragLife >= 0){
dragLife = -1;
switchStatus = false;
switchStatusChange = true;
} else {
switchStatusChange = false;
}
invalidate();
// upX = (int) event.getX();
break;
default:
break;
}
return true;
} private int getLifeDragMaxValue() {
return bmSwitchBackground.getWidth() - bmSwitchDrag.getWidth();
} /*@Override
protected void onFinishInflate() {
super.onFinishInflate(); if (switchStatus) {
moveEndX = getLifeDragMaxValue();
Log.d(TAG, ">>>>>>>>>>>>>>>>>onFinishInflate()............ getLifeDragMaxValue():" + getLifeDragMaxValue());
}
}*/ /**
* 定义点击事件状态
*/
private boolean isClick = true; @Override
public void onClick(View v) {
Log.d(TAG, "onClick() isClick:" + isClick);
if (isClick) {
if (switchStatus) {
switchStatus = false;
switchStatusChange = true;
} else {
switchStatus = true;
switchStatusChange = true;
}
// switchStatus = (switchStatus==true?false:true);
dragLife = -1;
invalidate();
}
} private OnSwitchChangeListener onSwitchChangeListener; /**
* 用户设置的 状态监听
* @param onSwitchChangeListener
*/
public void setOnSwitchChangeListener(OnSwitchChangeListener onSwitchChangeListener) {
this.onSwitchChangeListener = onSwitchChangeListener;
}
}

布局文件中去引用写好的自定义开关类

并设置自定义属性:

     myswitch:switch_background="@mipmap/switch_background"
myswitch:switch_drag="@mipmap/switch_drag"
myswitch:switch_status="true"
<!-- 自定义开关升级版 -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myswitch="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".upgrade.MainActivity"> <!-- 使用wrap_content,是因为不知道按钮的背景有多大,更加按钮图片的改变而变化 -->
<custom.view.upgrade.my_switch.MySwitch
android:id="@+id/custom_myswitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
myswitch:switch_background="@mipmap/switch_background"
myswitch:switch_drag="@mipmap/switch_drag"
myswitch:switch_status="true"
/> </RelativeLayout>

自定义规则arrts.xml文件声明:

<?xml version="1.0" encoding="utf-8"?>
<resources> <declare-styleable name="MySwitch"> <attr name="switch_status" format="boolean" /> <attr name="switch_background" format="reference" /> <attr name="switch_drag" format="reference" /> </declare-styleable> </resources>

模拟用户来使用:

MySwitch mySwitch = findViewById(R.id.custom_myswitch);

        // 设置开关的背景图片
mySwitch.setBmSwitchBackground(R.mipmap.switch_background); // 设置开关拖动的图片
mySwitch.setBmSwitchDrag(R.mipmap.switch_drag); // 设置开关的状态,打开、关闭
mySwitch.setSwitChStatus(false); mySwitch.setOnSwitchChangeListener(new OnSwitchChangeListener() {
@Override
public void onSwitchChange(boolean switchChangeStatus) {
String result;
if (switchChangeStatus) {
result = "打开";
} else {
result = "关闭";
}
Toast.makeText(MainActivity.this, "开关已" + result, Toast.LENGTH_SHORT).show();
}
});

Android-自定义开关(升级版)的更多相关文章

  1. Android 自定义 View 绘制

    在 Android 自定义View 里面,介绍了自定义的View的基本概念.同时在 Android 控件架构及View.ViewGroup的测量 里面介绍了 Android 的坐标系 View.Vie ...

  2. android 自定义动画

    android自定义动画注意是继承Animation,重写里面的initialize和applyTransformation,在initialize方法做一些初始化的工作,在applyTransfor ...

  3. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  4. Android自定义View4——统计图View

    1.介绍 周末在逛慕课网的时候,看到了一张学习计划报告图,详细记录了自己一周的学习情况,天天都是0节课啊!正好在学习Android自定义View,于是就想着自己去写了一个,这里先给出一张慕课网的图,和 ...

  5. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  6. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  7. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  8. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  9. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

随机推荐

  1. js cookie 工具类

    /*cookie start*/ var Cookie=new function(){ //添加cookie this.add=function(name,value,hours){ var life ...

  2. ddt数据驱动

    数据驱动原理 1.测试数据为多个字典的list类型 2.测试类前加修饰@ddt.ddt 3.case前加修饰@ddt.data() 4.运行后用例会自动加载成三个单独的用例 5.测试结果: Testi ...

  3. 面试总结之JAVA

    1. what is thread safe? 线程安全就是说多线程访问同一代码,不会产生不确定的结果.编写线程安全的代码是低依靠线程同步.线程安全: 在多线程中使用时,不用自已做同步处理线程不安全: ...

  4. windows8.1中组件服务DCOM配置里属性灰色不可修改的解决办法

    由于电脑升级,更换成了windows8.1的64位操作系统,今天遇到组件服务中DCOM配置里IIS Admin Service属性呈现灰色,不能修改. 查找官方文档,原来这是win8.1 x64的安全 ...

  5. 给iOS开发新手送点福利,简述UIAlertView的属性和用法

    UIAlertView 1.Title 获取或设置UIAlertView上的标题. 2.Message 获取或设置UIAlertView上的消息 UIAlertView *alertView = [[ ...

  6. 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #3 如何编写内核模块

    HACK #3 如何编写内核模块 本节将介绍向Linux内核中动态添加功能的结构—内核模块的编写方法.内核模块Linux内核是单内核(monolithic kernel),也就是所有的内核功能都集成在 ...

  7. samba性能调优

    不知道有多少公司的内部打印及文件服务器是用的Linux,我想肯定不会太多,因为Windows实现起来更方便,更快速,当然,Windows也 是更Danger. 因为Windows有太多不确定性的东西, ...

  8. zmq消息订阅

    一个需求,用户预约了手机超时没有使用,要通知到预约的用户“设备超时”. 我本来是自己这一端计时然后超时后推送通知的. 但是上海测说他那边计时,然后释放手机.我这边只要订阅他那边的消息就好了. 外部的应 ...

  9. Spring Boot 16 条最佳实践

    Spring Boot是最流行的用于开发微服务的Java框架.在本文中,我将与你分享自2016年以来我在专业开发中使用Spring Boot所采用的最佳实践.这些内容是基于我的个人经验和一些熟知的Sp ...

  10. LINQPad 4 初次使用心得

    最近学习EntityFramework,于是接触了LinqPad这款享誉已久的软件,深感相见恨晚.软件具体不多做介绍了,只简单介绍下使用方法. 数据库操作 添加数据库连接 1,首先通过点击Add co ...