介绍

前段时间,我看到了一篇关于可滑动开关Switch组件的文章,效果图如下:



思路也挺简单的:这个控件主要由田径场式背景和滑块组成。他将田径场式背景分为3部分,最左边的半圆,中间的两条直线部分和最右边的半圆。假设线的宽度为lx,半圆的半径则为lx的一半,通过监听touch事件,不停的绘制两个半圆和两条线段、滑块,从而达到滑块跟着手指滑动的显示效果。

虽然效果是实现了,但是田径场式背景被拆分绘制,我感觉还是有点繁琐,不统一,我就想有没有什么办法可以一次性将这个背景画出来?答案是有的(你这不是废话~)。

两种方法的差异

我们都知道在Android中有提供用来绘制各种图案的类:Path。Path主要用于绘制复杂的图形轮廓,比如折线,圆弧以及各种复杂图案。现在再来看这个田径场式背景,说白了就是一个圆角矩形形状,这个圆角设置足够大就可以了。我们可以使用Path中的addRoundRect(RectF rect, float[] radii, Direction dir)绘制出圆角矩形。其中第二个参数是8个值(矩形的4个角)的数组,4对[ x,y ]半径。

实现

这个控件支持自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- JYSwitchButton的自定义属性
openColor:开启状态的颜色
closeColor:关闭状态的颜色
circleColor:滑动圆形图标的颜色
openText:开启状态的文本
closeText:关闭状态的文本
openTextColor:开启状态的文本颜色
closeTextColor:关闭状态的文本颜色
textSize:字体大小
-->
<declare-styleable name="switchbutton">
<attr name="openColor" format="integer"></attr>
<attr name="closeColor" format="integer"></attr>
<attr name="circleColor" format="integer"></attr>
<attr name="openText" format="string"></attr>
<attr name="closeText" format="string"></attr>
<attr name="openTextColor" format="integer"></attr>
<attr name="closeTextColor" format="integer"></attr>
<attr name="textSize" format="dimension"></attr>
</declare-styleable>
</resources>

也提供了一些设置的方法:

 * 支持自定义颜色值setOpenColor/setCloseColor/setCircleColor;
* 支持设置偏移量setOffset;
* 支持设置初始状态changeState;
* 支持获取默认状态getDefaultState;
* 支持开启/关闭状态的监听setListener(OnSwitchStateChangeListener);
* 支持设置滑动圆形图标的边距setCirclePadding;
* 支持设置开启/关闭文本和颜色setOpenText、setCloseText、setOpenTextColor、setCloseTextColor
* 支持设置文本字体大小setTextSize

话不多说了,撸代码去:

package com.ha.cjy.jyswitchbutton;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View; /**
* 滑动开关按钮
*
* Created by cjy on 17/11/14.
*/ public class JYSwitchButton extends View {
private Context mContext; //画笔
private Paint mPaint;
//移动距离
private int mCurrentX;
//宽度
private int mViewWidth;
//高度
private int mViewHeight;
//Y中心
private int mCenterY;
//左边圆的X中心点
private int mStartX;
//右边圆的X中心点
private int mEndX;
//滑动圆形图标的半径
private int mRadius;
//滑动圆形图标的边距
private int mCirclePadding = 2;
//是否已经初始化好宽高了
private boolean mIsInit = false;
//是否开启,默认是关闭状态
private boolean mIsOpen = false;
//状态监听器
private OnSwitchStateChangeListener mListener;
//矩形4个角的半径坐标,左上,右上,右下,左下(顺时针)
private float[] mRadiusArr = new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f};
//偏移量,用来控制view的显示大小
private int mOffset = 20;
//开启状态的背景色
private int mOpenColor = Color.BLUE;
//关闭状态的背景色
private int mCloseColor = Color.GRAY;
//滑动圆形图标的颜色
private int mCircleColor = Color.LTGRAY;
//字体最大值、最小值
private float mTextMaxSize = 32;
private float mTextMinSize = 10;
//开启/关闭文本
private String mOpenText="";
private String mCloseText="";
//开启/关闭文本的颜色
private int mOpenTextColor = Color.WHITE;
private int mCloseTextColor = Color.WHITE; public JYSwitchButton(Context context) {
this(context, null);
} public JYSwitchButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
} public JYSwitchButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
getProperty(context,attrs);
init();
defaultRoundRadius();
} /**
* 初始化操作
*/
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.BLACK);
} /**
* 获取自定义属性
* @param context
* @param attrs
*/
private void getProperty(Context context, AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.switchbutton);
mOpenColor = typedArray.getInteger(R.styleable.switchbutton_openColor,mOpenColor);
mCloseColor = typedArray.getInteger(R.styleable.switchbutton_closeColor,mCloseColor);
mCircleColor = typedArray.getInteger(R.styleable.switchbutton_circleColor,mCircleColor);
mOpenText = typedArray.getString(R.styleable.switchbutton_openText);
mCloseText = typedArray.getString(R.styleable.switchbutton_closeText);
if (mOpenText == null)
mOpenText = "";
if (mCloseText == null)
mCloseText = "";
mOpenTextColor = typedArray.getInteger(R.styleable.switchbutton_openTextColor,mOpenTextColor);
mCloseTextColor = typedArray.getInteger(R.styleable.switchbutton_closeTextColor,mCloseTextColor);
mTextMaxSize = typedArray.getDimension(R.styleable.switchbutton_textSize,mTextMaxSize); //取完属性,记得释放
typedArray.recycle();
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredWidth() / 2 + mOffset;
setMeasuredDimension(mViewWidth, mViewHeight);
mCenterY = mViewHeight / 2 - mOffset;
mRadius = mViewHeight / 2 - mOffset;
mCurrentX = mRadius;
mStartX = mRadius;
mEndX = mViewWidth - mRadius; mIsInit = true;
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); if (mIsInit) {
//控制滑动区域
mCurrentX = mCurrentX > mStartX ? mCurrentX : mStartX;
mCurrentX = mCurrentX < mEndX ? mCurrentX : mEndX; Path path = new Path();
RectF rectF = new RectF();
rectF.top = 0;
rectF.left = 0;
rectF.right = mViewWidth;
rectF.bottom = mRadius * 2;
//画圆角矩形背景图
path.addRoundRect(rectF, mRadiusArr, Path.Direction.CW); if (mIsOpen) {
//左边色块
mPaint.setColor(mOpenColor);
canvas.drawPath(path, mPaint);
//绘制文本
drawText(canvas,mOpenText,mOpenTextColor);
} else {
//右边色块
mPaint.setColor(mCloseColor);
canvas.drawPath(path, mPaint);
//绘制文本
drawText(canvas,mCloseText,mCloseTextColor);
}
//滑动圆形
mPaint.setColor(mCircleColor);
int realRadius = mRadius-mCirclePadding;
if (realRadius < mRadius/2 ){
realRadius = mRadius/2;
}else if(realRadius > mRadius){
realRadius = mRadius;
}
canvas.drawCircle(mCurrentX, mCenterY,realRadius, mPaint);
}
} /**
* 绘制文本
* @param canvas
* @param text 文本
* @param color 文本颜色
*/
private void drawText(Canvas canvas,String text,int color){
if(text.isEmpty())
return;
mPaint.setColor(color);
mPaint.setTextSize(mTextMaxSize);
//文本宽度
int textWidth = mViewWidth-mRadius*2;
float trySize = mTextMaxSize;
//根据文本宽度,字体大小适配
while (mPaint.measureText(text)<textWidth){
trySize += 1;
mPaint.setTextSize(trySize);
}
while (mPaint.measureText(text)>textWidth){
trySize -= 1;
if (trySize < mTextMinSize){
trySize = mTextMinSize;
break;
}
mPaint.setTextSize(trySize);
}
mPaint.setTextSize(px2sp(mContext,trySize)); int x = mIsOpen?mStartX+10:mEndX-mRadius-10;
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
int textHeight = (int)(fontMetrics.descent-fontMetrics.ascent);
int baseline = (int) (mCenterY+(mCenterY*1.0/3.0));
canvas.drawText(text,x,baseline,mPaint);
} /**
* 默认的圆角数据
*/
private void defaultRoundRadius() {
mRadiusArr[0] = 120;
mRadiusArr[1] = 120;
mRadiusArr[2] = 120;
mRadiusArr[3] = 120;
mRadiusArr[4] = 120;
mRadiusArr[5] = 120;
mRadiusArr[6] = 120;
mRadiusArr[7] = 120;
} @Override
public boolean onTouchEvent(MotionEvent event) {
int lastX = 0;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
lastX = (int) event.getX();
break;
}
case MotionEvent.ACTION_MOVE: {
//滑动的偏移量
mCurrentX = (int) (event.getX() - lastX);
break;
}
case MotionEvent.ACTION_UP: {
mCurrentX = (int) (event.getX() - lastX);
if (mCurrentX > mViewWidth / 2) {//从左到右滑动
mCurrentX = mEndX;
if (mIsOpen == false) {
mIsOpen = true;
if (mListener != null) {
mListener.onSwitchStateChange(mIsOpen);
}
}
} else {//从右向左滑动
mCurrentX = mStartX;
if (mIsOpen == true) {
mIsOpen = false;
if (mListener != null) {
mListener.onSwitchStateChange(mIsOpen);
}
}
}
break;
}
}
postInvalidate();
return true;
} /**
* 获取默认状态
* @return
*/
public boolean getDefaultState(){
return this.mIsOpen;
} /**
* 设置状态:开启/关闭
* @param isOpen 是否开启 true-开启 false-关闭
*/
public void changeState(boolean isOpen){
this.mIsOpen = isOpen;
postDelayed(new Runnable() {//延迟100毫秒,等计算好宽高再进行重新绘制
@Override
public void run() {
if (mIsInit) {
if (mIsOpen) {
mCurrentX = mEndX;
} else {
mCurrentX = mStartX;
}
}
invalidate();
}
},100); } /**
* 设置滑动圆形图标的边距
* @param padding
*/
public void setCirclePadding(int padding){
this.mCirclePadding = padding;
} /**
* 设置开启状态的颜色
* @param color
*/
public void setOpenColor(int color){
this.mOpenColor = color;
}
/**
* 设置关闭状态的颜色
* @param color
*/
public void setCloseColor(int color){
this.mCloseColor = color;
}
/**
* 设置滑动圆形的颜色
* @param color
*/
public void setCircleColor(int color){
this.mCircleColor = color;
} /**
* 设置开启状态的文本
* @param value
*/
public void setOpenText(String value){
this.mOpenText = value;
} /**
* 设置关闭状态的文本
* @param value
*/
public void setCloseText(String value){
this.mCloseText = value;
} /**
* 设置开启状态的文本
* @param color
*/
public void setOpenTextColor(int color){
this.mOpenTextColor = color;
} /**
* 设置关闭状态的文本颜色
* @param color
*/
public void setCloseTextColor(int color){
this.mCloseTextColor = color;
} /**
* 设置字体大小
* @param textSize
*/
public void setTextSize(int textSize){
this.mTextMaxSize = textSize;
} /**
* 设置偏移量
* @param offset 偏移量
*/
public void setOffset(int offset){
this.mOffset = offset;
} /**
* 设置监听器
* @param listener
*/
public void setListener(OnSwitchStateChangeListener listener) {
this.mListener = listener;
} /**
* 将px值转换为sp值,保证文字大小不变
* @param context
* @param pxValue
* @return
*/
private float px2sp(Context context, float pxValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (pxValue / fontScale);
} /**
* 开启/关闭状态的监听
*/
interface OnSwitchStateChangeListener {
/**
* 状态变化的事件
* @param isOpen true-开启 false-关闭
*/
void onSwitchStateChange(boolean isOpen);
}
}

以上注释写的很详细了。主要的有几点:

  • 重写了onMeasure方法,使控件的高度依赖于控件宽度,保证控件的宽高比;
  • 控制好滑块的滑动范围;
  • 根据文本宽度进行字体大小的适配;
  • 设置状态监听器OnSwitchStateChangeListener,在控件内部定义其对象和开放方法setListener,以便外部进行状态的监听和调用;
  • 设置状态(changeState)的时候会去重新绘制,但是这时绘制如果控件的宽高还没有计算出来,就会导致数据不正确,滑块的位置就会显示错误,所以需要延迟一段时间去执行绘制工作。
  • ###使用

  • 布局文件:在该布局中添加该控件即可,因为有自定义的属性,需要声明其命名空间:xmlns:jy="http://schemas.android.com/apk/res/com.ha.cjy.jyswitchbutton"
  • ```

    <com.ha.cjy.jyswitchbutton.JYSwitchButton
    android:id="@+id/btnSwitch"
    android:layout_marginTop="40dp"
    android:layout_marginLeft="40dp"
    android:layout_width="60dp"
    android:layout_height="wrap_content"
    jy:textSize="40sp"
    jy:openColor="@color/colorAccent"
    jy:closeColor="@color/colorPrimary"
    jy:circleColor="@color/colorWhite"
    jy:openText="开"
    jy:closeText="关"
    jy:openTextColor="@color/colorWhite"
    jy:closeTextColor="@color/colorWhite"/>
    <li>Activity代码如下:</li>

    package com.ha.cjy.jyswitchbutton;

    import android.graphics.Color;

    import android.support.v7.app.AppCompatActivity;

    import android.os.Bundle;

    import android.util.Log;

    import android.widget.Toast;

    public class MainActivity extends AppCompatActivity implements JYSwitchButton.OnSwitchStateChangeListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //滑动开关控件
    JYSwitchButton btnSwitch = (JYSwitchButton) findViewById(R.id.btnSwitch);

    // btnSwitch.changeState(true);

    // Log.i("state",btnSwitch.getDefaultState()+"");

    // btnSwitch.setOpenColor(Color.RED);

    // btnSwitch.setCloseColor(Color.GRAY);

    // btnSwitch.setCircleColor(Color.BLUE);

    btnSwitch.setListener(this);

    }

    @Override
    public void onSwitchStateChange(boolean isOpen) {
    Toast.makeText(this,"状态:"+(isOpen?"开启":"关闭"),Toast.LENGTH_SHORT).show();
    }

    }

    ###总结
     其实还有一些细节问题我没有在这篇文章上讲出,比如文本绘制的baseline实现还有些问题,希望感兴趣的同学可以自行研究代码并完善它。项目地址传送门摸[我的github](https://github.com/hacjy/JYSwitchButton)。

    自定义view-滑动开关的更多相关文章

    1. 自定义view(一)

      最近在学习自定义view  一遍看一别学顺便记录一下 1.View的测量-------->onMeasure() 首先,当我们要画一个图形的时候,必须知道三个数据:位置,长度,宽度   才能确定 ...

    2. Android 自定义View及其在布局文件中的使用示例

      前言: 尽管Android已经为我们提供了一套丰富的控件,如:Button,ImageView,TextView,EditText等众多控件,但是,有时候在项目开发过程中,还是需要开发者自定义一些需要 ...

    3. Android自定义View之圆环交替 等待效果

      学习了前面两篇的知识,对于本篇实现的效果,相信大家都不会感觉太困难,我要实现的效果是什么样呢?下面请先看效果图: 看上去是不很炫的样子,它的实现上也不是很复杂,重点在与onDraw()方法的绘制. 首 ...

    4. Android自定义View初步

      经过上一篇的介绍,大家对于自定义View一定有了一定的认识,接下来我们就以实现一个图片下显示文字的自定义View来练习一下.废话不多说,下面进入我们的正题,首先看一下我们的思路,1.我们需要通过在va ...

    5. Android之自定义View的实现

      对于学习Android开发的小童鞋对于自定义View一定不会陌生,相信大家对它是又爱又恨,爱它可以跟随我们的心意设计出漂亮的效果:恨它想要完全流畅掌握,需要一定的功夫.对于初学者来说确实很不容易,网上 ...

    6. [转]Android自定义控件三部曲系列完全解析(动画, 绘图, 自定义View)

      来源:http://blog.csdn.net/harvic880925/article/details/50995268 一.自定义控件三部曲之动画篇 1.<自定义控件三部曲之动画篇(一)—— ...

    7. 通过圆形载入View了解自定义View

      这是自定义View的第一篇文章,通过制作简单的自定义View来了解自定义View的流程. 自定义View是Android学习和开发中必不可少的一部分.通过自定义View我们可以制作丰富绚丽的控件,自定 ...

    8. 自定义view(二)

      1.View 的绘制 通过继承View 并重写它的onDraw()来完成绘制. onDraw()有一个参数,就是Canvas对象.使用这个Canvas就可以绘制图像了,Canvas canvas = ...

    9. salesforce 零基础学习(五十)自定义View或者List以及查看系统原来的View或者List

      salesforce给我们提供了标准的页面,比如标准的页面包括标准的列表和标准的详细页视图.有的时候我们想要自定义视图,比如做一个项目的时候不希望使用者直接通过ID查看到标准的详细页,而是跳转到指定处 ...

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

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

    随机推荐

    1. C语言程序设计第一作业

      C语言程序设计第一作业 实验总结 (一) 1.题目:输入圆的半径,求圆周长和面积 2.流程图: 3.测试数据及运行结果: 4.实验分析: 问题1: 出现了错误 原因:是在赋值那写反了 解决方法:应该是 ...

    2. cors解决ajax请求跨域问题

      Access-Control-Allow-Origin: * 适用tomcat部署的项目 在web.xml里添加以下内容 <filter> <filter-name>CorsF ...

    3. JAVA提高二:枚举

      JDK5.0中有一个非常有用的特性:枚举,这个特性以前在C语言中出现过,后来JDK出现后,开始觉得没有必要,但随着使用JAVA语言的人数增多,发现大家对枚举的需求非常大,于是又加入了此特性,下面我们来 ...

    4. 翻译连载 | 第 10 章:异步的函数式(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

      原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

    5. zoj 2022

      分析: 组合数学类型的题目. 正常的话可能会去分解1~N数里面有几个5和2,但是这样的复杂度为O(nlogn). 其实有更巧妙的办法,可以把问题分解成子问题. 可以发现N!末尾的0与1~N中有几个5的 ...

    6. java Web Servlet学习笔记-1 HttpServletQequest对象的学习

       HttpServletQequest对象的学习 HttpServletRequest HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求 ...

    7. Linux上mysql的安装与配置

      前言 在我们使用Linux的过程中,可能会使用到数据库.那么,数据库的安装与配置就是我们需要掌握的了~所以呢,这篇博客小编就来给大家唠唠数据库的安装与配置. 说到编译安装,小编脑海里浮现的第一个方法就 ...

    8. Xp下麦克风设备及音量检测

      从Vista开始,windows底层的音频架构发生了改变:原本是底层API的waveXXX.mixerXXX等都在Core Audio APIs的基础上进行了重构,上升为了高层API:底层API变为C ...

    9. swift实现与OC的混编

      swift与OC的混编 现在写swift,很多的类库还不是很全,很多的第三方还是只有OC版的,这个时候swift想用,通常都是采用的swift和OC混编的方式.这里给大家演示一下混编是如何做的. sw ...

    10. python3.6如何安装pymssql

      使用pip install pymssql安装时,总是会出现UnicodeDecodeError.于是下载了适用的pymssql.whl进行安装,详细可参考 https://docs.microsof ...