Android修炼系列(十三),分享几个有趣的自定义view小栗子

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

public static PointF calcArcEndPointXY(float cirX, float cirY, float radius,
float cirAngle, float orginAngle) {
cirAngle = (orginAngle + cirAngle) % 360;
return calcArcEndPointXY(cirX, cirY, radius, cirAngle);
}

/*

  • @param cirAngle 当前弧角度
    */
    public static PointF calcArcEndPointXY(float cirX, float cirY,
    float radius, float cirAngle) {
    float posX = 0.0f;
    float posY = 0.0f;
    // 将角度转换为弧度
    float arcAngle = (float) (Math.PI * cirAngle / 180.0);
    if (cirAngle < 90) {
    posX = cirX + (float) (Math.cos(arcAngle)) * radius;
    posY = cirY + (float) (Math.sin(arcAngle)) * radius;
    } else if (cirAngle == 90) {
    posX = cirX;
    posY = cirY + radius;
    } else if (cirAngle > 90 && cirAngle < 180) {
    arcAngle = (float) (Math.PI * (180 - cirAngle) / 180.0);
    posX = cirX - (float) (Math.cos(arcAngle)) * radius;
    posY = cirY + (float) (Math.sin(arcAngle)) * radius;
    } else if (cirAngle == 180) {
    posX = cirX - radius;
    posY = cirY;
    } else if (cirAngle > 180 && cirAngle < 270) {
    arcAngle = (float) (Math.PI * (cirAngle - 180) / 180.0);
    posX = cirX - (float) (Math.cos(arcAngle)) * radius;
    posY = cirY - (float) (Math.sin(arcAngle)) * radius;
    } else if (cirAngle == 270) {
    posX = cirX;
    posY = cirY - radius;
    } else {
    arcAngle = (float) (Math.PI * (360 - cirAngle) / 180.0);
    posX = cirX + (float) (Math.cos(arcAngle)) * radius;
    posY = cirY - (float) (Math.sin(arcAngle)) * radius;
    }
    return new PointF(posX, posY);
    }

颜色的渐变效果实现,就是获取每个刻度所对应的颜色段内等比例的16进制颜色值,代码如下:

/**

  • 通过刻度获取当前渐变颜色值
  • @param p 当前刻度
  • @param specialScaleCorlors 每个范围的颜色值
  • @return 当前需要的颜色值
    */
    public static int evaluateColor(int p, int[] specialScaleCorlors) {
    // 定义的颜色区间
    int startInt = 0xFFbebebe;
    int endInt = 0xFFbebebe;
    float fraction = 0.5f;

if (p != 0 && p != 100) {
startInt = specialScaleCorlors[p / 20];
endInt = specialScaleCorlors[p / 20 + 1];
fraction = (p - (p / 20) * 20) / 20f;
}
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;

int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;

return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
| (int) ((startR + (int) (fraction * (endR - startR))) << 16)
| (int) ((startG + (int) (fraction * (endG - startG))) << 8)
| (int) ((startB + (int) (fraction * (endB - startB))));
}

其余的细节和方法就不贴了,都是比较常规的Paint方法。

栗子γ

这个效果和上面的很类似,不同的是这个控件可以通过拖动来选择刻度,具体见下:

2021-04-25 at 22.34.12.gif

在绘制“拖动按钮”Bitmap的时候,难点是确定bitmap的坐标,即根据圆心坐标,半径,扇形角度来求扇形终射线与圆弧交叉点的x, y坐标,上面是不是已经说啦,这样我们就能算出bitmap的左上角坐标了。

拖动效果是在我们允许的区域内,当手指按下,手指滑动,手指弹起时,不断绘制对应的进度p,给人一种圆环被拖着动画的错觉,其实这只是不断重绘的结果。这里需要我们通过onTouchEvent方法来监听手势及获取当前坐标。难点在于这是一个弧形轨迹,我们怎么通过当前坐标来获取角度,再根据角度获取相对应的进度。代码示例如下:

@Override
public synchronized boolean onTouchEvent(MotionEvent event) {

int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();

switch (action) {
case MotionEvent.ACTION_DOWN:
// isOnRing 注释见下
if (isOnRing(x, y) && y <= radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace) {
updateProgress(x, y);
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if (y <= radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace) {
updateProgress(x, y);
}
return true;
case MotionEvent.ACTION_UP:
invalidate();
break;
}

return super.onTouchEvent(event);
}

这是根据当前点的位置求角度,再转换成当前进度的方法:

private void updateProgress(int eventX, int eventY) {

double angle = Math.atan2(eventY - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace)
, eventX - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace)) / Math.PI;
angle = ((2 + angle) % 2 + (-beginLocation / 180f)) % 2;

if ((int) Math.round(angle * 100) >= 0) {
progress = (int) Math.round(angle * 100);
realShowProgress = getShowProgress(progress);
}

invalidate();
}

需要注意的是,当我们拖动“拖动按钮”时,我们需要定一个特定的接收事件的区域范围,只有当用户按在了规定的可滑动区域内,才能让用户拖动进度条,并不是在任意位置都能拖动小图标改变进度的,这是判断当前触摸屏幕的位置是否处于可滑动区域内的方法:

private boolean isOnRing(float eventX, float eventY) {

boolean result = false;
double distance = Math.sqrt(Math.pow(eventX - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace), 2)

  • Math.pow(eventY - (radius+getPaddingLeft() + specialScaleLineLength + scaleToRingSpace), 2));

if (distance < (2 * radius+getPaddingLeft() + getPaddingRight() + 2 * (specialScaleLineLength + scaleToRingSpace))
&& distance > radius - slideAbleLocation) {
result = true;
}

return result;
}

其余的细节和方法就不贴了,都是比较常规的Paint方法。

栗子δ

这是一个波纹扩散、圆球旋转缩小的效果,具体见下:

2021-04-25 at 23.12.11.gif

这个效果是由一个整体的自定义View不断绘制而成。其中波纹扩散动画,是通过定时改变波纹半径来实现的,此波纹是由先后两个空心圆组成,在实现过程中要注意时间和各自的尺寸变化,核心代码见下:

public void startAnima() {
if (drawTimingThread != null) {
drawTimingThread.sendEmptyMessage(MSG_DRAW0); // 开始1波纹
float time = (mRMaxRadius - mRMinRadius) / distance * 0.5f; // 先取整,再取中
drawTimingThread.sendEmptyMessageDelayed(MSG_DRAW1, (int)(animaBotIntervalTime * time));//定时开启2波纹
}
}

这是波纹1的半径变化,参考代码如下:

if (mCurRadius0 <= mRMaxRadius) {
mCurRadius0 += distance;
} else {
mCurRadius0 = mRMinRadius + distance;
}
circlePointF0 = drawCircleOnRipple(MSG_DRAW0, curIndex0);

mRPaint0.setAlpha(getAlphaOfRipple(curIndex0));//透明度
mCirclePaint0.setAlpha(getAlphaOfRipple(curIndex0));
curRadius0 = getRadiusOnRipple(curIndex0);
curIndex0++;
if (curIndex0 > (mRMaxRadius - mRMinRadius) / distance)
curIndex0 = 0;

cancleHandle(MSG_DRAW0);

圆球动画效果在这里是每隔200ms在相应的位置进行绘制,由于波纹扩散周期较短,所以我将圆球的隔旋转周期定为了45度,可自行修改。这里的难点也是在于怎么找到圆球的圆心坐标,即根据圆心坐标,半径,扇形角度来求扇形终射线与圆弧交叉点的x, y坐标的问题,上文也已经说过了,代码见下:

private PointF drawCircleOnRipple(int msg, int index) {

// 周期开始,随机初始角度
if (index == 0 && msg == MSG_DRAW0) {
cirAngel0 = (float) (Math.random() * -360 + 180);
} else if (index == 0) {
cirAngel1 = (float) (Math.random() * -360 + 180);
}

return CommentUtil.calcArcEndPointXY(
mRMaxRadius + getPaddingLeft() + mStrokeWidth
, mRMaxRadius + getPaddingTop() + mStrokeWidth
, msg == MSG_DRAW0 ? mCurRadius0 : mCurRadius1
// 每个周期旋转45度
, (msg == MSG_DRAW0 ? curIndex0 : curIndex1) * 1.0f
/ ((mRMaxRadius - mRMinRadius) / distance) * 45f
, msg == MSG_DRAW0 ? cirAngel0 : cirAngel1);
}

波纹和圆球的颜色渐变效果,由于不是渐变到全透明,所以我的alpha取值范围105-255,代码见下:

private int getAlphaOfRipple(int curIndex) {
final int alpha = curIndex * 150 * distance / (mRMaxRadius - mRMinRadius); // 只取150的二进制
return 255 - alpha;
}

其余的细节和方法就不贴了,都是比较常规的Paint方法。

栗子ε

这个效果是由四段贝塞尔曲线来拟合实现的,见下:

2021-04-25 at 23.32.00.gif

通过贝塞尔曲线我们能做很多的效果,todo:后续我会再出一篇贝塞尔的小栗子。下面是一个三阶贝塞尔曲线的动态图及公式,它通过控制曲线上的四个点:起始点、终止点以及两个相互分离的控制点来创造、编辑图形。其中参数 t 的值等于线段上某一个点距离起点的长度除以该线段长度。

在这里插入图片描述

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

由n段三阶贝塞尔曲线拟合圆形时,曲线端点到该端点最近的控制点的最佳距离是(4/3)tan(π/(2n))。且t=0.5时的点一定落在圆弧上。

222.png

所以当我们想用4条贝塞尔曲线拟合圆时,可以简单推导下h的值:

555.png

下面我们就拿四段贝塞尔曲线(h = 0.552284749831)组合成一条完整的圆,作为我们的初始态。求此 h 这个临界值的另一个作用是,我们需要运动的b曲线都是向外凸的。起始点和控制点的参考代码如下:

private void calculateCp() {
b = 0.552284749831;

if (startP == null || endP == null) {
startP = new PointF(0, - mRadius);
endP = new PointF(mRadius, 0);
}

// 平移后的画布坐标,坐标(0,0)为圆心

面试复习笔记:

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-LBRK0Owi-1713710108101)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值