前言

前两天在热搜上看到即刻app暂停服务了,正好之前同事问我说实践中仿写即刻的点赞你有思路吗,你不实现一下?看到热搜后,我突然记起来这件事,于是准备开始:实现其实并不难,用到了位移,缩放,渐变动画和自定义View的基础用法,刚好加深对自定义View的理解。

喜欢的小伙伴欢迎关注,我会定期分享Android知识点及解析,还会不断更新的BATJ面试专题,欢迎大家前来探讨交流,如有好的文章也欢迎投稿。

素材准备

把即刻app下载后,以解压包的方式解压,发现点赞效果有三张图,一张是没有点赞的小手图片,一张是点赞后的红色小手图片,最后一张是点赞后,点赞手指上的四点如下图:

实践思路

效果图

先仔细看上面即刻的点赞效果图,点赞后:灰色小手缩小了一下,并消失变成红色小手,红色小手放大了一些,并且手指上有四点出现,下面描述四点图像或者高亮都是指它。另外中间有一圈淡红色的圆形扩散放大效果,右边的数是一个一个字符第跳动,并不是整个数一起在跳动中被新的数替换掉,好像是数字轮表。如上面:3往上移并渐渐消失,4从下面出来并渐渐清晰出现。取消点赞后:高亮的四点消失,红色小手变成灰色小手,字符往下移,整个效果和点赞效果相反,下面准备用一个View来完成以上的实现。

具体分析

最外面的蓝色矩形就是这个自定义View的区域,里面有五个矩形,最上面的绿色矩形就是手指头距离本View顶部的范围,设定10px,下面绿色矩形也是小手底部距离本View底部的范围,设定10px。最左边的红色矩形是小手最左边距离本View左边缘的范围,设定10px,中间的红色矩形是小手距离数字文本显示的范围,距离10px,最右边的红色矩形是数字文本距离本View右边缘的范围,设定10px。
这样整个View的宽和高算出来了:
View的宽度 = 小手图像的宽度 + 数字文本宽度 + 30px
View的高度 = 小手图像的高度 (因为手指高度比数字文本高度高)+ 20px

下面确定小手上的四点位置,因为在Android上有坐标系这个概念,画出一张图只要确定左上角就可以了。

上图绿色矩形就是素材点赞后小手上的四点图像区域,绿色的点就是要我们要算出来,X坐标10px + 几px,10px是手指最左边距离坐标系Y轴的距离,为什么还要加几px呢,因为四点图像并不是和小手右边对齐,而是往里一点,所以还有加多4 - 5px。Y坐标其实不能非常准确算出来,我是用整个View的高度减去小手的高度再除2减去高亮四点图像的高度再加上15-17px的距离。
四点图像的左上角坐标:
X = (15px)
Y = (整个图像高度 - 小手图像高度)/ 2 - 高亮图像的高度 + 17px
小手的和手指上的四点位置绘制了,下面绘制数字文本,因为数字文本比较特殊,打算将整形转换为String类型,再通过字符数组将数字一个一个绘制出来。

image

因为绘制文本内容都是通过drawText(String text,float x,float y,Paint paint)这个方法实现的,方法的参数很简单,text是文本内容,x和y分别是文字的坐标,但是这个坐标并不是文字的左上角和描绘图像有区别的,先说x参数,x参数其实也并不是文字内容的左边位置,而是比文字内容的左边再往左一点点,因为在绝大多数字符,它们的宽度是要略微大于实际显示的宽度,也就是字符的左右两边会留一部分空隙,其实就是文字与文字之间,文字与边框之间的间隔。y参数,y参数是指文字的基线,就是用一条线让所有文字互相对齐的基准线,不同的语言文字,每个字符的高度和上下位置都是不一样的,让不同的文字并排显示的时候整体看起来很整齐,这个对齐不是顶部对齐或者底部对齐,而是重心对齐。这里先讲到这,具体详细自行网上查找。
因为数字字符是一个一个绘制的。
textX = 小手的宽度 + 20px + 右边字符的的宽度
textY = View的高度 + 文字区域的高度的一半下面实际用法不同,因为坐标原点不在左上角
画完一个字符后,下一个字符的x坐标加上上一个字符的宽度即可,高度是不变的,所以不用管。
点赞过程和取消点赞再结合动画实现就可以了。
上面只是粗略的把图像元素定位了,下面具体实现:

具体实现

初始化

在values下的attrs文件下添加属性集合如下:

<?xml version="1.0" encoding="utf-8"?>
<resources><!--name为声明的属性集合,可以随意取,最好是和自定义View一样的名称,这样方便管理--><declare-styleable name="JiKeLikeView"><!-- 声明属性,名称为like_number,取值是整形--><attr name="like_number" format="integer"/></declare-styleable></resources>

因为点赞只涉及到数字,所以声明和定义整形即可。
新建一个类继承View,并在构造函数中,读取attrs文件下配置属性:

public JiKeLikeView(Context context) {this(context, null);}public JiKeLikeView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public JiKeLikeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//获取attrs文件下配置属性TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.JiKeLikeView);//点赞数量 第一个参数就是属性集合里面的属性 固定格式R.styleable+自定义属性名字//第二个参数,如果没有设置这个属性,则会取设置的默认值likeNumber = typedArray.getInt(R.styleable.JiKeLikeView_like_number, 1999);//记得把TypedArray对象回收typedArray.recycle();init();}

init方法是初始化一些画笔,文本显示范围

private void init() {//创建文本显示范围textRounds = new Rect();//点赞数暂时8位widths = new float[8];//Paint.ANTI_ALIAS_FLAG 属性是位图抗锯齿//bitmapPaint是图像画笔bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//这是绘制原来数字的画笔 加入没点赞之前是45 那么点赞后就是46 点赞是46 那么没点赞就是45textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);oldTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//文字颜色大小配置 颜色灰色 字体大小为14textPaint.setColor(Color.GRAY);textPaint.setTextSize(SystemUtil.sp2px(getContext(), 14));oldTextPaint.setColor(Color.GRAY);oldTextPaint.setTextSize(SystemUtil.sp2px(getContext(), 14));//圆画笔初始化 Paint.Style.STROKE只绘制图形轮廓circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);circlePaint.setColor(Color.RED);circlePaint.setStyle(Paint.Style.STROKE);//设置轮廓宽度circlePaint.setStrokeWidth(SystemUtil.dp2px(getContext(), 2));//设置模糊效果 第一个参数是模糊半径,越大越模糊,第二个参数是阴影的横向偏移距离,正值向下偏移 负值向上偏移//第三个参数是纵向偏移距离,正值向下偏移,负值向上偏移 第四个参数是画笔的颜色circlePaint.setShadowLayer(SystemUtil.dp2px(getContext(), 1), SystemUtil.dp2px(getContext(), 1), SystemUtil.dp2px(getContext(), 1), Color.RED);}

在onAttachedToWindow方法上创建Bitmap对象

    /*** 这个方法是在Activity resume的时候被调用的,Activity对应的window被添加的时候* 每个view只会调用一次,可以做一些初始化操作*/@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();Resources resources = getResources();//构造Bitmap对象,通过BitmapFactory工厂类的static Bitmap decodeResource根据给定的资源id解析成位图unLikeBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_message_unlike);likeBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_message_like);shiningBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_message_like_shining);}

至于为什么要在这个方法构建而不写在init方法,上面代码附带了解释。
另外要在onDetachedFromWindow方法回收bitmap

/*** 和onAttachedToWindow对应,在destroy view的时候调用*/@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();//回收bitmapunLikeBitmap.recycle();likeBitmap.recycle();shiningBitmap.recycle();}

构造了三个Bitmap对象,上面分析很清楚了,一个是小手上的四点,一个是点赞小手,最后一个是没点赞的小手。

计算宽高

    /*** 测量宽高* 这两个参数是由父视图经过计算后传递给子视图* @param widthMeasureSpec 宽度* @param heightMeasureSpec 高度*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//MeasureSpec值由specMode和specSize共同组成,onMeasure两个参数的作用根据specMode的不同,有所区别。//当specMode为EXACTLY时,子视图的大小会根据specSize的大小来设置,对于布局参数中的match_parent或者精确大小值//当specMode为AT_MOST时,这两个参数只表示了子视图当前可以使用的最大空间大小,而子视图的实际大小不一定是specSize。所以我们自定义View时,重写onMeasure方法主要是在AT_MOST模式时,为子视图设置一个默认的大小,对于布局参数wrap_content。//高度默认是bitmap的高度加上下margin各10dpheightMeasureSpec = MeasureSpec.makeMeasureSpec(unLikeBitmap.getHeight() + SystemUtil.dp2px(getContext(), 20), MeasureSpec.EXACTLY);//宽度默认是bitmap的宽度加左右margin各10dp和文字宽度和文字右侧10dp likeNumber是文本数字String textnum = String.valueOf(likeNumber);//得到文本的宽度float textWidth = textPaint.measureText(textnum, 0, textnum.length());//计算整个View的宽度 小手宽度 + 文本宽度 + 30pxwidthMeasureSpec = MeasureSpec.makeMeasureSpec(((int) (unLikeBitmap.getWidth() + textWidth + SystemUtil.dp2px(getContext(), 30))), MeasureSpec.EXACTLY);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}

至于上面为什么用MeasureSpec.EXACTLY,上面已经解释很清楚了。

绘制onDraw

绘制小手

        super.onDraw(canvas);//获取正个View的高度int height = getHeight();//取中心int centerY = height / 2;//小手根据有没有点赞进行改变Bitmap handBitmap = isLike ? likeBitmap : unLikeBitmap;//得到图像宽度int handBitmapWidth = handBitmap.getWidth();//得到图像高度int handBitmapHeight = handBitmap.getHeight();//画小手int handTop = (height - handBitmapHeight) / 2;//先保存画布的状态canvas.save();//根据bitmap中心进行缩放canvas.scale(handScale, handScale, handBitmapWidth / 2, centerY);//画bitmap小手,第一个是参数对应的bitmap,第二个参数是左上角坐标,第三个参数上顶部坐标,第四个是画笔canvas.drawBitmap(handBitmap, SystemUtil.dp2px(getContext(), 10), handTop, bitmapPaint);//读取之前没有缩放画布的状态canvas.restore();

这里解释一下为什么用到canvas.save()和canvas.restore()呢,因为整个点赞效果是有动画效果的,对画布进行缩放,如果不保存画布之前的状态,缩放后继续绘制其他图像效果并不是你想要的。

画小手上的四点高亮

//画上面四点闪亮//先确定顶部int shiningTop = handTop - shiningBitmap.getHeight() + SystemUtil.dp2px(getContext(), 17);//根据隐藏系数设置点亮的透明度bitmapPaint.setAlpha((int) (255 * shiningAlpha));//保存画布状态canvas.save();//画布根据点亮的缩放系数进行缩放canvas.scale(shiningScale, shiningScale, handBitmapWidth / 2, handTop);//画出点亮的bitmapcanvas.drawBitmap(shiningBitmap, SystemUtil.dp2px(getContext(), 15), shiningTop, bitmapPaint);//恢复画笔之前的状态canvas.restore();//并且恢复画笔bitmapPaint透明度bitmapPaint.setAlpha(255);

注意只是用了bitmapPaint.setAlpha()方法设置这四点是否显示和消失,设置上这四点都是存在画布上的,点赞后设置setAlpha(255)出现,否则根据透明度来进行显示,有个变化的趋势。

画数字文本区域和绘制点赞时圆圈扩散

这里分两种大情况,一种是不同位数的数字变化,另外一种是同位数数字变化

    //画文字String textValue = String.valueOf(likeNumber);//如果点赞了,之前的数值就是点赞数-1,如果取消点赞,那么之前数值(对比点赞后)就是现在显示的String textCancelValue;if (isLike) {textCancelValue = String.valueOf(likeNumber - 1);} else {if (isFirst) {textCancelValue = String.valueOf(likeNumber + 1);} else {isFirst = !isFirst;textCancelValue = String.valueOf(likeNumber);}}//文本的长度int textLength = textValue.length();//获取绘制文字的坐标 getTextBounds 返回所有文本的联合边界textPaint.getTextBounds(textValue, 0, textValue.length(), textRounds);//确定X坐标 距离手差10dpint textX = handBitmapWidth + SystemUtil.dp2px(getContext(), 20);//确定Y坐标 距离 大图像的一半减去 文字区域高度的一半 即可得出 getTextBounds里的rect参数得到数值后,// 查看它的属性值 top、bottom会发现top是一个负数;bottom有时候是0,有时候是正数。结合第一点很容易理解,因为baseline坐标看成原点(0,0),// 那么相对位置top在它上面就是负数,bottom跟它重合就为0,在它下面就为负数。像小写字母j g y等,它们的bounds bottom都是正数,// 因为它们都有降部(在西文字体排印学中,降部指的是一个字体中,字母向下延伸超过基线的笔画部分)。int textY = height / 2 - (textRounds.top + textRounds.bottom) / 2;//绘制文字 这种情况针对不同位数变化 如 99 到100 999到10000if (textLength != textCancelValue.length() || textMaxMove == 0) {//第一个参数就是文字内容,第二个参数是文字的X坐标,第三个参数是文字的Y坐标,注意这个坐标//并不是文字的左上角 而是与左下角比较接近的位置//canvas.drawText(textValue,  textX, textY, textPaint);//点赞if (isLike) {//圆的画笔根据设置的透明度进行变化circlePaint.setAlpha((int) (255 * shingCircleAlpha));//画圆canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);//根据透明度进行变化oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));//绘制之前的数字canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);//设置新数字的透明度textPaint.setAlpha((int) (255 * textAlpha));//绘制新数字(点赞后或者取消点赞)canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);} else {oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));canvas.drawText(textCancelValue, textX, textY + textMaxMove + textMoveDistance, oldTextPaint);textPaint.setAlpha((int) (255 * textAlpha));canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);}return;}//下面这种情况区别与99 999 9999这种 就是相同位数变化//把文字拆解成一个一个字符 就是获取字符串中每个字符的宽度,把结果填入参数widths//相当于measureText()的一个快捷方法,计算等价于对字符串中的每个字符分别调用measureText(),并把//它们的计算结果分别填入widths的不同元素textPaint.getTextWidths(textValue, widths);//将字符串转换为字符数组char[] chars = textValue.toCharArray();char[] oldChars = textCancelValue.toCharArray();for (int i = 0; i < chars.length; i++) {if (chars[i] == oldChars[i]) {textPaint.setAlpha(255);canvas.drawText(String.valueOf(chars[i]), textX, textY, textPaint);} else {//点赞if (isLike) {circlePaint.setAlpha((int) (255 * shingCircleAlpha));canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));canvas.drawText(String.valueOf(oldChars[i]), textX, textY - textMaxMove + textMoveDistance, oldTextPaint);textPaint.setAlpha((int) (255 * textAlpha));canvas.drawText(String.valueOf(chars[i]), textX, textY + textMoveDistance, textPaint);} else {oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));canvas.drawText(String.valueOf(oldChars[i]), textX, textY + textMaxMove + textMoveDistance, oldTextPaint);textPaint.setAlpha((int) (255 * textAlpha));canvas.drawText(String.valueOf(chars[i]), textX, textY + textMoveDistance, textPaint);}}//下一位数字x坐标要加上前一位的宽度    textX += widths[i];}

我这里用了textValue和textCancelValue分别记录变化前后的数字,下面可能对确定y坐标的代码有疑问,这里解释一下:

        int textY = height / 2 - (textRounds.top + textRounds.bottom) / 2;  

这里textRounds.top是负数,坐标原点并不是在左上角,而是在文本的基线中,自己再查查相关资料和想想就明白了,上面代码也有解释。
透明度变化就不详细讲了,这里讲讲移动距离:

//点赞if (isLike) {//圆的画笔根据设置的透明度进行变化circlePaint.setAlpha((int) (255 * shingCircleAlpha));//画圆canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);//根据透明度进行变化oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));//绘制之前的数字canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);//设置新数字的透明度textPaint.setAlpha((int) (255 * textAlpha));//绘制新数字(点赞后或者取消点赞)canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);} else {oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));canvas.drawText(textCancelValue, textX, textY + textMaxMove + textMoveDistance, oldTextPaint);textPaint.setAlpha((int) (255 * textAlpha));canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);}

textMaxMove设置是20px,textMoveDistance设置是文字的高度14px

                //绘制之前的数字canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);//绘制新数字(点赞后或者取消点赞)canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);

这两行就是绘制新数字,最主要就是y坐标的变化,举个例子应该很好理解:假如现在104,我现在点赞要变成105,textCancelValue是104,textValue是105.因为textMoveDistance是从20变化0逐渐减少的,那么第一条公式是绘制105,textY - textMaxMove + textMoveDistance,y坐标越来越小,所以5就会上移,同理textY + textMoveDistance 根据这条公式4也会上移,因为数值越来越小,还有就是将数字转换为字符串进行处理不难理解。
画圆圈扩散主要是确定圆圈中心点,半径大概确定就行:

canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);

前两个参数就是确定圆中心,我设置在小手图像中心。

触摸处理onTouchEvent

我是设置触摸就触发点赞事件:

@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:jump();break;}return super.onTouchEvent(event);}

jump方法如下:

/*** 点赞事件触发*/private void jump() {isLike = !isLike;if (isLike) {++likeNumber;setLikeNum();//自定义属性 在ObjectAnimator中,是先根据属性值拼装成对应的set函数名字,比如下面handScale的拼装方法就是//将属性的第一个字母强制大写后与set拼接,所以就是setHandScale,然后通过反射找到对应控件的setHandScale(float handScale)函数//将当前数字值做为setHandScale(float handScale)的参数传入 set函数调用每隔十几毫秒就会被用一次//ObjectAnimator只负责把当前运动动画的数值传给set函数,set函数怎么来做就在里面写就行ObjectAnimator handScaleAnim = ObjectAnimator.ofFloat(this, "handScale", 1f, 0.8f, 1f);//设置动画时间handScaleAnim.setDuration(duration);//动画 点亮手指的四点 从0 - 1出现ObjectAnimator shingAlphaAnim = ObjectAnimator.ofFloat(this, "shingAlpha", 0f, 1f);// shingAlphaAnim.setDuration(duration);//放大 点亮手指的四点ObjectAnimator shingScaleAnim = ObjectAnimator.ofFloat(this, "shingScale", 0f, 1f);//画中心圆形有内到外扩散ObjectAnimator shingClicleAnim = ObjectAnimator.ofFloat(this, "shingCircleScale", 0.6f, 1f);//画出圆形有1到0消失ObjectAnimator shingCircleAlphaAnim = ObjectAnimator.ofFloat(this, "shingCircleAlpha", 0.3f, 0f);//动画集一起播放AnimatorSet animatorSet = new AnimatorSet();animatorSet.playTogether(handScaleAnim, shingAlphaAnim, shingScaleAnim, shingClicleAnim, shingCircleAlphaAnim);animatorSet.start();} else {//取消点赞--likeNumber;setLikeNum();ObjectAnimator handScaleAnim = ObjectAnimator.ofFloat(this, "handScale", 1f, 0.8f, 1f);handScaleAnim.setDuration(duration);handScaleAnim.start();//手指上的四点消失,透明度设置为0setShingAlpha(0);}}

上面用了几个动画函数,这里运用了兹定于属性,上面代码解释很清楚了
动画会触发下面相应setXXXX()方法

/*** 手指缩放方法** @param handScale*/public void setHandScale(float handScale) {//传递缩放系数this.handScale = handScale;//请求重绘View树,即draw过程,视图发生大小没有变化就不会调用layout过程,并且重绘那些“需要重绘的”视图//如果是view就绘制该view,如果是ViewGroup,就绘制整个ViewGroupinvalidate();}/*** 手指上四点从0到1出现方法** @param shingAlpha*/public void setShingAlpha(float shingAlpha) {this.shiningAlpha = shingAlpha;invalidate();}/*** 手指上四点缩放方法** @param shingScale*/@Keeppublic void setShingScale(float shingScale) {this.shiningScale = shingScale;invalidate();}/*** 设置数字变化*/public void setLikeNum() {//开始移动的Y坐标float startY;//最大移动的高度textMaxMove = SystemUtil.dp2px(getContext(), 20);//如果点赞了 就下往上移if (isLike) {startY = textMaxMove;} else {startY = -textMaxMove;}ObjectAnimator textInAlphaAnim = ObjectAnimator.ofFloat(this, "textAlpha", 0f, 1f);textInAlphaAnim.setDuration(duration);ObjectAnimator textMoveAnim = ObjectAnimator.ofFloat(this, "textTranslate", startY, 0);textMoveAnim.setDuration(duration);AnimatorSet animatorSet = new AnimatorSet();animatorSet.playTogether(textInAlphaAnim, textMoveAnim);animatorSet.start();}/*** 设置数值透明度*/public void setTextAlpha(float textAlpha) {this.textAlpha = textAlpha;invalidate();}/*** 设置数值移动*/public void setTextTranslate(float textTranslate) {textMoveDistance = textTranslate;invalidate();}/*** 画出圆形波纹** @param shingCircleScale*/public void setShingCircleScale(float shingCircleScale) {this.shingCircleScale = shingCircleScale;invalidate();}/*** 圆形透明度设置** @param shingCircleAlpha*/public void setShingCircleAlpha(float shingCircleAlpha) {this.shingCircleAlpha = shingCircleAlpha;invalidate();}

效果如下:

总结

这个简单例子对一些自定义View的基本使用都涉及了,如绘制,canvas的一些基本用法等。
和即刻点赞效果还是有区别,可以通过加下动画差值器优化。

觉得文章不错的小伙伴帮忙点点赞加关注哦 ,有什么问题的话也欢迎大家前来探讨交流。

即刻app暂停服务?趁现在教你一步一步实现即刻点赞效果相关推荐

  1. 即可app暂停服务?趁现在教你一步一步实现即刻点赞效果

    前言 前两天在热搜上看到即刻app暂停服务了,正好之前同事问我说实践中仿写即刻的点赞你有思路吗,你不实现一下?看到热搜后,我突然记起来这件事,于是准备开始:实现其实并不难,用到了位移,缩放,渐变动画和 ...

  2. Android自定义View教你一步一步实现即刻点赞效果

    前言 今天朋友看了HenCoder的自定义View后说,HenCoder对自定义View讲的不错.实践中仿写即刻的点赞你有思路吗,你不实现一下?二话不说,看了朋友手机效果,对他说:实现不难,用到了位移 ...

  3. android仿小米运动,GitHub - luciferldy/Bnimation: 模仿即刻 App 点赞与数字跳动效果,模仿小米运动首页转圈圈的烟花效果...

    Bnimation 现阶段实现了模仿即刻点赞效果以及数字跳动增长,小米运动首页烟花转圈圈(•̀⌄•́)效果. 即刻 即刻的点赞效果 下面是我实现的效果(最下为 TextView 自带的展示数字效果) ...

  4. 新浪微博回应热搜被暂停更新一周;即刻 APP 下架一年后恢复上线;Android 11 Beta 版发布| 极客头条...

    整理 | 屠敏 头图 | CSDN 下载自东方 IC 快来收听极客头条音频版吧,智能播报由出门问问「魔音工坊」提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极 ...

  5. 即刻App产品分析报告

    即刻产品分析报告 一.前期描述 即刻是一款基于兴趣的信息推送工具,支持iOS和Android平台. 操作方便.简洁.易上手,相比较于网易新闻.腾讯新闻.今日头条等新闻客户端,即刻更具有倾向性,它可根据 ...

  6. java搭建安卓服务器_无框架完整搭建安卓app及其服务端(一)

    技术背景: 我的一个项目做的的是图片处理,用 python 实现图片处理的核心功能后,想部署到安卓app中,但是对于一个对安卓和服务器都一知半解的小白来说要现学的东西太多了. 而实际上,我们的项目要求 ...

  7. 一年两次新产品发布,APICloud上线App定制服务平台

    9月20日,APICloud在既有的移动应用开发平台之上,推出了全新的服务体系--App定制平台,让传统企业和互联网创业公司多一个快速高效.高性价比的App开发选择.从钻研产品技术升级到提供专业企业服 ...

  8. android中暂停服务,Android暂停服务,线程,Asynctask?使用postdelayed的处理程序呢?...

    我有后台服务(Service→Thread→Timer→Asynctask). Timer每5秒执行一次Asynctask.如果Asynctask返回true,则发送通知.Android暂停服务,线程 ...

  9. linux备份mysql需要暂停服务吗_【MySQL运维】线上MySQL数据库停服迁移流程

    一.数据备份与恢复阶段,选在凌晨1点进行操作,暂停服务进行备份(允许停服2个小时) 1.首先停止Nginx服务,并且修改数据库用户密码,防止还有新的连接进来 2.杀掉某个用户所有进程 for i in ...

  10. 视觉中国、IC photo被责令暂停服务全面整改

    12月10日消息,国家网信办指导有关地方网信办约谈视觉中国网站.IC photo(东方IC)网站负责人,即日起两家网站暂停服务全面整改. 12月10日,针对视觉中国网站(域名vcg.com)和IC p ...

最新文章

  1. 有哪些适合远程办公的软件值得推荐?
  2. 也谈谈程序员职业规划的几个问题——我的一些故事
  3. python多态的概念_Python 多态
  4. BZOJ2115: [Wc2011] Xor
  5. pandas删除满足条件的行_入门Pandas练习
  6. Polemo Chat客户端行为
  7. react(90)--时间戳比较
  8. sql limit 子句_SQL Server TOP子句概述和示例
  9. windows无法访问指定设备_恢复 你的电脑/设备需要修改 未连接或无法访问所需设备。...
  10. webpack教程——css的加载
  11. 数学建模-非线性优化模型
  12. AD15PCB快速布局元器件
  13. vrep_Nao控制_学习记录
  14. 安装企业级的dokuwiki文档系统
  15. 三重积分平均值_二重积分或者三重积分里面如果积分区域关于坐标轴对称比如积分区域是一个圆或者球,就只用求第一象限或卦...
  16. 中国RV减速器行业竞争格局与发展趋势预测报告2022-2028年
  17. 基于可编程图形处理器的骨骼动画算法及其比较
  18. 怎样自己定制标签(Custom Tag)
  19. Java的支持率让小伙伴们都惊呆了
  20. MySQL循环语句实战

热门文章

  1. aix系统服务器限制端口访问,aix系统怎么查看端口是否开启
  2. 19款国产手机无一幸免:15分钟破解人脸识别,打印眼镜让刷脸形同虚设
  3. 正确学习PLC编程的方法
  4. 研磨设计模式笔记之简单工厂模式
  5. Windows环境CMake安装教程
  6. 占内存小的android浏览器,一点浏览器占内存吗 世界最小浏览器使用评测
  7. c语言的编译器vs2019的安装及简单实用
  8. 锐捷长ping_锐捷实战系列(二) Wireshark抓包分析Ping过程
  9. R 修改安装包默认存放位置的方法
  10. ISO9001和ISO27001质量管理体系(详解)