android——自定义控件(不停变化的textview、开关switch、动画效果的打勾)

ops/2024/10/18 1:22:04/

一、从开始数字到结束数字,不断变化

import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;import androidx.appcompat.widget.AppCompatTextView;import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;/*** FileName: NumberAnimTextView* Date: 2020/12/14* Description: TextView动画,从开始到结束,数字不断变化* History:* <author> <time> <version> <desc>*/public class NumberAnimTextView extends AppCompatTextView {private String mNumStart = "0";  //private String mNumEnd; //private long mDuration = 1000;          // 动画持续时间 ms,默认1sprivate String mPrefixString = "";      // 前缀private String mPostfixString = "";     // 后缀public NumberAnimTextView(Context context) {super(context);}public NumberAnimTextView(Context context, AttributeSet attrs) {super(context, attrs);}public NumberAnimTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public void setNumberString(String number) {setNumberString("0", number);}public void setNumberString(String numberStart, String numberEnd) {mNumStart = numberStart;mNumEnd = numberEnd;start();
//        if (checkNumString(numberStart, numberEnd)) {
//            // 数字合法 开始数字动画
//            start();
//        } else {
//            // 数字不合法 直接调用 setText 设置最终值
//            setText(mPrefixString + numberEnd + mPostfixString);
//        }}public void setDuration(long mDuration) {this.mDuration = mDuration;}public void setPrefixString(String mPrefixString) {this.mPrefixString = mPrefixString;}public void setPostfixString(String mPostfixString) {this.mPostfixString = mPostfixString;}private boolean isInt; // 是否是整数/*** 校验数字的合法性** @param numberStart  开始的数字* @param numberEnd    结束的数字* @return 合法性*/private boolean checkNumString(String numberStart, String numberEnd) {try {new BigInteger(numberStart);new BigInteger(numberEnd);isInt = true;} catch (Exception e) {isInt = false;e.printStackTrace();}try {BigDecimal start = new BigDecimal(numberStart);BigDecimal end = new BigDecimal(numberEnd);return end.compareTo(start) >= 0;} catch (Exception e) {e.printStackTrace();return false;}}private void start() {ValueAnimator animator = ValueAnimator.ofObject(new BigDecimalEvaluator(), new BigDecimal(mNumStart), new BigDecimal(mNumEnd));animator.setDuration(mDuration);animator.setInterpolator(new AccelerateDecelerateInterpolator());animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {BigDecimal value = (BigDecimal) valueAnimator.getAnimatedValue();setText(mPrefixString + format(value) + mPostfixString);}});animator.start();}/*** 格式化 BigDecimal ,小数部分时保留两位小数并四舍五入** @param bd  BigDecimal* @return 格式化后的 String*/private String format(BigDecimal bd) {String pattern;if (isInt) {pattern = "#,###";} else {pattern = "#,##0.00";}DecimalFormat df = new DecimalFormat(pattern);return df.format(bd);}class BigDecimalEvaluator implements TypeEvaluator {@Overridepublic Object evaluate(float fraction, Object startValue, Object endValue) {BigDecimal start = (BigDecimal) startValue;BigDecimal end = (BigDecimal) endValue;BigDecimal result = end.subtract(start);return result.multiply(new BigDecimal("" + fraction)).add(start);}}
}

设置开始数字和结束数字即可:setNumberString("12345", "1234567")

二、自定义开关switch

import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
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.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;import androidx.annotation.Nullable;import com.fslihua.my_application_1.R;/*** 自定义开关Switch*/
public class CustomSwitch extends View implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener{private final String TAG = CustomSwitch.class.getSimpleName();//默认的宽高比例private static final float DEFAULT_WIDTH_HEIGHT_PERCENT = 0.45f;//动画最大的比例private static final float ANIMATION_MAX_FRACTION = 1;private int mWidth,mHeight;//画跑道型背景private Paint mBackgroundPain;//画背景上的字private Paint mDisaboleTextPaint;//开启private Paint mEnableTextPaint;//关闭//画白色圆点private Paint mSlidePaint;//是否正在动画private boolean isAnimation;private ValueAnimator mValueAnimator;private float mAnimationFraction;private String openText;private String closeText;private int mOpenColor = Color.GREEN;private int mCloseColor = Color.GRAY;private int mCurrentColor = Color.GRAY;//监听private OnCheckedChangeListener mCheckedChangeListener;private boolean isChecked;public CustomSwitch(Context context) {super(context);init();}public CustomSwitch(Context context, @Nullable AttributeSet attrs) {super(context, attrs);TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomSwitch);openText = typedArray.getString(R.styleable.CustomSwitch_openText);closeText = typedArray.getString(R.styleable.CustomSwitch_closeText);mOpenColor = typedArray.getColor(R.styleable.CustomSwitch_openColor, Color.GREEN);mCloseColor = typedArray.getColor(R.styleable.CustomSwitch_closeColor, Color.GRAY);mCurrentColor = mCloseColor;
//        mWidth = typedArray.getInteger(R.styleable.CustomSwitch_customWidth,1);
//        mHeight = typedArray.getInteger(R.styleable.CustomSwitch_customHeight,1);typedArray.recycle();init();}public CustomSwitch(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomSwitch);openText = typedArray.getString(R.styleable.CustomSwitch_openText);closeText = typedArray.getString(R.styleable.CustomSwitch_closeText);mOpenColor = typedArray.getColor(R.styleable.CustomSwitch_openColor, Color.GREEN);mCloseColor = typedArray.getColor(R.styleable.CustomSwitch_closeColor, Color.GRAY);mCurrentColor = mCloseColor;
//        mWidth = typedArray.getInteger(R.styleable.CustomSwitch_customWidth,1);
//        mHeight = typedArray.getInteger(R.styleable.CustomSwitch_customHeight,1);typedArray.recycle();init();}private void init(){Log.e(TAG,"init()被调用");mBackgroundPain = new Paint();mBackgroundPain.setAntiAlias(true);mBackgroundPain.setDither(true);mBackgroundPain.setColor(Color.GRAY);
//        开启的文字样式mDisaboleTextPaint = new Paint();mDisaboleTextPaint.setAntiAlias(true);mDisaboleTextPaint.setDither(true);mDisaboleTextPaint.setStyle(Paint.Style.STROKE);mDisaboleTextPaint.setColor(Color.WHITE);mDisaboleTextPaint.setTextAlign(Paint.Align.CENTER);
//        关闭的文字样式mEnableTextPaint = new Paint();mEnableTextPaint.setAntiAlias(true);mEnableTextPaint.setDither(true);mEnableTextPaint.setStyle(Paint.Style.STROKE);mEnableTextPaint.setColor(Color.parseColor("#7A88A0"));mEnableTextPaint.setTextAlign(Paint.Align.CENTER);mSlidePaint = new Paint();mSlidePaint.setColor(Color.WHITE);mSlidePaint.setAntiAlias(true);mSlidePaint.setDither(true);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width =  MeasureSpec.getSize(widthMeasureSpec);int height = (int) (width*DEFAULT_WIDTH_HEIGHT_PERCENT);setMeasuredDimension(width,height);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mWidth = w;mHeight = h;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);drawBackground(canvas);drawSlide(canvas);}private void drawSlide(Canvas canvas){float distance = mWidth - mHeight;
//        Log.e(TAG,"distance = " + distance);
//        Log.e(TAG,"mAnimationFraction = " + mAnimationFraction);
//        canvas.drawCircle(mHeight/2+distance*mAnimationFraction,mHeight/2,mHeight/3,mSlidePaint);canvas.drawCircle(mHeight/2+distance*mAnimationFraction,mHeight/2, mHeight/2.5f,mSlidePaint);
//        canvas.drawCircle(mHeight/2+distance*mAnimationFraction, (float) (mHeight/2.2), (float) (mHeight/2.2),mSlidePaint);}private void drawBackground(Canvas canvas){Path path = new Path();RectF rectF = new RectF(0,0,mHeight,mHeight);path.arcTo(rectF,90,180);rectF.left = mWidth-mHeight;rectF.right = mWidth;path.arcTo(rectF,270,180);path.close();mBackgroundPain.setColor(mCurrentColor);canvas.drawPath(path,mBackgroundPain);//        mDisaboleTextPaint.setTextSize(mHeight/2);mDisaboleTextPaint.setTextSize(mHeight/2.2f);
//        mEnableTextPaint.setTextSize(mHeight/2);mEnableTextPaint.setTextSize(mHeight/2.2f);Paint.FontMetrics fontMetrics = mDisaboleTextPaint.getFontMetrics();float top = fontMetrics.top;float bottom = fontMetrics.bottom;
//        基线位置int baseLine = (int) (mHeight/2 + (bottom-top)*0.3);if (!TextUtils.isEmpty(openText)){//启用mDisaboleTextPaint.setAlpha((int) (255*mAnimationFraction));canvas.drawText(openText,mWidth*0.3f,baseLine,mDisaboleTextPaint);}if (!TextUtils.isEmpty(closeText)){//启用mEnableTextPaint.setAlpha((int) (255*(1-mAnimationFraction)));canvas.drawText(closeText,mWidth*0.7f,baseLine,mEnableTextPaint); //第二个值改变x轴的位置}}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:return true;case MotionEvent.ACTION_CANCEL:break;case MotionEvent.ACTION_UP:if (isAnimation){return true;}if (isChecked){startCloseAnimation();isChecked = false;if (mCheckedChangeListener!=null){mCheckedChangeListener.onCheckedChanged(false);}}else {startOpeAnimation();isChecked = true;if (mCheckedChangeListener!=null){mCheckedChangeListener.onCheckedChanged(true);}}return true;}return super.onTouchEvent(event);}private void startOpeAnimation(){mValueAnimator = ValueAnimator.ofFloat(0.0f, ANIMATION_MAX_FRACTION);mValueAnimator.setDuration(500);mValueAnimator.addUpdateListener(this);mValueAnimator.addListener(this);mValueAnimator.start();startColorAnimation();}private void startCloseAnimation(){mValueAnimator = ValueAnimator.ofFloat(ANIMATION_MAX_FRACTION, 0.0f);mValueAnimator.setDuration(500);mValueAnimator.addUpdateListener(this);mValueAnimator.addListener(this);mValueAnimator.start();startColorAnimation();}private void startColorAnimation(){int colorFrom = isChecked?mOpenColor:mCloseColor; //mIsOpen为true则表示要启动关闭的动画int colorTo = isChecked? mCloseColor:mOpenColor;ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);colorAnimation.setDuration(500); // millisecondscolorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animator) {mCurrentColor = (int)animator.getAnimatedValue();}});colorAnimation.start();}//设置监听public void setOnCheckedChangeListener(OnCheckedChangeListener listener){mCheckedChangeListener = listener;}public boolean isChecked() {return isChecked;}public void setChecked(boolean checked) {isChecked = checked;if (isChecked){mCurrentColor = mOpenColor;mAnimationFraction = 1.0f;}else {mCurrentColor = mCloseColor;mAnimationFraction = 0.0f;}invalidate();}@Overridepublic void onAnimationStart(Animator animator) {isAnimation = true;}@Overridepublic void onAnimationEnd(Animator animator) {isAnimation = false;}@Overridepublic void onAnimationCancel(Animator animator) {isAnimation = false;}@Overridepublic void onAnimationRepeat(Animator animator) {isAnimation = true;}@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {mAnimationFraction = (float) valueAnimator.getAnimatedValue();invalidate();}public interface OnCheckedChangeListener{void onCheckedChanged(boolean isChecked);}
}

attrs.xml代码

<?xml version="1.0" encoding="utf-8"?>
<resources><!--    开关按钮CustomSwitch的样式定义  --><declare-styleable name="CustomSwitch"><attr name="closeText" format="string" /><attr name="openText" format="string" /><attr name="closeColor" format="color" /><attr name="openColor" format="color" /><attr name="customWidth" format="integer" /><attr name="customHeight" format="integer" /></declare-styleable>
</resources>

activity的xml代码:

<com.fslihua.my_application_1.cumstom_ui.CustomSwitchandroid:id="@+id/customSwitch"android:layout_marginTop="20dp"android:layout_marginStart="20dp"android:layout_width="50dp"android:layout_height="25dp"app:closeColor="#6A6A6A"app:closeText="关"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />

三、包含动画效果的打勾

import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PathMeasure
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.animation.DecelerateInterpolator
import com.fslihua.my_application_1.R/*** 创建时间:2023/11/9* 类说明:包含动画效果的打勾*/
class AnimatedCheckView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)private val checkPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)private var circlePath = Path()private var checkPath = Path()private var circleColor = 0private var checkColor = 0private var pathLength = 0f// 默认值private var circleStrokeWidth = 1fprivate var checkStrokeWidth = 1fprivate val animator = ValueAnimator().apply {interpolator = DecelerateInterpolator()addUpdateListener {invalidate()}}private var animateTime: Float = 2f // 2秒init {context.theme.obtainStyledAttributes(attrs, R.styleable.AnimatedCheckView, 0, 0).apply {try {circleColor = getColor(R.styleable.AnimatedCheckView_circleColor, Color.BLACK)checkColor = getColor(R.styleable.AnimatedCheckView_checkColor, Color.BLACK)circleStrokeWidth = getDimension(R.styleable.AnimatedCheckView_circleStrokeWidth, dp2px(context, circleStrokeWidth).toFloat())checkStrokeWidth = getDimension(R.styleable.AnimatedCheckView_checkStrokeWidth, dp2px(context, checkStrokeWidth).toFloat())animateTime = getFloat(R.styleable.AnimatedCheckView_animateTime, animateTime)Log.i("AnimatedCheckView", "circleStrokeWidth: $circleStrokeWidth, checkStrokeWidth: $checkStrokeWidth" )} finally {recycle()}}circlePaint.apply {style = Paint.Style.STROKEstrokeWidth = circleStrokeWidthcolor = circleColor}checkPaint.apply {style = Paint.Style.STROKEstrokeWidth = checkStrokeWidthcolor = checkColor}}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)val centerX = w / 2fval centerY = h / 2fval radius = w.coerceAtMost(h) / 2f - circleStrokeWidth / 2initPaths(centerX, centerY, radius)animator.apply {setFloatValues(0f, pathLength)duration = (animateTime * 1000).toLong()start()}Log.i("AnimatedCheckView", "w: $w, h: $h, centerX: $centerX, centerY: $centerY, radius: $radius" )}private fun initPaths(centerX: Float, centerY: Float, radius: Float) {circlePath.addCircle(centerX, centerY, radius, Path.Direction.CW)checkPath.apply {moveTo(centerX - radius / 2 - radius / 15, centerY + radius / 15)lineTo(centerX - radius / 2, centerY)lineTo(centerX - radius / 8, centerY + radius / 3)lineTo(centerX + radius / 2, centerY - radius / 3)}val measure = PathMeasure(circlePath, false)val circleLength = measure.lengthmeasure.setPath(checkPath, false)val checkLength = measure.lengthpathLength = circleLength + checkLength}@SuppressLint("DrawAllocation")override fun onDraw(canvas: Canvas) {super.onDraw(canvas)val measure = PathMeasure(circlePath, false)val dst = Path()val animatedValue = animator.animatedValue as Floatval circleLength = measure.lengthif (animatedValue < circleLength) {measure.getSegment(0f, animatedValue, dst, true)canvas.drawPath(dst, circlePaint)} else {measure.getSegment(0f, circleLength, dst, true)canvas.drawPath(dst, circlePaint)measure.nextContour()measure.setPath(checkPath, false)dst.rewind()measure.getSegment(0f, animatedValue - circleLength, dst, true)canvas.drawPath(dst, checkPaint)}}fun dp2px(context: Context, dpVal: Float): Int {return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpVal, context.resources.displayMetrics).toInt()}
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="AnimatedCheckView"><attr name="circleColor" format="color" /><attr name="checkColor" format="color" /><attr name="circleStrokeWidth" format="dimension" /><attr name="checkStrokeWidth" format="dimension" /><attr name="animateTime" format="float" /></declare-styleable>
</resources>

activity中的代码:

<com.fslihua.my_application_1.cumstom_ui.AnimatedCheckViewandroid:id="@+id/ac_success"android:layout_width="55dp"android:layout_height="55dp"android:layout_marginTop="64dp"app:animateTime="0.7"app:checkColor="#3ACFA5"app:checkStrokeWidth="2dp"app:circleColor="#3ACFA5"app:circleStrokeWidth="2dp"app:layout_constraintStart_toEndOf="@+id/customSwitch"app:layout_constraintTop_toTopOf="parent" />


http://www.ppmy.cn/ops/126346.html

相关文章

Linux之如何找回 root 密码?

1、启动系统&#xff0c;进入开界面&#xff0c;在界面中按“e"进入编辑界面 2、进入编辑界面&#xff0c;使用键盘上的上下键把光标往下移动&#xff0c;找到以”Linux16“开通内容所在的行数&#xff0c;在行的最后面输入&#xff1a;init/bin/sh 3、输入完成后&…

【Spring AI】Java实现类似langchain的第三方函数调用_原理与详细示例

Spring AI 介绍 &#xff1a;简化Java AI开发的统一接口解决方案 在过去&#xff0c;使用Java开发AI应用时面临的主要困境是没有统一且标准的封装库&#xff0c;导致开发者需要针对不同的AI服务提供商分别学习和对接各自的API&#xff0c;这增加了开发难度与迁移成本。而Sprin…

408算法题leetcode--第36天

96. 不同的二叉搜索树 题目地址&#xff1a;96. 不同的二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 题解思路&#xff1a;dp 时间复杂度&#xff1a;O(n^2) 空间复杂度&#xff1a;O(n) 代码: class Solution { public:int numTrees(int n) {// dp[]: i个节点的二…

JavaWeb Servlet--09深入:注册系统03--删除用户业务

删除用户业务 在显示用户的界面游两个超链接&#xff1a;修改和删除&#xff0c;这里将对删除进行业务实现&#xff1a; 思想&#xff1a;在页面展示信息&#xff0c;点击删除的超链接后&#xff0c;获取id&#xff0c;在controller层进行调用service的业务逻辑处理&#xff…

【Linux系统编程】环境基础开发工具使用

目录 1、Linux软件包管理器yum 1.1 什么是软件包 1.2 安装软件 1.3 查看软件包 1.4 卸载软件 2、Linux编辑器-vim 2.1 vim的概念 2.2 vim的基本操作 2.3 vim的配置 3、Linux编译器-gcc/g 3.1 gcc编译的过程​编辑​编辑​编辑 3.2 详解链接 动态链接 静态链接 4…

oracle + mybatis 批量新增

oracle mybatis 批量新增 mybatis 批量最大1000条&#xff0c;数据多的话&#xff0c;分多次执行批量操作&#xff1a; <dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.4&l…

手机通过carlink投屏到车机,播放QQ音乐卡顿问题分析

1. 背景 相信开车的朋友&#xff0c;都会使用carlink或者carplay功能&#xff0c;实现手机投屏到车机&#xff0c;用来导航或者播放音乐等&#xff0c;可能吐槽过这个投屏功能为什么会那么卡&#xff0c;那么卡。。。 Carlife 和 CarPlay 都是车载系统&#xff0c;旨在将智能…

C语言二维数组的遍历 Java的强制转换和隐形转换

1. #define M 3 #define N 4 int main(void) { int arr[M][N] { {1,4,7,10},{2,5,8,11},{3,6,9,12} }; int i 0; int j 0; for (i 0;i < M;i) { for (j 0;j < N;j)//两个for语句颠倒&#xff0c;就是按列遍历 { pr…