利用SurfaceView实现下雨与下雪动画效果详解(Kotlin语法)

前言

最近打算做一波东西巩固一下自己近期所学所得。话不多说,先看一下最终完成的效果图:


下雨.gif

这里比较懒……第二个图片中还是降雨……不过这不是关键点……

下雪.gif

录制的mp4,转成了gif。第一个gif设置了帧率,所以看起来可能掉帧比较严重,但是实际上并不会,因为这里我也注意了1s要绘制60帧的问题。阅读本文需要一些基本的View知识和会一些基础Kotlin语法。说实话,就知识点来说,跟Kotlin是没多大关系的,只要懂基本的语法就可以了。

理清思路

在动手前先要理一下思路,从以下几个方面来分析一下该采用什么方案来实现这个效果:

  • 工作线程:首先要想到的是:这个下雨的效果需要通过不停的绘制来实现,如果在主线程做这个操作,很有可能会阻塞主线程,导致ANR或者异常卡顿。所以需要一个能在子线程进行绘制的View,毫无疑问SurfaceView可以满足这个需求。
  • 如何实现:分析一下一颗雨滴的实现。首先,简单的效果其实可以用画线的方式代替。并不是每个人都有写轮眼,动态视力那么好的,一旦动起来谁还知道他是条线还是雨滴……当然了,Canvas绘制的API有很多,并不一定非要用这种方式来实现。所以在在设计类的时候我们将draw的方法设置成可以让子类复写就可以了,你不满意我的实现?没问题,我给你改的自由~
  • 下落的实现:让雨滴动起来,有两种方式,一种是纯按坐标来绘制,另外一种是利用属性动画,自己重写估值器,动态改变y值。最终我还是采用了前一种方案,后一种属性动画的方案我为什么放弃了呢?原因是:这里的绘制的方式是靠外部不断的触发绘制事件来实现动态绘制的,很显然第一种方式更加符合这里的情况。

以上就是我初期的一些关于实现的思考了,接下来是代码实现分析。

代码实现分析

先放代码结构图:

代码结构

WeatherShape所有天气的父类,Rain和Snow是两个具体实现类。

看一下父类的代码:

package com.xiasuhuei321.gank_kotlin.customview.weather

import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
import com.xiasuhuei321.gank_kotlin.context
import com.xiasuhuei321.gank_kotlin.extension.getScreenWidth
import java.util.*

/**
 * Created by xiasuhuei321 on 2017/9/5.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 *
 * desc: All shape's parent class.It describes a shape will have
 * what feature.It's draw flows are:
 * 1.Outside the class init some value such as the start and the
 * end point.
 * 2.Invoke draw(Canvas) method, in this method, there are still
 * two flows:
 * 1) Get random value to init paint, this will affect the shape
 * draw style.
 * 2) When the shape is not used, invoke init method, and when it
 * is not used invoke drawWhenInUse(Canvas) method. It should be
 * override by user and to implement draw itself.
 *
 */
abstract class WeatherShape(val start: PointF, val end: PointF) {
 open var TAG = "WeatherShape"

 /**
  * 是否是正在被使用的状态
  */
 var isInUse = false

 /**
  * 是否是随机刷新的Shape
  */
 var isRandom = false

 /**
  * 下落的速度,特指垂直方向,子类可以实现自己水平方向的速度
  */
 var speed = 0.05f

 /**
  * shape的宽度
  */
 var width = 5f

 var shapeAlpha = 100

 var paint = Paint().apply {
  strokeWidth = width
  isAntiAlias = true
  alpha = alpha
 }

 // 总共下落的时间
 var lastTime = 0L
 // 原始x坐标位置
 var originX = 0f

 /**
  * 根据自己的规则计算加速度,如果是匀速直接 return 0
  */
 abstract fun getAcceleration(): Float

 /**
  * 绘制自身,这里在Shape是非使用的时候进行一些初始化操作
  */
 open fun draw(canvas: Canvas) {
  if (!isInUse) {
   lastTime += randomPre()
   initStyle()
   isInUse = true
  } else {
   drawWhenInUse(canvas)
  }
 }

 /**
  * Shape在使用的时候调用此方法
  */
 abstract fun drawWhenInUse(canvas: Canvas)

 /**
  * 初始化Shape风格
  */
 open fun initStyle() {
  val random = Random()
  // 获取随机透明度
  shapeAlpha = random.nextInt(155) + 50
  // 获得起点x偏移
  val translateX = random.nextInt(10).toFloat() + 5
  if (!isRandom) {
   start.x = translateX + originX
   end.x = translateX + originX
  } else {
   // 如果是随机Shape,将x坐标随机范围扩大到整个屏幕的宽度
   val randomWidth = random.nextInt(context.getScreenWidth())
   start.x = randomWidth.toFloat()
   end.x = randomWidth.toFloat()
  }
  speed = randomSpeed(random)
  // 初始化length的工作留给之后对应的子类去实现
  // 初始化color也留给子类去实现
  paint.apply {
   alpha = shapeAlpha
   strokeWidth = width
   isAntiAlias = true
  }
  // 如果有什么想要做的,刚好可以在追加上完成,就使用这个函数
  wtc(random)
 }

 /**
  * Empty body, this will be invoke in initStyle
  * method.If current initStyle method can satisfy your need
  * but you still add something, by override this method
  * will be a good idea to solve the problem.
  */
 open fun wtc(random:Random): Unit {

 }

 abstract fun randomSpeed(random: Random): Float

 /**
  * 获取一个随机的提前量,让shape在竖屏上有一个初始的偏移
  */
 open fun randomPre(): Long {
  val random = Random()
  val pre = random.nextInt(1000).toLong()
  return pre
 }
}

说起这个代码,恩,还是经历过一番重构的……周六去找同学玩的路上顺便重构了一下,将一些可以放到基类中的操作都抽取到了基类中。这样虽然灵活不足,但是子类可以很方便的通过继承实现一个需要类似功能的东西,就比如这里的下雨和下雪。顺便吐槽一下……我注释的风格不太好,中英混搭……如果你仔细观察,可以看到gif中的雨点或者雪花形态可能都有一些些的不一样,是的,每一滴雨和雪花,都经过了一些随机的转变。

里面比较重要的两个属性是isInUse和isRandom,本来想用一个容器来作为Shape的管理类,统一管理,但是这样肯定会让使用和复用的流程更加复杂。最后还是决定用简单一点的方法,Shape内部保存一个使用状态和是否是随机的。isRandoma表示这个Shape是否是随机的,随机在目前的代码中会体现在Shape的x坐标上。如果随机标识是true,那么x坐标将是0 ~ ScreenWidth中的任意值。那么不是随机的呢?在我的实现中,同一类Shape将会被分为两类,一类常量组。会拥有相对固定的x值,但是也会有10~15px的随机偏移。另一类就是随机组,x值全屏自己随机,这样就尽量让屏幕各处都有雨滴(雪花)但会有疏密之别。initStyle就是这一随机的过程,有兴趣可以看看实现~

start和end是Shape的左上角点和右下角点,如果你对于Cavans的api有了解,就应该知道通过对start和end的转换和计算,可以绘制出大部分的形状。

接下来看一下具体实现的Snow类:

package com.xiasuhuei321.gank_kotlin.customview.weather

import android.graphics.*
import com.xiasuhuei321.gank_kotlin.context
import com.xiasuhuei321.gank_kotlin.extension.getScreenHeight
import java.util.*

/**
 * Created by xiasuhuei321 on 2017/9/5.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */
class Snow(start: PointF, end: PointF) : WeatherShape(start, end) {

 /**
  * 圆心,用户可以改变这个值
  */
 var center = calcCenter()

 /**
  * 半径
  */
 var radius = 10f

 override fun getAcceleration(): Float {
  return 0f
 }

 override fun drawWhenInUse(canvas: Canvas) {
  // 通过圆心与半径确定圆的位置及大小
  val distance = speed * lastTime
  center.y += distance
  start.y += distance
  end.y += distance
  lastTime += 16
  canvas.drawCircle(center.x, center.y, radius, paint)
  if (end.y > context.getScreenHeight()) clear()
 }

 fun calcCenter(): PointF {
  val center = PointF(0f, 0f)
  center.x = (start.x + end.x) / 2f
  center.y = (start.y + end.y) / 2f
  return center
 }

 override fun randomSpeed(random: Random): Float {
  // 获取随机速度0.005 ~ 0.01
  return (random.nextInt(5) + 5) / 1000f
 }

 override fun wtc(random: Random) {
  // 设置颜色渐变
  val shader = RadialGradient(center.x, center.y, radius,
    Color.parseColor("#FFFFFF"), Color.parseColor("#D1D1D1"),
    Shader.TileMode.CLAMP)
  // 外部设置的起始点其实并不对,先计算出半径
  radius = random.nextInt(10) + 15f
  // 根据半径计算start end
  end.x = start.x + radius
  end.y = start.y + radius
  // 计算圆心
  calcCenter()

  paint.apply {
   setShader(shader)
  }
 }

 fun clear() {
  isInUse = false
  lastTime = 0
  start.y = -radius * 2
  end.y = 0f

  center = calcCenter()
 }
}

这个类只要理解了圆心的计算和绘制,基本也就没什么东西了。首先排除干扰项,getAcceleration这玩意在设计之初是用来通过加速度计算路程的,后来发现……算了,还是匀速吧……于是都return 0f了。这里wtc()函数和drawWhenInUse可能会看的你一脸懵逼,什么函数名,drawWhenInUse倒是见名知意,这wtc()是什么玩意?这里wtc是相当于一种追加初始化,完全状态的函数名应该是wantToChange() 。这些个函数调用流程是这样的:


流程图

其中draw(canvas)是父类的方法,对供外部调用的方法,在isInUse标识位为false时对Shape进行初始化操作,具体的就是调用initStyle()方法,而wtc()则会在initStyle()方法的最后调用。如果你有什么想要追加的初始化,可以通过这个函数实现。而drawWhenInUse(canvas)方法则是需要实现动态绘制的函数了。我这里就是在wtc()函数中进行了一些初始化操作,并且根据圆的特性重新计算了start、end和圆心。

接下来,就看看我们到底是怎么把这些充满个性(口胡)的雪绘制到屏幕上:

package com.xiasuhuei321.gank_kotlin.customview.weather

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.util.AttributeSet
import android.view.SurfaceHolder
import android.view.SurfaceView
import com.xiasuhuei321.gank_kotlin.extension.LogUtil
import java.lang.Exception

/**
 * Created by xiasuhuei321 on 2017/9/5.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */
class WeatherView(context: Context, attributeSet: AttributeSet?, defaultStyle: Int) :
  SurfaceView(context, attributeSet, defaultStyle), SurfaceHolder.Callback {
 private val TAG = "WeatherView"

 constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)

 constructor(context: Context) : this(context, null, 0)

 // 低级并发,Kotlin中支持的不是很好,所以用一下黑科技
 val lock = Object()
 var type = Weather.RAIN
 var weatherShapePool = WeatherShapePool()

 @Volatile var canRun = false
 @Volatile var threadQuit = false

 var thread = Thread {
  while (!threadQuit) {
   if (!canRun) {
    synchronized(lock) {
     try {
      LogUtil.i(TAG, "条件尚不充足,阻塞中...")
      lock.wait()
     } catch (e: Exception) {
     }
    }
   }
   val startTime = System.currentTimeMillis()
   try {
    // 正式开始表演
    val canvas = holder.lockCanvas()
    if (canvas != null) {
     canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
     draw(canvas, type, startTime)
    }
    holder.unlockCanvasAndPost(canvas)
    val drawTime = System.currentTimeMillis() - startTime
    // 平均16ms一帧才能有顺畅的感觉
    if (drawTime < 16) {
     Thread.sleep(16 - drawTime)
    }
   } catch (e: Exception) {
//    e.printStackTrace()
   }
  }
 }.apply { name = "WeatherThread" }

 override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
  // surface发生了变化
//  canRun = true

 }

 override fun surfaceDestroyed(holder: SurfaceHolder?) {
  // 在这里释放资源
  canRun = false
  LogUtil.i(TAG, "surfaceDestroyed")
 }

 override fun surfaceCreated(holder: SurfaceHolder?) {
  threadQuit = false
  canRun = true
  try {
   // 如果没有执行wait的话,这里notify会抛异常
   synchronized(lock) {
    lock.notify()
   }
  } catch (e: Exception) {
   e.printStackTrace()
  }
 }

 init {
  LogUtil.i(TAG, "init开始")
  holder.addCallback(this)
  holder.setFormat(PixelFormat.RGBA_8888)
//  initData()
  setZOrderOnTop(true)
//  setZOrderMediaOverlay(true)
  thread.start()
 }

 private fun draw(canvas: Canvas, type: Weather, startTime: Long) {
  // type什么的先放一边,先实现一个
  weatherShapePool.drawSnow(canvas)
 }

 enum class Weather {
  RAIN,
  SNOW
 }

 fun onDestroy() {
  threadQuit = true
  canRun = true
  try {
   synchronized(lock) {
    lock.notify()
   }
  } catch (e: Exception) {
  }
 }
}

init{}是kotlin中提供给我们用于初始化的代码块,在init进行了一些初始化操作并让线程start了。看一下线程中执行的代码,首先会判断一个叫做canRun的标识,这个标识会在surface被创建的时候置为true,否则将会通过一个对象让这个线程等待。而在surface被创建后,则会调用notify方法让线程重新开始工作。之后是进行绘制的工作,绘制前后会有一个计时的动作,计算时间是否小于16ms,如果不足,则让thread sleep 补足插值。因为16ms一帧的绘制速度就足够了,不需要绘制太快浪费资源。

这里可以看到我创建了一个Java的Object对象,主要是因为Kotlin本身对于一些并发原语支持的并不好。Kotlin中任何对象都是继承与Any,Any并没有wait、notify等方法,所以这里用了黑科技……创建了Java对象……

代码中关键代码绘制调用了WeatherShapePool的drawRain(canvas)方法,最后在看一下这个类:

package com.xiasuhuei321.gank_kotlin.customview.weather

import android.graphics.Canvas
import android.graphics.PointF
import com.xiasuhuei321.gank_kotlin.context
import com.xiasuhuei321.gank_kotlin.extension.getScreenWidth

/**
 * Created by xiasuhuei321 on 2017/9/7.
 * author:luo
 * e-mail:xiasuhuei321@163.com
 */
class WeatherShapePool {
 val constantRain = ArrayList<Rain>()
 val randomRain = ArrayList<Rain>()

 val constantSnow = ArrayList<Snow>()
 val randomSnow = ArrayList<Snow>()

 init {
  // 初始化
  initData()
  initSnow()
 }

 private fun initData() {
  val space = context.getScreenWidth() / 20
  var currentSpace = 0f
  // 将其均匀的分布在屏幕x方向上
  for (i in 0..19) {
   val rain = Rain(PointF(currentSpace, 0f), PointF(currentSpace, 0f))
   rain.originLength = 20f
   rain.originX = currentSpace
   constantRain.add(rain)
   currentSpace += space
  }

  for (j in 0..9) {
   val rain = Rain(PointF(0f, 0f), PointF(0f, 0f))
   rain.isRandom = true
   rain.originLength = 20f
   randomRain.add(rain)
  }
 }

 fun drawRain(canvas: Canvas) {
  for (r in constantRain) {
   r.draw(canvas)
  }
  for (r in randomRain) {
   r.draw(canvas)
  }
 }

 private fun initSnow(){
  val space = context.getScreenWidth() / 20
  var currentSpace = 0f
  // 将其均匀的分布在屏幕x方向上
  for (i in 0..19) {
   val snow = Snow(PointF(currentSpace, 0f), PointF(currentSpace, 0f))
   snow.originX = currentSpace
   snow.radius = 20f
   constantSnow.add(snow)
   currentSpace += space
  }

  for (j in 0..19) {
   val snow = Snow(PointF(0f, 0f), PointF(0f, 0f))
   snow.isRandom = true
   snow.radius = 20f
   randomSnow.add(snow)
  }
 }

 fun drawSnow(canvas: Canvas){
  for(r in constantSnow){
   r.draw(canvas)
  }

  for (r in randomSnow){
   r.draw(canvas)
  }
 }
}

这个类还是比较简单的,只是一个单纯的容器,至于叫Pool……因为刚开始自己想的是自己管理回收复用之类的,所以起了个名叫Pool,后来感觉这玩意好像不用实现的这么复杂……

总之,这玩意,会者不难,我的代码也非尽善尽美,如果我有任何纰漏或者你有什么好的意见,都可以提出,邮件或者是在文章下评论最佳。

项目地址:https://github.com/ForgetAll/GankKotlin

本地下载:http://xiazai.jb51.net/201709/yuanma/GankKotlin(jb51.net).rar

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • kotlin 官方学习教程之基础语法详解

    kotlin 官方学习教程之基础语法详解 Google 在今天的举行了 I/O 大会,大会主要主要展示内有容 Android O(Android 8.0)系统.Google Assistant 语音助手.Google 智能音箱.人工智能.机器学习.虚拟现实等.作为一个 Android 开发者,我关心的当然是 Android O(Android 8.0)系统了,那么关于 Android O 系统的一个重要消息是全面支持 Kotlin 编程语言,使得 Kotlin 成为了 Android 开发的官方

  • Kotlin 基础语法详细介绍

    Kotlin 基础语法详细介绍 基础语法 定义包名 包名的定义应当在源文件的头部 package my.demo import java.util.* // ... 文件路径和包名并不要求匹配,源文件可以被放置在文件系统任意位置 参考:包 定义函数 函数有两个Int类型参数和Int类型返回值: fun sum(a: Int, b: Int): Int { return a + b } 函数体中只有一个表达式并且作为函数的返回值: fun sum(a: Int, b: Int) = a + b 函

  • Kotlin 基本语法实例详解

    基本语法示例 实例代码: package com.stone.basic.syntax /** * desc : * author: stone * email : aa86799@163.com * time : 27/05/2017 11 01 */ class BasicSyntax { //Function having two Int parameters with Int return type: public fun sum(a: Int, b: Int): Int {//访问修饰

  • Android利用SurfaceView实现下雨的天气动画效果

    首先是最终实现的效果图: 先分析一下雨滴的实现: 每个雨滴其实就是一条线,通过 canvas.drawLine() 绘制 线(雨滴)的长度.宽度.下落速度.透明度以及位置都是在一定范围内随机生成 每 draw 一次然后改变雨滴的位置然后重绘即可实现雨滴的下落效果 分析完了,那么可以直接写一个类直接继承 View ,然后重写 onDraw() 吗?可以看到效果图中的雨滴的下落速度很快,那么意味着每一帧都要调用 onDraw() 一次使其重新绘制一次,假如你的 onDraw() 方法里面的渲染代码稍

  • Kotlin学习第一步 kotlin语法特性

    今年 Google I/O 2017 开发者大会中,Google 宣布正式把 Kotlin 纳入 Android 程序的官方一级开发语言(First-class language),作为Android开发者,当然要逐步熟悉这门语言,第一步就要从语法开始学习. 在这之前,我们需要了解怎么使用Kotlin编写一个Android应用.对于Android Studio 3.0版本,我们在创建工程的时候直接勾选 Include Kotlin support 选项就可以了:对于3.0以前的版本,我们需要安装

  • Kotlin 基础语法实例详解

    Kotlin 基础语法实例详解 包 定义和引入Java一样,在文件开头, 行结束不需要" ; " package com.test.hello import android.os.Bundle 变量 只读变量,val 开头,初始化后不能再赋值,相当于Java的 final 变量 val a: Int = 1 val b = 1 //类型自动推断为Int val c: Int //没有初始化时必须指定类型 c = 1 //初始化 可变变量, var 关键字开头 var x = 10 x

  • 利用SurfaceView实现下雨与下雪动画效果详解(Kotlin语法)

    前言 最近打算做一波东西巩固一下自己近期所学所得.话不多说,先看一下最终完成的效果图: 下雨.gif 这里比较懒--第二个图片中还是降雨--不过这不是关键点-- 下雪.gif 录制的mp4,转成了gif.第一个gif设置了帧率,所以看起来可能掉帧比较严重,但是实际上并不会,因为这里我也注意了1s要绘制60帧的问题.阅读本文需要一些基本的View知识和会一些基础Kotlin语法.说实话,就知识点来说,跟Kotlin是没多大关系的,只要懂基本的语法就可以了. 理清思路 在动手前先要理一下思路,从以下

  • Android Flutter实现五种酷炫文字动画效果详解

    目录 前言 波浪涌动效果 波浪线跳动文字组 彩虹动效 滚动广告牌效果 打字效果 其他效果 自定义效果 总结 前言 偶然逛国外博客,看到了一个介绍文字动画的库,进入 pub 一看,立马就爱上这个动画库了,几乎你能想到的文字动画效果它都有!现在正式给大家安利一下这个库:animated_text_kit.本篇我们介绍几个酷炫的效果,其他的效果大家可以自行查看官网文档使用. 波浪涌动效果 波浪涌动 上面的动画效果只需要下面几行代码,其中loadUntil用于控制波浪最终停留的高度,取值是0-1.0,如

  • Jetpack Compose实现列表和动画效果详解

    目录 创建一个列表消息卡片 可交互的动画效果 创建一个列表消息卡片 到目前为止,我们只有一个消息的卡片,看上去有点单调,所以让我们来改善它,让它拥有多条信息.我们需要创建一个能够显示多条消息的函数.对于这种情况,我们可以使用 Compose 的 LazyColumn 和 LazyRow.这些 Composable 只渲染屏幕上可见的元素,所以它们的设计对于长列表来说很有效果.同时,它们避免了 RecyclerView 与 XML 布局的复杂性. import androidx.compose.f

  • Flutter实现不同缩放动画效果详解

    目录 需求背景 可缩放组件介绍 ScaleTransition SizeTransition AnimatedSize AnimatedBuilder 小结 需求背景 组件缩放可以向着一个方向进行缩放,放大列表中某一个Cell期望它是向后进行放大而非组件中心点开始缩放.具体效果如下图所示: 可缩放组件介绍 ScaleTransition ScaleTransition具体实现如下代码,设置AnimationController控制器若需要增加数值操作可以再增加Animate再调用forward方

  • AngularJS 实现JavaScript 动画效果详解

    AngularJS 应用中实现 JavaScript 动画效果 AngularJS 是一组用于创建单页Web应用的丰富框架,给构建丰富交互地应用带来了所有需要的功能.其中一项主要的特性就是Angular带来了对动画的支持. 我们能够在应用的部分内容当中使用动画来表明一些变化正在发生.在我上一篇文章当中,我讲到了在Angular应用中对CSS动画的支持.在这篇文章当中,我们会看到怎样利用JavaScript脚本在AngularJS应用当中生成动画效果. 在Angular当中,CSS和JavaScr

  • Android实现动画效果详解

    目前Android平台提供了两类动画一类是Tween动画,第二类就是 Frame动画,具体内容介绍请看下文: 一类是Tween动画,就是对场景里的对象不断的进行图像变化来产生动画效果(旋转.平移.放缩和渐变). 第二类就是 Frame动画,即顺序的播放事先做好的图像,与gif图片原理类似. 实现动画有两种方式:一种使用XML文件(文件放在res/anim),一种直接代码搞定  1.透明度控制动画效果alpha <!-- 透明度控制动画效果alpha 浮点型值: fromAlpha 动画起始时透明

  • 微信小程序的动画效果详解

    前言 由于公司计划有变,所以从H5页面改成去小程序写.所以在着手开发小程序.本人也不是什么前端高手,只是一名写后端偶尔写写前端的渣渣.请前端大神们勿喷. 一.什么是微信小程序? 小程序在我的理解中只是高度封装的H5,封装了各种组件.根据官方的说法小程序运行不是在浏览器当中.姑且算是微信的插件吧. 二.小程序不能操纵DOM 小程序不能直接操纵DOM,鼓励的是数据绑定.例如vue.js这种.所以个人而言感觉跟如果习惯了用JQ去操纵DOM的开发者很不习惯.需要一个习惯的过程. 三.小程序不能引用JQ

  • 利用swift实现卡片横向滑动动画效果的方法示例

    本文主要给大家介绍了关于利用swift实现卡片横向滑动动画效果的相关资料,分享出来供大家参考学习,下面来一起看看详细的介绍吧. 根据惯例,首先上效果图: 那天去面试,面试官突然拿出手机点开了一个app,自个在那点了一会,然后问我 这个效果怎么实现,当时一看可以滑动,肯定用scrollView 或者 collectionView实现,就大概的说了下.今天刚好闲下来,就敲一敲这个效果. 先来分析下这个效果: 卡片是横向滚动,并且每个卡片的位置都是保持在屏幕中间的,而且 左右相邻的卡片都露出来一点边

  • iOS 基本动画、关键帧动画、利用缓动函数实现物理动画效果

    iOS基本动画/关键帧动画/利用缓动函数实现物理动画效果 先说下基本动画部分 基本动画部分比较简单, 但能实现的动画效果也很局限 使用方法大致为: #1. 创建原始UI或者画面 #2. 创建CABasicAnimation实例, 并设置keypart/duration/fromValue/toValue #3. 设置动画最终停留的位置 #4. 将配置好的动画添加到layer层中 举个例子, 比如实现一个圆形从上往下移动, 上代码: //设置原始画面 UIView *showView = [[UI

随机推荐