【Kotlin】一个有趣的商品数量加减交互控件(商详页或者购物车商品数量的加减)

前言

自从5月18号那天Google宣布Kotlin成为Android第一开发语言的时候,Kotlin就火了,然后再6月Kotlin就直接挤入编程语言排行前50,好吧,既然Google都这么做了,那我们也是不是应该适当的熟悉熟悉呢?所以,我想,接下来的我的博客应该都会尝试用Kotlin来开发,然后再说说遇到的问题和自己的感受吧。

既然本次开发使用Kotlin的,那么如果不熟悉或者不会在Android studio上用Kotlin开发的,可以先去看看我之前(去年)的博文
【Android】使用Kotlin在Android Studio上开发App

先看看今天我们要实现的效果,自己感受下吧,可能这边效果不是很棒
这里写图片描述

引出BlinChengDemos

然后既然要用Kotlin了,那么我们就新建一个全新的项目吧,然后我想把我之前的所有东西都整合起来变成一个大的Demo,直接共享到github上供大家参考。然后该全新的项目当然是用Kotlin来开发啦~啦啦啦。看看结构目录
这里写图片描述
嗯,大概就是这样的。然后很多东西其实我还没做,所以,先在这边提一下,等慢慢晚上了,我再来介绍。今天的主角可不是这个哦~

GoodAmountView

咱们就来看看我们今天怎么来实现吧,东西相当简单哈~其实真的如果没啥问题,大家应该都可以写出来,我今天提的主要是这个创意哈。其实开发那么久,比如我,商城类App写了又写~然后,到今天,发现,其实,我们做一个功能也可以这么有趣。之前我们在商详那边商品数量一般来说为了避免键盘操作,都直接是通过上面的 加减按钮来操作,然后在购物车,每次更改商品数量还可能会引发购物车价格活动因素的刷新,所以可想而知,当我一个商品想从1加到10的时候,你要点10次加号,然后要接受购物车的10次刷新。。。其实我是不能接受的,但是是在由于购物车的活动价格等复杂的因素,不得不有一个即时性。但是通过这种方式,拖动的方式,我们可以拖动到10,然后再松手,这个时候就之刷新一次了,是不~

实现方式

把那么多东西封装到一个空间里面,那就首先选择ViewGroup这样的喽,哈哈哈 直接上代码

/**
 * Author: Blincheng.
 * Date: 2017/6/9.
 * Description:一个有趣的商品数量加减交互控件
 */

class GoodAmountView : RelativeLayout, View.OnTouchListener, View.OnClickListener {
    var actionBack: OnActionBack?= null
    var ONCE_TIME = 160L //加减的时间间隔
    var ANIMATION_DURATION = 200L//恢复动画时间
    var startX = 0f
    var countView: LinearLayout?= null
    var onActionTime = 0L
    var isAction = false//是否正在被拖动(触发加减操作)
    var amount_tv: TextView?= null
    var add_img: ImageView?= null
    var sub_img: ImageView?= null
    var amount = 1//文本数量
    constructor(context: Context) : super(context) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init()
    }

    fun init(){
        LayoutInflater.from(context).inflate(R.layout.layout_good_amount_view, this, true)
        countView = findViewById(R.id.good_amount_linear) as LinearLayout
        countView?.setOnTouchListener(this)
        amount_tv = findViewById(R.id.good_amount_tv) as TextView
        add_img = findViewById(R.id.good_amount_add_btn) as ImageView
        sub_img = findViewById(R.id.good_amount_sub_btn) as ImageView
        add_img?.setOnClickListener(this)
        sub_img?.setOnClickListener(this)
    }
    override fun onClick(v: View?) {
        when(v?.id){
            R.id.good_amount_add_btn -> {
                amount ++
                amount_tv?.text = amount.toString()
                actionBack?.onAddAction()
            }
            R.id.good_amount_sub_btn -> {
                if(amount > 1)
                    amount --
                amount_tv?.text = amount.toString()
                actionBack?.onSubAction()
            }
        }
    }
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        when(event?.action){
            MotionEvent.ACTION_DOWN -> {
                startX = event?.rawX
            }
            MotionEvent.ACTION_MOVE -> {
                var moveX = event?.rawX - startX
                moveAnimation(moveX.toInt())
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                backAnimation()
            }
        }
        return true
    }
    fun moveAnimation(x: Int){
        if (x in -UIUtils.dip2px(context,30f)..UIUtils.dip2px(context,30f)){
            var layout: LayoutParams = countView?.layoutParams as LayoutParams
            layout?.leftMargin = x+ UIUtils.dip2px(context,30f)
            isAction = false
            countView?.layoutParams = layout
            return
        }
        //一些回调
        if(isAction){
            if(x < -UIUtils.dip2px(context,25f)){//减
                actionCallBack(false)
            }else if(x > UIUtils.dip2px(context,25f)){//加
                actionCallBack(true)
            }
        }else{
            isAction = true
            onActionTime = Date().time
        }
    }
    fun backAnimation(){
        var layout: LayoutParams = countView?.layoutParams as LayoutParams
        var valueAnimator = ValueAnimator.ofFloat(layout.leftMargin.toFloat(), UIUtils.dip2px(context,30f).toFloat())
        valueAnimator.duration = ANIMATION_DURATION
        valueAnimator.interpolator = AccelerateInterpolator()
        valueAnimator.addUpdateListener { animation ->
            var layout: LayoutParams = countView?.layoutParams as LayoutParams
            layout.leftMargin = (animation.animatedValue as Float).toInt()
            countView?.layoutParams = layout
        }
        valueAnimator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {
            }

            override fun onAnimationEnd(animation: Animator?) {
                actionBack?.onResult(amount)
            }

            override fun onAnimationCancel(animation: Animator?) {
            }

            override fun onAnimationStart(animation: Animator?) {
            }

        })
        valueAnimator.start()
    }
    fun actionCallBack(isAdd: Boolean){
        var now =  Date().time
        if(now - onActionTime > ONCE_TIME){
            if(isAdd){
                amount ++
                amount_tv?.text = amount.toString()
                actionBack?.onAddAction()
            }else{
                if(amount > 1)
                    amount --
                amount_tv?.text = amount.toString()
                actionBack?.onSubAction()
            }
            onActionTime = now
        }
    }
    fun setOnActionBackListener(onActionBack: OnActionBack){
        actionBack = onActionBack
    }
    interface OnActionBack{
        fun onAddAction()
        fun onSubAction()
        fun onResult(amount: Int)
    }
}

接下来就就是来分析一下,这边我选择使用了RelativeLayout来作为我们的最外层布局,通过加载一个layout布局文件。也可以简单看下Kotlin的语法哈,继承关系是通过:来处理的。然后必要的要重写实现的方法需要用constructor关键字。

fun init(){
        LayoutInflater.from(context).inflate(R.layout.layout_good_amount_view, this, true)
        countView = findViewById(R.id.good_amount_linear) as LinearLayout
        countView?.setOnTouchListener(this)
        amount_tv = findViewById(R.id.good_amount_tv) as TextView
        add_img = findViewById(R.id.good_amount_add_btn) as ImageView
        sub_img = findViewById(R.id.good_amount_sub_btn) as ImageView
        add_img?.setOnClickListener(this)
        sub_img?.setOnClickListener(this)
    }

我们通过直接LayoutInflater.from(context).inflate(R.layout.layout_good_amount_view, this, true)来加载这个布局,然后就是一些必要控件的初始化和绑定操作。

实现手指拖动效果

看效果图,其实我们只关心手指的X轴的移动距离,对吧。然后我的布局是RelativeLayout,所以最终是通过leftMargin这个属性的变化来实现移动的效果。

override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        when(event?.action){
            MotionEvent.ACTION_DOWN -> {
                startX = event?.rawX
            }
            MotionEvent.ACTION_MOVE -> {
                var moveX = event?.rawX - startX
                moveAnimation(moveX.toInt())
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                backAnimation()
            }
        }
        return true
    }

我们再看看moveAnimation这个方法

fun moveAnimation(x: Int){
        if (x in -UIUtils.dip2px(context,30f)..UIUtils.dip2px(context,30f)){
            var layout: LayoutParams = countView?.layoutParams as LayoutParams
            layout?.leftMargin = x+ UIUtils.dip2px(context,30f)
            isAction = false
            countView?.layoutParams = layout
            return
        }
        //一些回调
        if(isAction){
            if(x < -UIUtils.dip2px(context,30f)){//减
                actionCallBack(false)
            }else if(x > UIUtils.dip2px(context,30f)){//加
                actionCallBack(true)
            }
        }else{
            isAction = true
            onActionTime = Date().time
        }
    }

看上面代码,注意if里面的return,也就是说,上面if里面是正常的拖动移动位置,当移动到一个边界值的时候,就触发我们的数量加减操作,并且不移动中间的文本。然后处理一些回调,回调的之后说明。

松手回弹动画

fun backAnimation(){
        var layout: LayoutParams = countView?.layoutParams as LayoutParams
        var valueAnimator = ValueAnimator.ofFloat(layout.leftMargin.toFloat(), UIUtils.dip2px(context,30f).toFloat())
        valueAnimator.duration = ANIMATION_DURATION
        valueAnimator.interpolator = AccelerateInterpolator()
        valueAnimator.addUpdateListener { animation ->
            var layout: LayoutParams = countView?.layoutParams as LayoutParams
            layout.leftMargin = (animation.animatedValue as Float).toInt()
            countView?.layoutParams = layout
        }
        valueAnimator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {
            }

            override fun onAnimationEnd(animation: Animator?) {
                actionBack?.onResult(amount)
            }

            override fun onAnimationCancel(animation: Animator?) {
            }

            override fun onAnimationStart(animation: Animator?) {
            }

        })
        valueAnimator.start()
    }

这边就一个还原动画,那就借助ValueAnimator来实现呗。把一个当前的leftMargin和初始值传入,然后就让ValueAnimator来帮我们实现呗。layout.leftMargin = (animation.animatedValue as Float).toInt()这行代码我单独说下。。。因为这边animation是一个Object类型,刚开始我就直接as Int然后就报错了,需要知道的是Kotlin也是需要先把Object转成Float,然后再通过基本类型的toInt来转换。

数量变更

fun actionCallBack(isAdd: Boolean){
        var now =  Date().time
        if(now - onActionTime > ONCE_TIME){
            if(isAdd){
                amount ++
                amount_tv?.text = amount.toString()
                actionBack?.onAddAction()
            }else{
                if(amount > 1)
                    amount --
                amount_tv?.text = amount.toString()
                actionBack?.onSubAction()
            }
            onActionTime = now
        }
    }

因为数量的增加是看手指拖动的时间来决定的,所以我这边是采用这样的方式,先在边界的时候出发加减操作的第一次记录那个时候的时间,然后在每次move的时候通过时间的计算来看看是否大于ONCE_TIME,如果大于了,就该加加该减减。然后覆盖第一次触发的时间为当前时间。

应该需要的回调

interface OnActionBack{
        fun onAddAction()//加事件
        fun onSubAction()//减事件
        fun onResult(amount: Int)//最终回弹后回调事件
    }

本次Kotlin开发带来的一些感受

这边就来说说这次用Kotlin来开发和Java的区别哈~其实我自从去年那篇博文之后,也没有真正意义上用Kotlin来开发Android。
1. `声明真的变化很大

val ONCE_TIME = 160L //加减的时间间隔
    val ANIMATION_DURATION = 200L//恢复动画时间

这边这些常量的声明真的简单明了,全靠Kotlin自己推断,然后不习惯的就是变量的声明

var amount_tv: TextView?= null
var add_img: ImageView?= null
var sub_img: ImageView?= null

看看哈,以往怎么声明?后面一定要跟null吗?nonono,但是直接跟null还是有问题的,还必须第一次不赋值的情况下还要加个?,表示可能为null类型。哈哈哈~习惯一下吧,毕竟这样强制做的好处就是空类型安全。
2. `各种有可能为null的变量使用之前都要加?

actionBack?.onAddAction()

哈哈哈,其实真的很棒的,体验一下吧。
当然如果真的确定一定以及肯定,你可以加在使用之前加!!表示该变量一定不为null
3. switch没了。变成了when

when(x){
    1 -> {}
    2 -> {}
    3 -> {}
    else ->{}
}

这就是最简单的用法,的确去掉那个以前可恶的case 用起来还是很爽的 ,没有多余的代码,也不用break。甚至可以用任意表达式(不只是常量哦)作为分之条件,是不是非常爽哈。当然,这么强大的都可以取代if了。
4. 匿名表达式

valueAnimator.addUpdateListener { animation ->

        }

上面貌似有用到哈,这是最简单的,一参的。和java中的匿名内部类那样,我们直接就可以这么简单粗暴的使用~当然你可以直接选择自动补全

valueAnimator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {
            }

            override fun onAnimationEnd(animation: Animator?) {
                actionBack?.onResult(amount)
            }

            override fun onAnimationCancel(animation: Animator?) {
            }

            override fun onAnimationStart(animation: Animator?) {
            }

        })

这边还有一个就是这个鬼~刚开始其实我自动补全啊 ,什么的折腾了一会,就是报错,不知道这边语法要怎么走~然后最后发现竟然有多个需要实现的方法的时候,要这么做~其实我对Kotlin的语法了解也不多,哈哈哈。
5. java中的get真的没了
比如之前你在java中想在Fragment中获取当前Activity,直接通过getActivity()来获取是吧,然后现在就变成了这样activity,直接可以通过activity这个变量来获取~studio也会直接提示,你用自动补全按回车后的结果也是这样哦~
这里写图片描述
6. in关键词
之前比如说你要判断x是否在1到10之间,就要这样做if(x>1&&x<10)但是貌似现在这么写,studio直接会提示你要你改成这样if(in 1..10)哈哈哈 貌似挺棒的,的确简洁。

项目地址

https://github.com/Blincheng/BlinChengDemos

总结

最后吧,提前祝大家周末愉快喽~公司本周周五开始到周日有上海亚米星球美食节,吃货们可以去哈~我也建议大家可以去尝试用Kotlin写一些Demo,这样或许比你直接看Kotlin的文档,以为看一遍就能上手用的快得多,高效得多吧!之前我有尝试直接去看文档,真的。。。那真的枯燥无味,但是你想想,如果你用Kotlin去开发一点东西,然后自然而然就慢慢的熟悉掌握Kotlin的用法了呢!哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值