JetPack-Compose - Flutter 动态UI?

image.pngimage.png
enenenen.gif222ddd.gif
在这里插入图片描述

一、Flutter-初遇

  • 2018-06月左右入坑Flutter,于是拿出美团和痘印等好看的界面感受了一波Flutter UI和绘制等写了三天的Demo也感受到了Flutter强大,当时匆匆忙忙就写了相关Demo上传了Github。不知不觉Github很多Star很开心,接着决定进行录制基础教学视频,在B站也收到了很多感谢私信、技术交流,记得2018年素未谋面的大哥因为我的热心无缘无故送我2018款MacBook Pro-在他的再三坚持下。带着这份感动和感激我也开始写了很多提供初学者一起学习的文章和依赖库。

Flutter开源库和文章

flutter_excle Flutter插件生成Excle文件    -     -   CSDN

Flutter3D玩的开心

flutter_time_axis时间轴插件    -   GitHub

flutter_pickter_plugin支持图片和文件视频的等的选择    -     -   GitHub

Flutter时间日期格式化等操作(一个月的最后一天日期,时间段内所有日期…)插件    -  

WGS84与大地2000坐标转换(Java,C#,Dart)Flutter发布了支持库

当然还有很多在CSDN


B站教学链接

               image.png

Android自定义绘制相关文章

Android自定义-艺术在于绘制

Android-自定义-曲线折线图表填充渐变,手势滑动,手势缩放等

Android自定义-任意区域可点击的折线图

Android自定义-手势缩放折线图

Android自定义-手势滑动缩放渐变填充曲线折线图表


Android-自定义可伸展的ViewGroup

Android自定义-曲线渐变填充

Jetpack-Compose

JetPack-Compose - 自定义绘制
当然还有很多在CSDN

IOS相关文章

SwiftUI学习笔记【基础UI

SwiftUI学习笔记-【列表】

SwiftUI学习笔记【path绘制】

SwiftUI学习笔记【Sqlite】

SwiftUI学习笔记【XML解析】

当然还有很多在CSDN

只要在编程的路上文章不会停下来...希望给你带来方便,你的点赞是我就是莫大的开心

二、Flutter UI带来的惊喜

  • Flutter带来的惊喜不仅点点:有状态的热重载可以快速重新渲染界面、富有表现力、极其灵活的UI。自身渲染引擎,脱离了原生基本组件不在作为映射,相对于Uni和RN等框架在性能方面占据绝对优势。滚动,导航,图标和字体,可以在iOS和Android上提供完整的原生性能体验,且支持Web,Desktop,Embedded…等多端。18年接触了Flutter就感受了一下UI的可塑性和创造性,完全不输原生且更加灵活多变,当时写的页面当然有很多这里列举其三:自定义裁剪绘制让应用的UI图破了天花板,而手势动画的加入更让你的应用,赏心悦目、超凡脱俗。那我们的Compose是否可以呢?今天主要的内容是探究Compose在UI上所表现的能力。

                image.png

如果你对Flutter绘制和裁剪或者其他任何疑难杂症我想这个男人张风捷特烈一定能给你说的清清楚楚,透透彻彻

三、Compose UI-强大而简单

  • 前面两章我们学会了基本UI的编写自定义绘制裁剪的应用。这节课我们将结合基本UI以及自定义加简单的手势动画来创造花里胡哨的界面,当然这并不代表美,目的在于学习Compose的可创造性,这节会穿插自定义曲线的讲解,好像几乎都离不开曲线。先看看下面的效果?

               动画Compose.gif

UI界面不仅是简单的官方组件排布,好的UI更需要精心的自定义,和动态的交互性。上面图虽然不代表美,但包含了很多交互和动态效果,我们从组件的交互动态可能性作为出发点进行探讨。当然你觉得什么样的UI最难可以发表你的看法,有时间我定还你一个朗朗乾坤。技术交流群QQ裙730772561

1、旋转、缩放、背景模糊

  • 组件的旋转缩放如果用Flutter来实现如何做呢?Transform或者RotationTransition这样的容器部件进行包裹,且需要设置动画控制AnimationController等。那Compose我们如何来旋转和缩放组件呢?Modifier不仅解决了参数过多可以统一配置问题,而且提供了极其强大的裁剪变换指针手势装饰等方法,大大的提高了便捷性和创造性。
1、Modifier.rotate(degrees: Float)
  • 任意的组件都可以通过Modifier进行旋转[Modifier.rotate(degrees: Float)]平移[Modifier.offset(x: Dp = 0.dp, y: Dp = 0.dp)]缩放[Modifier.scale(scale: Float)]、等变换。

                [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OIlFcIoa-1616049802373)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/04e7f808b1bf4522b7e12281389df7db~tplv-k3u1fbpfcp-watermark.image)]

观察这部分动态特效
1、中间图片的旋转。
2、圆形图片外圆弧缩放。
3、背景的模糊和缩放。
4、运动的曲线。

2、动画创建和使用
  • 当我们鼠标点击界面时候中间的图片进行旋转了任意角度。我们已经知道旋转通过Modifier.rotate来设置,只需要在点击屏幕时候执行动画继续性更新角度值即可。所以简单动画创建需要掌握一下。
    动画储值器
    我们可以点击源码进行观看,动画执行过程中的值储存在mutableStateOf所以具有状态且发生改变时状态向下可以刷新UI,可以查看之前章节或者官网

image.png

//进行动画过程数值储存器
val animatedDegree = remember {
    Animatable(0f) }

执行动画
通过.animateTo(targetValue: T,animationSpec: AnimationSpec = defaultSpringSpec…)开始执行动画,可以设置动画目标值和动画规格速度等…

animatedOffset.animateTo(targetValue,animationSpec = spring(stiffness = StiffnessLow))

fun Modifier.pointerInput(key1: Any?, block: suspend PointerInputScope.() -> Unit):以处理修改后的元素区域内的指针输入。pointerInput可以调用PointerInputScope-awaitPointerEventScope来安装一个指针输入处理程序,该处理程序可以使waitPointerEventScope-awaitPointerEvent接收使用指针输入事件。 可以通过PointerInputScope.AwaitPointerEventScope上的扩展功能定义为执行更高级别的手势检测。 我们通过awaitPointerEventScope获取屏幕点击坐标作为我们的角度。

 @Compose
 fun LoginPage(){
   
 //1.进行动画过程数值储存器
 val animatedDegree = remember {
    Animatable(0f) }
 Box() {
   
        //底部模糊大背景
        Image(
            //获取模糊bitmap
            bitmap = BitmapBlur.doBlur(getBitmap(resource =R.drawable.head_god).asAndroidBitmap(),
            animatedRound.value.toInt()+20,false).asImageBitmap(),
            contentDescription = "",
            contentScale = ContentScale.FillHeight,
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
                .scale(animatedScale.value, animatedScale.value),
        )
        //圆形图片和圆形弧圈
        Column(verticalArrangement = Arrangement.Center, modifier = Modifier.pointerInput(Unit) {
   
            coroutineScope {
   
                while (true) {
   
                    //2.通过awaitPointerEventScope来处理指针事件
                    val offset = awaitPointerEventScope {
   
                        //获取第一次按下的位置坐标
                        awaitFirstDown().position

                    }
                    //携程
                    launch {
   
                            //3.设置animated的目标值为按下的屏幕坐标系内的x值,且设置动画格式为比较平缓不生硬。开始执行动画。
                            animatedDegree.animateTo(
                            offset.x,
                            animationSpec = spring(stiffness = StiffnessLow)
                        )

                    }

                }
            }
        }) {
   
          Image( bitmap = getBitmap(R.drawable.head_god),
                        contentDescription = "w",
                        contentScale = ContentScale.FillBounds,
                        modifier = Modifier
                            .height(80.dp)
                            .width(80.dp)
                            .background(color = Color(0XFF0DBEBF), shape = CircleShape)
                            .padding(3.dp)
                            .clip(
                                CircleShape
                            )
                            .shadow(elevation = 150.dp, clip = true)
                            .rotate(//4.设置角度为初始化到目标x的动画值跟新UI
                                animatedDegreen.value
                            )
                    )
        }
   }     

compose旋转缩放.gif

3、半透明圆环缩放颜色动画
  • 同样的通过val animatedScale = remember { Animatable(1f) }来创建缩数值储存器,通过点击时候执行动画去跟新图片大小颜色也是同样设置val animatedColor = remember { Animatable(Color(206, 199, 250, 121)) }点击时候去设置目标缩放值和颜色执行.animalTo即可执行连续动画且向上去跟新储存状态再下发到跟新UI。圆形裁剪相关的看之前文章
  Box(contentAlignment = Alignment.Center,
      modifier = Modifier.padding(0.dp).clip(CicleImageShape())
                        .background(animatedColor.value)
                        .width((130 * animatedScale.value).dp)
                        .height((130 * animatedScale.value).dp)
                ) {
   
                    Image(
                        bitmap = getBitmap(R.drawable.head_god),
                        contentDescription = "",
                        contentScale = ContentScale.FillBounds,
                        modifier = Modifier
                            .height(80.dp)
                            .width(80.dp)
                            .background(color = Color(0XFF0DBEBF), shape = CircleShape)
                            .padding(3.dp)
                            .clip(
                                CircleShape
                            )
                            .shadow(elevation = 150.dp, clip = true)
                            .rotate(
                                animatedOffset.value
                            )
                    )
                }
4、图片高斯模糊的获取

Bitmap和ImageBitmap可以相互转换:Bitmap.asImageBitmap(): ImageBitmapfun ImageBitmap.asAndroidBitmap(): Bitmap

object BitmapBlur {
   
    fun doBlur(sentBitmap: Bitmap, radiu: Int = 1, canReuseInBitmap: Boolean): Bitmap {
   
        var radius: Int = radiu
        val bitmap: Bitmap = if (canReuseInBitmap) {
   
            sentBitmap
        } else {
   
            sentBitmap.copy(sentBitmap.config, true)
        }
        if (radius < 1) {
   
            radius = 0
        }
        val w = bitmap.width
        val h = bitmap.height
        val pix = IntArray(w * h)
        bitmap.getPixels(pix, 0, w, 0, 0, w, h)
        val wm = w - 1
        val hm = h - 1
        val wh = w * h
        val div = radius + radius + 1
        val r = IntArray(wh)
        val g = IntArray(wh)
        val b = IntArray(wh)
        var rsum: Int
        var gsum: Int
        var bsum: Int
        var x: Int
        var y: Int
        var i: Int
        var p: Int
        var yp: Int
        var yi: Int
        var yw: Int
        val vmin = IntArray(Math.max(w, h))
        var divsum = div + 1 shr 1
        divsum *= divsum
        val dv = IntArray(256 * divsum)
        i = 0
        while (i < 256 * divsum) {
   
            dv[i] = i / divsum
            i++
        }
        yi = 0
        yw = yi
        val stack = Array(div) {
   
            IntArray(
                3
            )
        }
        var stackpointer: Int
        var stackstart: Int
        var sir: IntArray
        var rbs: Int
        val r1 = radius + 1
        var routsum: Int
        var goutsum: Int
        var boutsum: Int
        var rinsum: Int
        var ginsum: Int
        var binsum: Int
        y = 0
        while (y < h) {
   
            bsum = 0
            gsum = bsum
            rsum = gsum
            boutsum = rsum
            goutsum = boutsum
            routsum = goutsum
            binsum = routsum
            ginsum = binsum
            rinsum = ginsum
            i = -radius
            while (i <= radius) {
   
                p = pix[yi + Math.min(wm, Math.max(i, 0))]
                sir = stack[i + radius]
                sir[0] = p and 0xff0000 shr 16
                sir[1] = p and 0x00ff00 shr 8
                sir[2] = p and 0x0000ff
                rbs = r1 - Math.abs(i)
                rsum += sir[0] * rbs
                gsum += sir[1] * rbs
                bsum += sir[2] * rbs
                if (i > 0) {
   
                    rinsum += sir[0]
                    ginsum += sir[1]
                    binsum += sir[2]
                } else {
   
                    routsum += sir[0]
                    goutsum += sir[1]
                    boutsum += sir[2]
                }
                i++
            }
            stackpointer = radius
            x = 0
            while (x < w) {
   
                r[yi] = dv[rsum]
                g[yi] = dv[gsum]
                b[yi] = dv[bsum]
                rsum -= routsum
                gsum -= goutsum
                bsum -= boutsum
                stackstart = stackpointer - radius + div
                sir = stack[stackstart % div]
                routsum -= sir[0]
                goutsum -= sir[1]
                boutsum -= sir[2
  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值