Android:会呼吸的悬浮气泡

写在前面

最早看到这个效果是 MIUI6 系统升级界面,有很多五颜六色的气泡悬浮着,觉得很好看。

可惜现在找不到动态图了。而且虽然 MIUI8 更新界面也有类似的气泡,但是不过是静态的,不咋好看。

再次见到这个效果是在 Pure 天气这款软件中,可惜开发者不开源。不过万能的 Github 上有类似的实现,于是果断把自定义 View 部分抽出来学习学习。

怀着敬意放上原项目地址,很好看的一款天气 APP:

还是那句话,学习自定义 View 没有什么捷径,就是看源码、模仿、动手。

具体实现

先思考

在看源码之前,我自己想了一下该怎样去实现,思路如下:

自定义一个圆形 View ,支持大小、颜色、位置等属性

浮动利用最简单的平移动画来实现

平移的范围通过自定义圆心的移动范围来确定

最后给动画一个循环就行了

虽然看起来比较简单,但是实现起来还是遇到不少坑。首先画圆一点问题都没有,问题出在动画上。动画看起来很迟钝,根本就不是呼吸效果,像哮喘一样。

所以不能用动画,就想到了不断重绘。于是仍然给圆心设置一个小圆,让圆心在小圆上移动,在这个过程中不断重绘,结果直接 Crash 了,看了看 Log ,发现是线程阻塞了,但是这里并没有开启子线程啊,一看,我去,主线程。

那这条路行不通,又想到用贝塞尔去做,结果突然想起来之前绘制阻塞了主线程,那开子线程绘制不就完了,Android View 里面能开子线程绘制的不就是 SurfaceView 。于是看了看作者源码,果然是自定义SurfaceView 。

关于 SurfaceView 我只在以前学习的视频案例、撕MM衣服案例、还有手写板案例中遇到过,学的不是很深,加上本文它不是重点,所以就不详细说了,如果不了解这个或者想深入了解一下的话,可以点击文末的相关链接,这里只简单提一下比较重要的一点,也就是 SurfaceView跟 View 的主要区别:

SurfaceView 在一个新起的单独线程中重新绘制画面,而 View必须在 UI 线程中更新画面。

这就决定了 SurfaceView 的一些特定使用场景:

需要界面迅速更新;

对帧率要求较高的情况;

渲染 UI 需要较长的时间。

所以综合来看,SurfaceView 无疑是实现这类效果的最佳选择。

再分析

废话不多说,来分析一下思路。

1、首先光从界面上能看到就是圆,且是能浮动的圆,所以不管能不能动,先得把圆画出来。要是我的话,我直接就拿着 Paint 在 Canvas 上开画了。在源码中开发者单独抽取了绘制圆的类,但这个类的作用不仅仅是绘制圆,后面我们再说。

2、其次就是自定义 SurfaceView ,我们需要把画出来的圆放到SurfaceView 中。而自定义 SurfaceView 需要实现SurfaceHolder.Callback 接口,就是一些回调方法。同时需要开子线程去不断刷新界面,因为这些圆是需要动起来的.

3、另外重要的一点就是,SurfaceView 在渲染过程中需要消耗大量资源,比如内存啊、CPU 啊之类的,所以最好提供一个生命周期相关的方法,让它和 Activity 的生命周期保持一致,尽量保证及时回收资源,减少消耗。

4、最后需要提一点的是,SurfaceView 本身并不需要绘制内容,或者说在这里它的主要作用就是刷新界面就行了。就好像在放视频的时候,只需要刷新视频页面就行,它并不参与视频具体内容的绘制。

所以这样来说的话,我们最好定义一个绘制过程的中间者,主要作用就是把绘制出来的圆放在 SurfaceView 上,同时也能做一些其他的工作,比如绘制背景、设置尺寸等。这样做的好处就是能让 SurfaceView专心的做一件事:不断刷新,这就够了。

OK,总结一下我们到底需要哪些东西:

专门绘制圆的类

刷新过程中的子线程

实现 SurfaceHolder.Callback 接口方法

提供生命周期相关方法

一个绘制过程的中间对象

多提一句,最后的绘制中间者也可以不定义,全部封装到自定义SurfaceView 中,但是从我实践来看,我最后不得不单独抽取出来,因为 SurfaceView 类看起来太乱了,这也是源码中的实现方式。

后动手

Talk is cheap,Show me the code .

1、画圆

既然要画圆,我们肯定要设置一些圆的基本属性:

圆心坐标

圆的半径

圆的颜色

由于需要圆动起来,也就是说它会偏移,所以要确定一个范围。范围确定了,就需要指定它该怎么变化,因为我们要求它缓慢而顺畅的呼吸,不能瞬间大喘气,也就是它不能瞬间移动偏移量那么多,所以最好指定它每一步变化多少,那就需要下面这两样东西:

圆心偏移范围

每一帧的变化量

额外的,因为移动是每次都需要变的,下一次变化时不能重新开始,所以我们要记录当前已经偏移的距离,然后根据一个标志位不断呼气…吐气…呼气…吐气,所以需要:

当前帧变化量

标志位

好了,看构造函数吧:

/**

* @author Mixiaoxiao

* @revision xiarui 16/09/27

* @description 圆形浮动气泡

*/

class CircleBubble {

private final float cx, cy; //圆心坐标

private final float dx, dy; //圆心偏移距离

private final float radius; //半径

private final int color; //画笔颜色

private final float variationOfFrame; //设置每帧变化量

private boolean isGrowing = true; //根据此标志位判断左右移动

private float curVariationOfFrame = 0f; //当前帧变化量

CircleBubble(float cx, float cy, float dx, float dy, float radius, float variationOfFrame, int color) {

this.cx = cx;

this.cy = cy;

this.dx = dx;

this.dy = dy;

this.radius = radius;

this.variationOfFrame = variationOfFrame;

this.color = color;

}

//...画圆方法先省略

}

好了,构造好了圆就要开始绘制圆了。之前说到,这个类的作用不仅仅是绘制圆,还要不断更新圆的位置,也就是不断重绘圆。更直接地说,我们需要绘制出不断偏移的每一帧的圆。

步骤如下:

确定当前帧偏移位置

根据当前帧偏移位置计算圆心坐标

设置圆的颜色透明度等属性

真正的开始绘制圆

代码如下,结合上面的步骤和代码中的注释应该很容易看懂:

/**

* 更新位置并重新绘制

*

* @param canvas 画布

* @param paint 画笔

* @param alpha 透明值

*/

void updateAndDraw(Canvas canvas, Paint paint, float alpha) {

/**

* 每次绘制时都根据标志位(isGrowing)和每帧变化量(variationOfFrame)进行更新

* 说白了其实就是每帧都会变化一段距离 连在一起就产生动画效果

*/

if (isGrowing) {

curVariationOfFrame += variationOfFrame;

if (curVariationOfFrame > 1f) {

curVariationOfFrame = 1f;

isGrowing = false;

}

} else {

curVariationOfFrame -= variationOfFrame;

if (curVariationOfFrame < 0f) {

curVariationOfFrame = 0f;

isGrowing = true;

}

}

//根据当前帧变化量计算圆心偏移后的位置

float curCX = cx + dx * curVariationOfFrame;

float curCY = cy + dy * curVariationOfFrame;

//设置画笔颜色

int curColor = convertAlphaColor(alpha * (Color.alpha(color) / 255f), color);

paint.setColor(curColor);

//这里才真正的开始画圆形气泡

canvas.drawCircle(curCX, curCY, radius, paint);

}

其中的 convertAlphaColor() 方法是个工具方法,作用就是转化一下颜色,不必深究:

/**

* 转成透明颜色

*

* @param percent 百分比

* @param originalColor 初始颜色

* @return 带有透明效果的颜色

*/

private static int convertAlphaColor(float percent, final int originalColor) {

int newAlpha = (int) (percent * 255) & 0xFF;

return (newAlpha << 24) | (originalColor & 0xFFFFFF);

}

到此,画每一帧圆的工作我们就完成了。

2、绘制中间者对象

现在来说这个特殊的中间者对象,前文说了,单独抽取这个类不是必须的。但最好抽取一下,让 SurfaceView 专心做自己的事情。在这个中间者对象中我们做两件事情:

绘制背景

绘制悬浮气泡

先来看绘制背景。为什么需要绘制背景呢,因为 SurfaceView 本身其实是个黑色,从我们日常看视频的软件中也能发现,视频播放时周围都是黑色的。有人问为什么不能直接在布局中设置呢?当然可以直接设置啊,不过要记得添加一句 setZOrderOnTop(true) ,不然会把之后绘制的悬浮气泡遮挡住。

在这里就来绘制一下吧,因为源码中给出了一个渐变色的绘制,我觉得挺好玩,学一学。直接看代码吧,都是模板代码,没啥好解释的,简单的 get/set 再画一下就好了:

/**

* @author Mixiaoxiao

* @revision xiarui 16/09/27

* @description 绘制圆形浮动气泡及设定渐变背景的绘制对象

*/

public class BubbleDrawer {

/*===== 图形相关 =====*/

private GradientDrawable mGradientBg; //渐变背景

private int[] mGradientColors; //渐变颜色数组

/**

* 设置渐变背景色

*

* @param gradientColors 渐变色数组 必须 >= 2 不然没法渐变

*/

public void setBackgroundGradient(int[] gradientColors) {

this.mGradientColors = gradientColors;

}

/**

* 获取渐变色数组

*

* @return 渐变色数组

*/

private int[] getBackgroundGradient() {

return mGradientColors;

}

/**

* 绘制渐变背景色

*

* @param canvas 画布

* @param alpha 透明值

*/

private void drawGradientBackground(Canvas canvas, float alpha) {

if (mGradientBg == null) {

//设置渐变模式和颜色

mGradientBg = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, getBackgroundGradient());

//规定背景宽高 一般都为整屏

mGradientBg.setBounds(0, 0, mWidth, mHeight);

}

//然后开始画

mGradientBg.setAlpha(Math.round(alpha * 255f));

mGradientBg.draw(canvas);

}

//...暂时省略圆的绘制方法

}

上面代码就一点需要注意,渐变最少需要两种颜色,不然没法渐变,这个很好理解吧,不再多解释了。现在我们来画气泡,步骤如下:

设置一下圆的范围,一般都为全屏

根据圆的构造方法添加多个圆

绘制添加的这些圆

直接来看代码,其实也很简单:

/*===== 图形相关 =====*/

private Paint mPaint; //抗锯齿画笔

private int mWidth, mHeight; //上下文对象

private ArrayList mBubbles; //存放气泡的集合

/**

* 构造函数

*

* @param context 上下文对象 可能会用到

*/

public BubbleDrawer(Context context) {

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mBubbles = new ArrayList<>();

}

/**

* 设置显示悬浮气泡的范围

* @param width 宽度

* @param height 高度

*/

void setViewSize(int width, int height) {

if (this.mWidth != width && this.mHeight != height) {

this.mWidth = width;

this.mHeight = height;

if (this.mGradientBg != null) {

mGradientBg.setBounds(0, 0, width, height);

}

}

//设置一些默认的气泡

initDefaultBubble(width);

}

/**

* 初始化默认的气泡

*

* @param width 宽度

*/

private void initDefaultBubble(int width) {

if (mBubbles.size() == 0) {

mBubbles.add(new CircleBubble(0.20f * width, -0.30f * width, 0.06f * width, 0.022f * width, 0.56f * width,

0.0150f, 0x56ffc7c7));

mBubbles.add(new CircleBubble(0.58f * width, -0.15f * width, -0.15f * width, 0.032f * width, 0.6f * width,

0.00600f, 0x45fffc9e));

//...

}

}

/**

* 用画笔在画布上画气泡

*

* @param canvas 画布

* @param alpha 透明值

*/

private void drawCircleBubble(Canvas canvas, float alpha) {

//循环遍历所有设置的圆形气泡

for (CircleBubble bubble : this.mBubbles) {

bubble.updateAndDraw(canvas, mPaint, alpha);

}

}

从代码中看出,已经将所有添加的圆放到集合里,然后遍历集合去画,这就不用添加一个画一个了,只需统一添加再统一绘制即可。

既然背景绘制好了,气泡也绘制好了,那就到了最后一步,需要提供方法让 SurfaceView 去添加背景和气泡:

/**

* 画背景 画所有的气泡

*

* @param canvas 画布

* @param alpha 透明值

*/

void drawBgAndBubble(Canvas canvas, float alpha) {

drawGradientBackground(canvas, alpha);

drawCircleBubble(canvas, alpha);

}

到此,这个绘制中间者对象就完成了。

3、自定义 SurfaceView

终于到了重要的 SurfaceView 部分了,这部分不太好描述,因为最好的解释方式就是看代码。

首先自定义 FloatBubbleView 继承于 SurfaceView ,看一下简单的变量定义、构造方法:

/**

* @author Mixiaoxiao

* @revision xiarui 16/09/27

* @description 用圆形浮动气泡填充的View

* @remark 因为气泡需要不断绘制 所以防止阻塞UI线程 需要继承 SurfaceView 开启线程更新 并实现回调类

*/

public class FloatBubbleView extends SurfaceView implements SurfaceHolder.Callback {

private DrawThread mDrawThread; //绘制线程

private BubbleDrawer mPreDrawer; //上一次绘制对象

private BubbleDrawer mCurDrawer; //当前绘制对象

private float curDrawerAlpha = 0f; //当前透明度 (范围为0f~1f,因为 CircleBubble 中 convertAlphaColor 方法已经处理过了)

private int mWidth, mHeight; //当前屏幕宽高

public FloatBubbleView(Context context) {

super(context);

initThreadAndHolder(context);

}

//...省略其他构造方法

/**

* 初始化绘制线程和 SurfaceHolder

*

* @param context 上下文对象 可能会用到

*/

private void initThreadAndHolder(Context context) {

mDrawThread = new DrawThread();

SurfaceHolder surfaceHolder = getHolder();

surfaceHolder.addCallback(this); //添加回调

surfaceHolder.setFormat(PixelFormat.RGBA_8888); //渐变效果 就是显示SurfaceView的时候从暗到明

mDrawThread.start(); //开启绘制线程

}

/**

* 当view的大小发生变化时触发

*

* @param w 当前宽度

* @param h 当前高度

* @param oldw 变化前宽度

* @param oldh 变化前高度

*/

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

mWidth = w;

mHeight = h;

}

//...省略其他方法

}

这里其他的内容都比较好理解,重点提两个变量:

private BubbleDrawer mPreDrawer; //上一次绘制对象

private BubbleDrawer mCurDrawer; //当前绘制对象

这是什么意思呢,开始我也不太理解,那换个思路,大家还记得ListView 中的 ViewHolder 么,这个 ViewHolder 其实就是用来复用的。那 SurfaceView 中也有个 SurfaceHolder ,作用可以看做是相同的,就是用来不断复用不断刷新界面的。

那这里的这两个变量是干什么的呢?就是相当于 当前刷新的中间者对象 和 上一次刷新的中间者对象 。

那获得这两个对象有什么用呢?注意看,还有个 curDrawerAlpha 变量,顾名思义,当前的透明度。

三者结合在一起,再加上一个这样的小循环:

if (curDrawerAlpha < 1f) {

curDrawerAlpha += 0.5f;

if (curDrawerAlpha > 1f) {

curDrawerAlpha = 1f;

mPreDrawer = null;

}

}

那这又有什么作用呢,别急,先看下面两张对比图,分别设置curDrawerAlpha += 0.2f 和 curDrawerAlpha += 0.8f:

模拟器太卡,将就着看

再看 0.8f ,从暗到明显然快了点:

现在知道作用了么,就是实现界面从暗到明的效果。那为什么需要这样的效果呢,我尝试过去掉这个,发现绘制的时候会偶尔出现闪黑屏的现象,黑色刚好是 SurfaceView 的本身颜色,加上这个效果就不会出现了。

好,接下来看重中之重的绘制线程方法,为了方便我单独抽取了线程类,并将 run 方法按照不同的功能分成好几个方法,注释写的很清晰:

/**

* 绘制线程 必须开启子线程绘制 防止出现阻塞主线程的情况

*/

private class DrawThread extends Thread {

SurfaceHolder mSurface;

boolean mRunning, mActive, mQuit; //三种状态

Canvas mCanvas;

@Override

public void run() {

//一直循环 不断绘制

while (true) {

synchronized (this) {

//根据返回值 判断是否直接返回 不进行绘制

if (!processDrawThreadState()) {

return;

}

//动画开始时间

final long startTime = AnimationUtils.currentAnimationTimeMillis();

//处理画布并进行绘制

processDrawCanvas(mCanvas);

//绘制时间

final long drawTime = AnimationUtils.currentAnimationTimeMillis() - startTime;

//处理一下线程需要的睡眠时间

processDrawThreadSleep(drawTime);

}

}

}

/**

* 处理绘制线程的状态问题

*

* @return true:不结束继续绘制 false:结束且不绘制

*/

private boolean processDrawThreadState() {

//处理没有运行 或者 Holder 为 null 的情况

while (mSurface == null || !mRunning) {

if (mActive) {

mActive = false;

notify(); //唤醒

}

if (mQuit)

return false;

try {

wait(); //等待

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//其他情况肯定是活动状态

if (!mActive) {

mActive = true;

notify(); //唤醒

}

return true;

}

/**

* 处理画布与绘制过程 要注意一定要保证是同步锁中才能执行 否则会出现

*

* @param mCanvas 画布

*/

private void processDrawCanvas(Canvas mCanvas) {

try {

mCanvas = mSurface.lockCanvas(); //加锁画布

if (mCanvas != null) { //防空保护

//清屏操作

mCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

drawSurface(mCanvas); //真正开始画 SurfaceView 的地方

}

}catch (Exception ignored){

}finally {

if(mCanvas != null){

mSurface.unlockCanvasAndPost(mCanvas); //释放canvas锁,并显示视图

}

}

}

/**

* 真正的绘制 SurfaceView

*

* @param canvas 画布

*/

private void drawSurface(Canvas canvas) {

//防空保护

if (mWidth == 0 || mHeight == 0) {

return;

}

//如果前一次绘制对象不为空 且 当前绘制者有透明效果的话 绘制前一次的对象即可

if (mPreDrawer != null && curDrawerAlpha < 1f) {

mPreDrawer.setViewSize(mWidth, mHeight);

mPreDrawer.drawBgAndBubble(canvas, 1f - curDrawerAlpha);

}

//直到当前绘制完全不透明时将上一次绘制的置空

if (curDrawerAlpha < 1f) {

curDrawerAlpha += 0.5f;

if (curDrawerAlpha > 1f) {

curDrawerAlpha = 1f;

mPreDrawer = null;

}

}

//如果当前有绘制对象 直接绘制即可 先设置绘制宽高再绘制气泡

if (mCurDrawer != null) {

mCurDrawer.setViewSize(mWidth, mHeight);

mCurDrawer.drawBgAndBubble(canvas, curDrawerAlpha);

}

}

/**

* 处理线程需要的睡眠时间

* View通过刷新来重绘视图,在一些需要频繁刷新或执行大量逻辑操作时,超过16ms就会导致明显卡顿

*

* @param drawTime 绘制时间

*/

private void processDrawThreadSleep(long drawTime) {

//需要睡眠时间

final long needSleepTime = 16 - drawTime;

if (needSleepTime > 0) {

try {

Thread.sleep(needSleepTime);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

知道看这种代码很枯燥,但不能急。首先这里有三种状态:正在绘制、活动、退出。其中活动是一种中间状态,指既没有活动又没有被销毁。在回调类中需要根据这种状态进行绘制线程的控制。

那就来看回调方法:

/*========== Surface 回调方法 需要加同步锁 防止阻塞 START==========*/

@Override

public void surfaceCreated(SurfaceHolder holder) {

synchronized (mDrawThread) {

mDrawThread.mSurface = holder;

mDrawThread.notify(); //唤醒

}

}

@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override

public void surfaceDestroyed(SurfaceHolder holder) {

synchronized (mDrawThread) {

mDrawThread.mSurface = holder;

mDrawThread.notify(); //唤醒

while (mDrawThread.mActive) {

try {

mDrawThread.wait(); //等待

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

holder.removeCallback(this);

}

/*========== Surface 回调方法 需要加同步锁 防止阻塞 END==========*/

可以看到,在销毁的时候绘制线程是在等待状态。

然后就是一些生命周期相关方法了,也很简单,就是设置相关状态:

/*========== 处理与 Activity 生命周期相关方法 需要加同步锁 防止阻塞 START==========*/

public void onDrawResume() {

synchronized (mDrawThread) {

mDrawThread.mRunning = true; //运行状态

mDrawThread.notify(); //唤醒线程

}

}

public void onDrawPause() {

synchronized (mDrawThread) {

mDrawThread.mRunning = false; //不运行状态

mDrawThread.notify(); //唤醒线程

}

}

public void onDrawDestroy() {

synchronized (mDrawThread) {

mDrawThread.mQuit = true; //退出状态

mDrawThread.notify(); //唤醒线程

}

}

/*========== 处理与 Activity 生命周期相关方法 需要加同步锁 防止阻塞 END==========*/

最后就是提供方法,给这个自定义的 SurfaceView 设置中间绘制者对象了:

/**

* 设置绘制者

*

* @param bubbleDrawer 气泡绘制

*/

public void setDrawer(BubbleDrawer bubbleDrawer) {

//防空保护

if (bubbleDrawer == null) {

return;

}

curDrawerAlpha = 0f; //完全透明

//如果当前有正在绘制的对象 直接设置为前一次绘制对象

if (this.mCurDrawer != null) {

this.mPreDrawer = mCurDrawer;

}

//当前绘制对象 为设置的对象

this.mCurDrawer = bubbleDrawer;

}

到此,自定义 FloatBubbleView 就完成了,代码很长,建议直接看文末的源码。

看结果

好了, 现在只要在 Activity 中这样:

/**

* 初始化Data

*/

private void initData() {

//设置气泡绘制者

BubbleDrawer bubbleDrawer = new BubbleDrawer(this);

//设置渐变背景 如果不需要渐变 设置相同颜色即可

bubbleDrawer.setBackgroundGradient(new int[]{0xffffffff, 0xffffffff});

//给SurfaceView设置一个绘制者

mDWView.setDrawer(bubbleDrawer);

}

这样就大功告成了!效果图再贴一下吧,颜色大小位置都可以定义:

后话

虽然效果实现了,但是我并没有将设置气泡的方法暴露出来,只写死在 BubbleDrawer 中:

if (mBubbles.size() == 0) {

mBubbles.add(new CircleBubble(0.20f * width, -0.30f * width, 0.06f * width, 0.022f * width, 0.56f * width,0.0150f, 0x56ffc7c7));

//...

}

开始我确实抽取了方法,提供给 Activity ,结果发现 Activity 中的代码太难看。另一方面因为 SurfaceView 消耗资源太多,我们应该不会在主要界面大量使用它,所以我觉得写死就够了,必要的时候动一动写死的数据就行了。

还有一点就是,虽然效果很好看,但是确实消耗资源很大,有时候会很卡,不知道还有没有可以优化的地方,建议只在简单的页面,比如关于软件的页面用这样的效果,其他的主页面还是算了吧。

参考资料

项目源码

android 呼吸气泡动画,Android:会呼吸的悬浮气泡相关推荐

  1. android 吐泡泡动画,android仿摩拜贴纸碰撞|气泡碰撞

    转载请注明出处 准备 气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,,等等一系列因素,想要在android 用view描述现实世界中的气泡实在是难度很大.网上查找资料 ...

  2. android红心点赞动画,Android控件实现直播App特效之点赞飘心动画

    现在市面上直播类的应用可以说是一抓一大把,随随便便就以什么主题来开发个直播App,说白了就想在这领域分杯羹.在使用这些应用过程中其实不难发现,在所有的直播界面,少不了的就是各种打赏.各种点赞.今天自己 ...

  3. android红心点赞动画,Android控件FlowLikeView实现点赞动画

    现在市面上直播类的应用可以说是一抓一大把,随随便便就以什么主题来开发个直播App,说白了就想在这领域分杯羹.在使用这些应用过程中其实不难发现,在所有的直播界面,少不了的就是各种打赏.各种点赞.今天自己 ...

  4. android红心点赞动画,Android控件实现直播App点赞飘心动画

    现在市面上直播类的应用可以说是一抓一大把,随随便便就以什么主题来开发个直播App,说白了就想在这领域分杯羹.在使用这些应用过程中其实不难发现,在所有的直播界面,少不了的就是各种打赏.各种点赞.今天自己 ...

  5. android 图片查看动画,Android 共享动画实现点击列表图片跳转查看大图页面

    主要内容使用系统提供的 API 实现共享动画 在实现过程中遇到的问题图片点击和关闭之后会出现短暂的黑屏问题实现的动画效果如下: 共享动画.gif 具体实现这个效果是在两个页面之间的切换动画,既然是两个 ...

  6. Android实现蝴蝶动画,Android中的动画具体解释系列——飞舞的蝴蝶

    这一篇来使用逐帧动画和补间动画来实现一个小样例,首先我们来看看Android中的补间动画. Android中使用Animation代表抽象的动画类,该类包含以下几个子类: AlphaAnimation ...

  7. android 上下扫描动画,Android扫描雷达动画

    很简单的一个组合动画,用好基本动画啥子效果都不怕 老规矩先上图 效果图.gif ok 来 既然往下翻那就看看如何实现的吧 首先效果分为两部分 第一部分中间指针(其实这里就是一张图片) 第二部分就是波纹 ...

  8. android 缩放透明动画,Android旋转、平移、缩放和透明度渐变的补间动画

    android实现旋转.平移.缩放和透明度渐变的补间动画,具体实现如下: 1.在新建项目的res目录中,创建一个名为anim的目录,并在该目录中创建实现旋转.平移.缩放和透明度渐变的动画资源文件. 透 ...

  9. android 指示器平移动画,Android实现带指示器的自动轮播式ViewPager

    前言 最近在做项目的时候,有个需求就是实现自动轮播式的ViewPager,最直观的例子就是知乎日报顶部的ViewPager,它内部有着好几个子view,每个一段时间便自动滑动到下一个item view ...

最新文章

  1. HTML5 本地文件操作之FileSystemAPI整理(二)
  2. 网站优化之各个页面的关键词密度的把控
  3. python ctypes库5_如何传递一个字符串数组
  4. selenium | TypeError:object of type ‘WebElement’ has no len()
  5. python sql 日期查询_Python--flask使用 SQLAlchemy查询数据库最近时间段或之前的数据...
  6. jquery ajax post请求连续多个问号特殊数据异常问题
  7. Auto Highlight for Mac(Safari文本自动高亮插件)
  8. 智力与体力的人种矛盾
  9. python报错: list object has no attribute shape的解决
  10. 开发QQ桌球瞄准器(5):使用注册表保存配置
  11. ccf有趣的数java_CCF CSP 有趣的数
  12. 计算机工资表2017,薪级工资对照表2017年最新
  13. 计算机网络 (头歌平台)实验二
  14. Vue+ElementUI table表格分页
  15. 离散数学-集合-笛卡尔积-07
  16. 田径运动会成绩管理系统
  17. 专业学习与职业发展之我见(二)
  18. Android -- 传感器使用示例, 用方向传感器做指南针
  19. 怎样创建网页快捷方式,用非默认浏览器打开该网页
  20. 穿越火线 raquo; 自动准备挂机刷分器V3.4 6月18号(支持永久跟新)

热门文章

  1. python爬取城市公交线路及公交站点坐标
  2. linux 无线网卡移植,移植wifi无线网卡到arm linux上全过程
  3. N76E003,C51开发不编译
  4. 数字图像处理学习笔记4:图像增强之空间滤波2(一阶微分锐化滤波(梯度),二阶微分锐化(拉普拉斯),非锐化掩蔽)
  5. 文网文是什么?与ICP和EDI许可证有什么区别?
  6. 零基础学ios开发培训要培训多久
  7. @Autowired的原理
  8. 汉王科技持续走下坡路,发展寻求突破
  9. Spring @Conditional相关
  10. 青蛙变态跳+python