自定义View -简单的 SwitchView

简介: 前言实现一个简单的滑动开发,效果图如下:switchView完整版本分析平分整个View为两份平分VIew测量字体的高度和宽度,确定左右View的文字的位置并进行绘制确定字体的位置和绘制绘制...

前言

实现一个简单的滑动开发,效果图如下:

img_8bd352f363505b3d3f857d6ce377993d.gif
switchView完整版本

分析

  1. 平分整个View为两份
img_31d27c125b210bc13851988ab7c27ce6.png
平分VIew
  1. 测量字体的高度和宽度,确定左右View的文字的位置并进行绘制
img_816feea56bd9523aa3b2d6e6cf508475.png
确定字体的位置和绘制
  1. 绘制背景颜色,如果有圆角,绘制==圆角 #f44336==

  2. 在文字下层绘制一个==背景View(矩形) #ff5722==,有圆角的情况下会绘制圆角

img_dfbf5c693b1e5b8758fda5ec24efd827.png
绘制背景View
  1. 设置点击事件,在点击事件中开启一个 ==ValueAnimator.ofFloa(1) #f44336== 动画, 在==onDraw() #f44336==不断的通过动画的执行==百分比 #f44336== 计算背景View的X轴坐标进行绘制。

6 动画完成保存状态和设置文字的颜色。

编码

确定属性

首先确定需要哪一些属性,然后在慢慢的对属性进行实现。

<declare-styleable name="SwitchView">
    <!--关闭文字-->
    <attr name="off_text" format="string" />
    <!--打开文字-->
    <attr name="on_text" format="string" />
    <!--打开文字颜色-->
    <attr name="on_text_color" format="color" />
    <!--关闭文字颜色-->
    <attr name="off_text_color" format="color" />
    <!--无状态下的背景颜色-->
    <attr name="background_color" format="color" />
    <!--打开的背景颜色-->
    <attr name="on_background_color" format="color" />
    <!--关闭的背景颜色-->
    <attr name="off_background_color" format="color" />
    <!--字体大小-->
    <attr name="text_size" format="dimension" />
    <!--圆角-->
    <attr name="radius" format="dimension" />
</declare-styleable>

创建SwitchView

==SwitchView #f44336== 使用的完全接手 ==onDraw #f44336== ,自行进行相关绘制的自定义方式,所以我们需要继承至 ==View #f44336==

public class SwitchView extends View{
    public SwitchView(Context context) {
        this(context, null);
    }

    public SwitchView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs);
    }

    private void initAttr(Context context, AttributeSet attrs) {
        
    }
}

初始化相关属性

/**
 * 初始化属性
 *
 * @param context 上下午
 * @param attrs 属性
 */
private void initAttr(Context context, AttributeSet attrs) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwitchView);
    //关闭文字
    offText = typedArray.getString(R.styleable.SwitchView_off_text);
    offText = TextUtils.isEmpty(offText) ? "关闭" : offText;
    //打开文字
    onText = typedArray.getString(R.styleable.SwitchView_on_text);
    onText = TextUtils.isEmpty(onText) ? "打开" : onText;
    //关闭文字颜色
    offTextColor = typedArray.getColor(R.styleable.SwitchView_off_text_color, offTextColor);
    //打开文字颜色
    onTextColor = typedArray.getColor(R.styleable.SwitchView_on_text_color, onTextColor);
    //背景颜色
    mBackgroundColor = typedArray.getColor(R.styleable.SwitchView_background_color, mBackgroundColor);
    //打开背景颜色
    mOnBackgroundColor = typedArray.getColor(R.styleable.SwitchView_on_background_color, mOnBackgroundColor);
    //关闭背景颜色
    mOffBackgroundColor = typedArray.getColor(R.styleable.SwitchView_off_background_color, mOnBackgroundColor);
    //文字大小
    textSize = typedArray.getDimension(R.styleable.SwitchView_text_size, 16);
    //圆角
    mRadius = typedArray.getDimension(R.styleable.SwitchView_radius, mRadius);
    //前面那个按钮的长度
    mFrontGroundWidth = typedArray.getDimension(R.styleable.SwitchView_front_ground_width, 0);

    typedArray.recycle();
    //初始化画笔
    mPaint = new Paint();
    mPaint.setStrokeWidth(5);
    mPaint.setAntiAlias(true);
}

测量和计算

测量整个View的宽高,确定左右两部分的长度和文字的位置

protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    //View的宽度
    mWidth = w;
    //View的高度
    mHeight = h;
    //高度的中间
    mCenterHeight = h / 2;
    //宽度的中间
    mCenterWidth = w / 2;
    //创建背景矩形
    mBackgroundRectf = new RectF(0, 0, mWidth, mHeight);
    //打开的矩形
    mOnRectf = new RectF(0, 0, mCenterWidth, mHeight);
    //文字的中间高度
    Rect mRect = new Rect();
    mPaint.setTextSize(textSize);
    // 测量打开文字
    mPaint.getTextBounds(onText, 0, onText.length(), mRect);
    onTextCenterHeight = mRect.height() * 0.4f;
    //测量关闭文字
    mPaint.getTextBounds(offText, 0, offText.length(), mRect);
    offTextCenterHeight = mRect.height() * 0.4f;
}

绘制文字和背景

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 设置颜色
    mPaint.setColor(mBackgroundColor);
    ////绘制背景矩形
    canvas.drawRoundRect(mOnRectf, mRadius, mRadius, mPaint);

    //绘制打开文字
    mPaint.setColor(onTextColor);
    mPaint.setTextSize(textSize);
    canvas.drawText(onText, mCenterWidth / 2 - mPaint.measureText(onText) / 2, mCenterHeight + onTextCenterHeight, mPaint);


    //绘制关闭文字
    mPaint.setColor(offTextColor);
    mPaint.setTextSize(textSize);
    canvas.drawText(offText, (mCenterWidth + mCenterWidth / 2) - mPaint.measureText(offText) / 2, mCenterHeight + offTextCenterHeight, mPaint);

}

查看效果:

img_fcf0b73b6fedf34cc3cb4752725e89fc.png
背景效果

绘制打开背景

绘制开关颜色

mOnRectf = new RectF(0 , 0, width , mHeight);
mPaint.setColor(mOnBackgroundColor);
canvas.drawRoundRect(mOnRectf, mRadius, mRadius, mPaint);

img_41709999f3fe91687dfa982d3cea779e.png
打开预览

完善细节

基本上到这一步骤就已经是差不多了,现在需要做的是:

  • 响应点击事件,启动动画
  • 根据动画的执行值更改RectF的left的值,如果处于打开,那么left增加,关闭则left减少
  • 定义一个接口,动画完成回调结果。

点击事件和动画

 @Override
public void onClick(View v) {
    startAnim();
}

private void startAnim() {
    if (valueAnimator == null || !valueAnimator.isRunning()) {
        //发散一个宽度的值
        valueAnimator = ValueAnimator.ofFloat(1).setDuration(300);
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                aminValueHundred = (float) animation.getAnimatedValue();
                invalidate();
            }

        });
        isExchangeColor = false;
        valueAnimator.start();
    }
}

onDraw代码

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 更改颜色
    mPaint.setColor(mBackgroundColor);
    // 绘制背景矩形
    canvas.drawRoundRect(mBackgroundRectf, mRadius, mRadius, mPaint);


    //当前百分比的宽度
    int valueWidth = (int) (mCenterWidth * aminValueHundred);
    if (isOn) {
        //打开
        mOnRectf = new RectF(0 + valueWidth, 0, mCenterWidth + valueWidth, mHeight);
        mPaint.setColor(mOnBackgroundColor);
        if (aminValueHundred >= 0.5 && !isExchangeColor) {
            ////置换两种颜色
            mTempTextColor = offTextColor;
            offTextColor = onTextColor;
            onTextColor = mTempTextColor;
            isExchangeColor = true;
        }
        if (aminValueHundred >= 0.5) {
            mPaint.setColor(mOffBackgroundColor);
        }
    } else {
        //关闭
        mOnRectf = new RectF(mCenterWidth - valueWidth, 0, mWidth - valueWidth, mHeight);
        mPaint.setColor(mOffBackgroundColor);
        if (aminValueHundred >= 0.5 && !isExchangeColor) {
            //置换两种颜色
            mTempTextColor = onTextColor;
            onTextColor = offTextColor;
            offTextColor = mTempTextColor;
            isExchangeColor = true;
        }
        if (aminValueHundred >= 0.5) {
            mPaint.setColor(mOnBackgroundColor);
        }
    }
    if (!isOn && aminValueHundred == 1 && valueAnimator == null) {
        mOnRectf = new RectF(valueWidth, 0, mWidth, mHeight);
        mPaint.setColor(mOffBackgroundColor);
    }

    canvas.drawRoundRect(mOnRectf, mRadius, mRadius, mPaint);

    //绘制打开文字
    mPaint.setColor(onTextColor);
    mPaint.setTextSize(textSize);
    canvas.drawText(onText, mCenterWidth / 2 - mPaint.measureText(onText) / 2, mCenterHeight + onTextCenterHeight, mPaint);


    //绘制关闭文字
    mPaint.setColor(offTextColor);
    mPaint.setTextSize(textSize);
    canvas.drawText(offText, (mCenterWidth + mCenterWidth / 2) - mPaint.measureText(offText) / 2, mCenterHeight + offTextCenterHeight, mPaint);
    // 动画结束
    if (aminValueHundred == 1 && valueAnimator != null) {
        valueAnimator = null;
        isOn = !isOn;
        if (onSwitchListener != null) {
            onSwitchListener.onSwitchListener(isOn, isOn ? onText : offText);
        }

    }
}

查看效果:


img_e40fa08c80d4f3916980e1916d3f73ff.gif
完成预览

最后

当前只是实现了一个简单的切换,更多背景的颜色切换并没有完成,还有一些细节,现在还是比较生硬,希望下一步能实现更细腻的动画,就像下面这个这样:

img_dfff76ddf2cad12b39246cf25289c9d2.png
switch button

未完待续、敬请期待!
我的博客地址
源码地址

img_1ee92a858822d3b1d90a45e40e7b1042.jpe
FullScreenDeveloper
目录
相关文章
|
编译器 数据处理 C#
C#中的异步流:使用IAsyncEnumerable<T>和await foreach实现异步数据迭代
【1月更文挑战第10天】本文介绍了C#中异步流的概念,并通过使用IAsyncEnumerable<T>接口和await foreach语句,详细阐述了如何异步地迭代数据流。异步流为处理大量数据或需要流式处理数据的场景提供了一种高效且非阻塞性的方法,使得开发者能够更优雅地处理并发和数据流问题。
|
机器学习/深度学习 自然语言处理 搜索推荐
如何避免LLM的“幻觉”(Hallucination)
生成式大语言模型(LLM)可以针对各种用户的 prompt 生成高度流畅的回复。然而,大模型倾向于产生幻觉或做出非事实陈述,这可能会损害用户的信任。
485 1
|
Android开发 开发者
Android Split APK介绍
【2月更文挑战第5天】
|
前端开发 Java 开发者
LayUI之动态树
LayUI之动态树
349 0
|
机器学习/深度学习 人工智能 数据挖掘
数据上新 | AI Earth上线长时序土地覆盖数据集(来自武汉大学黄昕教授团队)
数据上新 | AI Earth上线长时序土地覆盖数据集(来自武汉大学黄昕教授团队)
数据上新 | AI Earth上线长时序土地覆盖数据集(来自武汉大学黄昕教授团队)
|
安全 NoSQL Linux
常见的挖矿木马
常见的挖矿木马
738 1
|
存储 测试技术 数据安全/隐私保护
自动化测试小技巧之Airtest-Selenium和Excel的无缝协作
【8月更文挑战第26天】在自动化测试中,Airtest-Selenium 与 Excel 的无缝协作能显著提升测试效率与可维护性。通过将 Excel 作为数据源,可轻松存储和读取测试用例数据;测试结果可自动记录在 Excel 中,便于跟踪与分析;利用 Excel 管理测试用例,简化了用例的增删改查;此外,还能自动截图并记录日志,方便问题定位。这种方式不仅提高了自动化测试的灵活性,还使得测试过程更加透明与高效。
502 1
|
机器学习/深度学习 人工智能 自然语言处理
【AI 生成式】如何利用生成式人工智能进行机器学习的数据增强?
【5月更文挑战第4天】【AI 生成式】如何利用生成式人工智能进行机器学习的数据增强?
|
存储 监控 关系型数据库
如何优化InnoDB的整体性能?
【5月更文挑战第14天】如何优化InnoDB的整体性能?
447 2
|
机器学习/深度学习 算法 API
【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)
【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)
524 0