result = Math.min(result, specSize);
}
}
return result;
}
3、【绘制文字text】
这里我是用自己的方式实现:当文字长度超过控件长度时,文字需要来回滚动。所以自定义控件因为你需要什么样的功能可以自己去实现(当然这个方法也是在onDraw里,为什么这么个顺序讲,目的希望我希望你能循序渐进的理解,如果你觉得onDraw方代码太杂,你可以用个方法独立出去,你可以跟作者一样用private void drawText(Canvas canvas) {}), //绘制文字的路径(文字过长时,文字来回滚动需要用到)
private Path textPath = new Path():
textRect.left = 0;
textRect.top = 0;
textRect.right = width;
textRect.bottom = height; //这里确定文字绘制区域,其实就是控件区域
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
//这里是获取文字绘制的y轴位置,可以理解上下居中
int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
//这里判断文字长度是否大于控件长度,当然我控件2边需要留文字的间距,所以不是大于width,这么说只是更好的理解
//这里是当文字内容大于控件长度,启动回滚效果。建议先看下面else里的正常情况
if ((buttonString.length() * textSize) > (width - height * 5 / 3)) {
textPath.reset();
//因为要留2遍间距,以heigh/3为间距
textPath.moveTo(height / 3, baseline);
textPath.lineTo(width - height / 3, baseline);
//这里的意思是文字从哪里开始写,可以是居中,这里是右边
textPaint.setTextAlign(Paint.Align.RIGHT);
//这里是以路径绘制文字,scrollSize可以理解为文字在x轴上的便宜量,同时,我的混动效果就是通过改变scrollSize
//刷新绘制来实现
canvas.drawTextOnPath(buttonString, textPath, scrollSize, 0, textPaint);
if (isShowLongText) {
//这里是绘制遮挡物,因为绘制路径没有间距这方法,所以绘制遮挡物类似于间距方式
canvas.drawRect(new Rect(width - height / 2 - textSize / 3, 0, width - height / 2, height),paintOval);
canvas.drawRect(new Rect(height / 2, 0, height / 2 + textSize / 3, height), paintOval);
//这里有个bug 有个小点-5 因画笔粗细产生
canvas.drawArc(new RectF(width - height, 0, width - 5, height), -90, 180, true, paintOval);
canvas.drawArc(new RectF(0, 0, height, height), 90, 180, true, paintOval);
}
if (animator_text_scroll == null) {
//这里是计算混到最右边和最左边的距离范围
animator_text_scroll = ValueAnimator.ofInt(buttonString.length() * textSize - width + height * 2 / 3,-textSize);
//这里是动画的时间,scrollSpeed可以理解为每个文字滚动控件外所需的时间,可以做成控件属性提供出去
animator_text_scroll.setDuration(buttonString.length() * scrollSpeed);
//设置动画的模式,这里是来回滚动
animator_text_scroll.setRepeatMode(ValueAnimator.REVERSE);
//设置插值器,让整个动画流畅
animator_text_scroll.setInterpolator(new LinearInterpolator());
//这里是滚动次数,-1无限滚动
animator_text_scroll.setRepeatCount(-1);
animator_text_scroll.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//改变文字路径x轴的偏移量
scrollSize = (int) animation.getAnimatedValue();
postInvalidate();
}
});
animator_text_scroll.start();
}
} else {
//这里是正常情况,isShowLongText,是我在启动控件动画的时候,是否启动 文字有渐变效果的标识,
//如果是长文字,启动渐变效果的话,如果控件变小,文字内容在当前控件外,会显得很难看,所以根据这个标识,关闭,这里你可以先忽略(同时因为根据路径绘制text不能有间距效果,这个标识还是判断是否在控件2遍绘制遮挡物,这是作者的解决方式,如果你有更好的方式可以在下方留言)
isShowLongText = false;
/**
-
简单的绘制文字,没有考虑文字长度超过控件长度
-
*/
//这里是居中显示
textPaint.setTextAlign(Paint.Align.CENTER);
//参数1:文字
//参数2,3:绘制文字的中心点
//参数4:画笔
canvas.drawText(buttonString, textRect.centerX(), baseline, textPaint);
}
4、【自定义控件属性】
<?xml version="1.0" encoding="utf-8"?>这里以,文案为例, textStr。比如你再布局种用到app:txtStr=“文案内容”。在自定义控件里获取如下:
public SmartLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//自定义控件的3参方法的attrs就是我们设置自定义属性的关键
//比如我们再attrs.xml里自定义了我们的属性,
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SmartLoadingView);
//这里是获取用户有没有设置整个属性
//这里是从用户那里获取有没有设置文案
String title = typedArray.getString(R.styleable.SmartLoadingView_textStr);
if (TextUtils.isEmpty(title)){
//如果获取来的属性是空,那么可以默认一个属性
//(作者忘记设置了!因为已经发布后期优化,老尴尬了)
buttonString =“默认文案”;
}else{
//如果有设置文案
buttonString = title;
}
}
5、【设置点击事件,启动动画】
为了点击事件的直观,也可以把处理防止重复点击事件封装在里面
//这是我自定义登录点击的接口
public interface LoginClickListener {
void click();
}
public void setLoginClickListener(final LoginClickListener loginClickListener) {
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (loginClickListener != null) {
//防止重复点击
if (!isAnimRuning) {
start();
loginClickListener.click();
}
}
}
});
}
6、【动画讲解】
6.1、第一个动画,矩形到正方形,以及矩形到圆角矩形(这里是2个动画,只是同时进行)
矩形到正方形(为了简化,我把源码一些其他属性去掉了,这样方便理解)
//其中 default_all_distance = (w - h) / 2;除以2是因为2遍都往中间缩短
private void set_rect_to_circle_animation() {
//这是一个属性动画,current_left 会在duration时间内,从0到default_all_distance匀速变化
//想添加多样化的话 还可以加入插值器。
animator_rect_to_square = ValueAnimator.ofInt(0, default_all_distance);
animator_rect_to_square.setDuration(duration);
animator_rect_to_square.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//这里的current_left跟onDraw相关,还记得吗
//onDraw里的控件区域
//控件左边区域 rectf.left = current_left;
//控件右边区域 rectf.right = width - current_left;
current_left = (int) animation.getAnimatedValue();
//刷新绘制
invalidate();
}
});
矩形到圆角矩形。就是从一个没有圆角的变成完全圆角的矩形,当然我展示的时候只有第三个图,最后一个按钮才明显了。
其他的我直接设置成了圆角按钮,因为我把圆角做成了一个属性。
还记得onDraw里的canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);circleAngle就是圆角的半径
可以想象一下如果全是圆角,那么circleAngle会是多少,当然是height/2;没错吧,所以
因为我把圆角做成了属性obtainCircleAngle是从xml文件获取的属性,如果不设置,则为0,就没有任何圆角效果
animator_rect_to_angle = ValueAnimator.ofInt(obtainCircleAngle, height / 2);
animator_rect_to_angle.setDuration(duration);
animator_rect_to_angle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//这里试想下如果是一个正方形,刚好是圆形的圆角,那就是一个圆
circleAngle = (int) animation.getAnimatedValue();
//刷新绘画
invalidate();
}
});
2个属性动画做好后,用 private AnimatorSet animatorSet = new AnimatorSet();把属性动画加进去,可以设置2个动画同时进行,还是先后顺序 这里是同时进行所用用with
animatorSet
.play(animator_rect_to_square).with(animator_rect_to_angle);
6.2、变成圆形后,有一个loading加载动画
这里就是画圆弧,只是不断改变,圆弧的起始点和终点,最终呈现loading状态,也是在onDraw里
//绘制加载进度
if (isLoading) {
//参数1:绘制圆弧区域
//参数2,3:绘制圆弧起始点和终点
canvas.drawArc(new RectF(width / 2 - height / 2 + height / 4, height / 4, width / 2 + height / 2 - height / 4, height / 2 + height / 2 - height / 4), startAngle, progAngle, false, okPaint);
//这里是我通过实践,实现最佳loading动画
//当然这里有很多方式,因为我自定义这个view想把所有东西都放在这个类里面,你也可以有你的方式
//如果有更好的方式,欢迎留言,告知我一下
startAngle += 6;
if (progAngle >= 270) {
progAngle -= 2;
isAdd = false;
} else if (progAngle <= 45) {
progAngle += 6;
isAdd = true;
} else {
if (isAdd) {
progAngle += 6;
} else {
progAngle -= 2;
}
}
//刷新绘制,这里不用担心有那么多刷新绘制,会不会影响性能
//
postInvalidate();
}
6.3、loading状态,到打勾动画
那么这里首先要把loading动画取消,那么直接改变isLoading=false;不会只它同时启动打勾动画;打勾动画的动画,这里比较麻烦,也是我在别人自定义动画里学习的,通过PathMeasure,实现路径动画
/** * 路径–用来获取对勾的路径 /private Path path = new Path();/* * 取路径的长度 */private PathMeasure pathMeasure;
//初始化打勾动画路径;private void initOk() { //对勾的路径 path.moveTo(default_all_distance + height / 8 * 3, height / 2); path.lineTo(default_all_distance + height / 2, height / 5 * 3); path.lineTo(default_all_distance + height / 3 * 2, height / 5 * 2); pathMeasure = new PathMeasure(path, true);}
/**
- 路径–用来获取对勾的路径
*/
private Path path = new Path();
/**
- 取路径的长度
*/
private PathMeasure pathMeasure;
//初始化打勾动画路径;
private void initOk() {
//对勾的路径
path.moveTo(default_all_distance + height / 8 * 3, height / 2);
path.lineTo(default_all_distance + height / 2, height / 5 * 3);
path.lineTo(default_all_distance + height / 3 * 2, height / 5 * 2);
pathMeasure = new PathMeasure(path, true);
}
//初始化打勾动画
private void set_draw_ok_animation() {
animator_draw_ok = ValueAnimator.ofFloat(1, 0);
animator_draw_ok.setDuration(duration);
animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
startDrawOk = true;
isLoading = false;
float value = (Float) animation.getAnimatedValue();
effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
okPaint.setPathEffect(effect);
invalidate();
}
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
nimation.getAnimatedValue();
effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
okPaint.setPathEffect(effect);
invalidate();
}
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter
[外链图片转存中…(img-2UeV2x4c-1715260304723)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!