首页 技术 正文
技术 2022年11月14日
0 收藏 970 点赞 4,944 浏览 10390 个字

介绍

前段时间,我看到了一篇关于可滑动开关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)。
    上一篇: 移动端Push推送
    下一篇: 我的Java编码规范
    相关推荐
    python开发_常用的python模块及安装方法
    adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
    日期:2022-11-24 点赞:878 阅读:9,104
    Educational Codeforces Round 11 C. Hard Process 二分
    C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
    日期:2022-11-24 点赞:807 阅读:5,580
    下载Ubuntn 17.04 内核源代码
    zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
    日期:2022-11-24 点赞:569 阅读:6,428
    可用Active Desktop Calendar V7.86 注册码序列号
    可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
    日期:2022-11-24 点赞:733 阅读:6,200
    Android调用系统相机、自定义相机、处理大图片
    Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
    日期:2022-11-24 点赞:512 阅读:7,835
    Struts的使用
    一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
    日期:2022-11-24 点赞:671 阅读:4,918